Análisis de Vulnerabilidades de Autenticación en Aplicaciones Java: Un Estudio de Caso en Filtros de Sesión

  1. Introducción: La Complejidad de los Filtros de Sesión

Durante una auditoría de seguridad en un sistema basado en Java, se identificó un componente crítico: un filtro de sesión (similar a SessionFilter). Este tipo de filtro actúa como la primera línea de defensa en el control de acceso, siendo responsable de validar las solicitudes entrantes y asegurar que solo los usuarios autenticados accedan a recursos protegidos. Sin embargo, su implementación reveló varias deficiencias de seguridad que podrían ser explotadas para eludir los controles de autenticación.

  1. Fallas en la Lista Blanca de Rutas

2.1. Lógica de Validación Permisiva

El sistema empleaba una lista blanca de rutas que supuestamente permitía el acceso sin autenticación a ciertas URLs. La lógica para esta validación presentaba una vulnerabilidad:


public static boolean esRutaPublica(String rutaSolicitada, HttpServletRequest req) {
    String[] rutasExcluidas = new String[]{"/inicio_sesion.jsp", "/api/usuarios/acceso.do", "/servicio/", /* ... */};
    String contextoApp = req.getContextPath(); // e.g., /miApp
    
    for (String rutaExcluida : rutasExcluidas) {
        // La comparación se realiza uniendo el contexto con la ruta excluida
        if (rutaSolicitada.startsWith(contextoApp + rutaExcluida)) {
            return true; // Acceso permitido sin validación de sesión
        }
    }
    return false;
}

La debilidad radica en que cualquier URL que comience con una de las entradas de la lista blanca, incluso después de la concatenación con contextPath, se considera válida. Esto abre la puerta a técnicas de derivación de rutas.

2.2. Elusión por Normalización de Rutas

Un atacante podría explotar la forma en que los servidores web o los marcos de trabajo normalizan las rutas URL. Por ejemplo, al inyectar secuencias como ../, la ruta solicitada puede transformarse de una manera que coincida con una entrada de la lista blanca.

Ejemplo de Ataque:

  • Una URL maliciosa como /aplicacion/recursos/../inicio_sesion.jsp
  • Podría ser normalizada internamente a /aplicacion/inicio_sesion.jsp

Si /inicio_sesion.jsp está en la lista blanca y la comparación es por prefijo, el atacante lograría eludir la autenticación y acceder a recursos restringidos.

  1. Deficiencias en los Mecanismos de Control Adicionales

3.1. Validación de Referer Falsificable

El filtro implementaba una comprobación del encabezado Referer para la mayoría de las solicitudes, excepto para un subconjunto específico de rutas. No obstante, el encabezado Referer es fácilmente manipulable por un atacante.


public boolean esRefererValido(String pathActual, HttpServletRequest req) {
    String[] rutasExentas = new String[]{"/api/sso/login"};
    String contextoApp = req.getContextPath();

    // Solo se exime la ruta especificada
    for (String exenta : rutasExentas) {
        if (pathActual.startsWith(contextoApp + exenta)) {
            return true; // No se valida Referer para esta ruta
        }
    }

    String referer = req.getHeader("Referer");
    // Lógica de validación débil o inexistente para el resto...
    // Si Referer es nulo o no se valida contra un dominio esperado, podría ser omitido.
    return (referer != null && referer.contains("dominioSeguro.com")); // Ejemplo simplificado
}

Un atacante puede simplemente configurar el encabezado Referer: http://dominioSeguro.com/ para pasar esta verificación, incluso si la solicitud proviene de una fuente no autorizada.

3.2. Dependencia de Configuraciones Externas

Ciertas reglas de seguridad dependían de banderas de configuración globales, como interruptores para sistemas de terceros. Esto introduce un riesgo significativo:


if (ConfiguracionGlobal.SERVICIO_EXTERNO_ACTIVO && "PROD".equals(ConfiguracionGlobal.ENTORNO_TIPO) 
    && this.esAccesoRestringido(rutaActual, req)) {
    resp.setStatus(HttpServletResponse.SC_FORBIDDEN); // 403 Forbidden
    return;
}

La seguridad del sistema no debería depender de configuraciones que puedan ser inconsistentes en entornos de alta concurrencia o que puedan modificarse dinámicamente sin un control de seguridad estricto.

3.3. Lógica Defectuosa en esAccesoRestringido

La función encargada de determinar si el acceso debe ser restringido (esAccesoRestringido) tenía condiciones muy específicas, creando grandes brechas para la mayoría de los puntos finales de la API.


private boolean esAccesoRestringido(String pathRecurso, HttpServletRequest req) {
    if (pathRecurso.contains("extension/flujo")) {
        // Solo restringe métodos que no sean GET
        String metodoHttp = req.getMethod();
        return !"GET".equalsIgnoreCase(metodoHttp);
    }
    // Restricciones para otras rutas específicas
    String[] rutasCriticas = new String[]{"extension/confianza"};
    for (String critica : rutasCriticas) {
        if (pathRecurso.contains(critica)) {
            // Aplica alguna lógica de restricción aquí, si la hay
            return true; 
        }
    }
    return false; // Por defecto, la mayoría de las rutas no están restringidas
}

Esto implica que si una ruta no contenía "extension/flujo" (o se accedía a ella con GET), y no era una "extension/confianza", se consideraba segura. La mayoría de las operaciones de negocio caen fuera de estas condiciones, haciendo que el filtro sea ineficaz.

  1. Debilidades en el Manejo de Solicitudes Ajax

Una vulnerabilidad crítica se encontró en la forma en que el filtro manejaba las solicitudes Ajax frente a las solicitudes regulares:


if (req.getHeader("X-Requested-With") != null 
    && req.getHeader("X-Requested-With").equalsIgnoreCase("XMLHttpRequest")) {
    // Lógica para solicitudes Ajax: verifica un token de sesión o cookie
    String identificadorSesion = UtilCookies.obtenerCookie(req, "identificador_sesion");
    if (identificadorSesion == null || identificadorSesion.isEmpty()) {
        resp.setHeader("EstadoSesion", "expirada");
        return; // Sesión expirada para Ajax
    }
    cadenaFiltros.doFilter(req, resp);
} else {
    // ¡Las solicitudes que no son Ajax se procesan directamente sin validar sesión!
    cadenaFiltros.doFilter(req, resp);
}

El problema fatal es que si el encabezado X-Requested-With no está presente (lo cual es el comportamiento predeterminado de un navegador para una solicitud normal), el filtro omite completamente la validación de la sesión. Un atacante puede simplemente no incluir este encabezado en sus solicitudes para eludir todas las comprobaciones de sesión.

  1. Cadena de Explotación y Evaluación de Riesgos

5.1. Construcción de la Cadena de Ataque

La combinación de estas vulnerabilidades permite a un atacante construir una cadena de ataque efectiva:

  1. Eludir la lista blanca: Utilizando técnicas de normalización de rutas (ej., ../).
  2. Falsificar Referer: Estableciendo un encabezado Referer válido para pasar la verificación (si aplica).
  3. Evitar esAccesoRestringido: Apuntando a interfaces de negocio comunes que no cumplen las condiciones de restricción.
  4. Saltar validación de sesión para Ajax: Asegurándose de no incluir el encabezado X-Requested-With.

5.2. Impacto de las Vulnerabilidades

La explotación exitosa de estas fallas puede llevar a consecuencias graves:

  • Acceso no autorizado: A cualquier funcionalidad que no esté explícitamente protegida de forma robusta.
  • Fuga de datos: Obtención de información sensible del negocio.
  • Escalada de privilegios: Ejecución de operaciones con permisos elevados a través de interfaces no autorizadas.
  • Manipulación de lógica de negocio: Derivación de flujos de trabajo y controles empresairales normales.
  1. Recomendaciones de Mitigación y Buenas Prácticas

6.1. Medidas de Reparación Inmediata

  • **Refuerzo de la Validación de Rutas:**Implementar una normalización estricta de las rutas URL antes de cualquier comparación con listas blancas. Es crucial que la normalización ocurra antes de que el filtro evalúe la URL.

    
    // Normalizar la ruta antes de la comparación
    String rutaNormalizada = java.nio.file.Paths.get(rutaSolicitada).normalize().toString();
    // Luego, comparar rutaNormalizada con las entradas de la lista blanca
    
    
  • **Validación Robusta de Referer:**Validar el Referer de manera integral, comprobando el dominio y, si es posible, el origen completo, y no solo la presencia del encabezado o un prefijo simple.

    
    String referer = req.getHeader("Referer");
    if (referer != null) {
        URI uriReferer = new URI(referer);
        // Verificar el host del Referer contra una lista de hosts permitidos
        if (!hostPermitido(uriReferer.getHost())) {
            // Registrar y denegar el acceso
            resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
            return;
        }
    } else {
        // Si Referer es nulo, decidir si se permite o deniega por defecto
        // Una política estricta denegaría
    }
    
    
  • **Eliminar la Lógica de Excepción para Ajax:**Aplicar una lógica de validación de sesión y autenticación uniforme para todas las solicitudes, independientemente de si son Ajax o no. El encabezado X-Requested-With no debe ser un criterio para omitir controles de seguridad fundamentales.

    
    // Lógica de validación de sesión unificada para todas las solicitudes
    String tokenSesion = UtilCookies.obtenerCookie(req, "token_seguridad");
    if (tokenSesion == null || tokenSesion.isEmpty() || !validarToken(tokenSesion)) {
        // Manejar la expiración o falta de sesión de forma consistente
        if ("XMLHttpRequest".equalsIgnoreCase(req.getHeader("X-Requested-With"))) {
            resp.setHeader("EstadoSesion", "no_autorizado");
        } else {
            resp.sendRedirect(req.getContextPath() + "/relogin.jsp");
        }
        return;
    }
    cadenaFiltros.doFilter(req, resp);
    
    

Etiquetas: java Servlet Filter Auditoría de Código autenticación Autorización

Publicado el 7-2 01:42