2009-09-14 12 views
15

En mi web.xml he definido un usuario de datos en restricciones para algunos recursos:¿Cómo prevenir el secuestro de sesiones de tomcat?

<security-constraint> 
    <web-resource-collection> 
     <web-resource-name>Personal Area</web-resource-name> 
     <url-pattern>/personal/*</url-pattern> 
    </web-resource-collection> 
    <web-resource-collection> 
     <web-resource-name>User Area</web-resource-name> 
     <url-pattern>/user/*</url-pattern> 
    </web-resource-collection> 
    <user-data-constraint> 
     <transport-guarantee>CONFIDENTIAL</transport-guarantee> 
    </user-data-constraint> 
</security-constraint> 
  1. Cuando me carga la página http Tengo mi JSESSIONID ID1 en mi galleta.
  2. Cuando cambio a context/user/sample.faces, Tomcat realiza una redirección 302 a HTTPS. Pero mi JSESSIONID sigue siendo ID1.

Creo que esto es una vulnerabilidad? ¿O es mi error de configuración?

El problema que veo es el siguiente: mientras navego por HTTP con la cookie ID1, hay un atacante que está escuchando mi tráfico de red. Él "roba" mi cookie ID1. Ahora cambio a HTTPS y mi cookie sigue siendo ID1. Me conecto El atacante puede tomar mi sesión porque sabe mi cookie ...

+0

Re tu comentario: El motivo por el que SSLID es el mismo es que la sesión es la misma (después de todo, hice clic en actualizar en Firefox). Puede usar este hecho en la gestión de su sesión. En cuanto a cómo se construye el SSLID, no está cubierto por las especificaciones del servlet, por lo que cada proveedor puede usar sus propios mecanismos. Tendría que verificar las fuentes de Tomcat, supongo. De todos modos, no debe confiar en ninguna implementación específica, simplemente utilícela como usaría JSESSIONID, como un valor opaco. –

Respuesta

11

Si se trata de una versión reciente de Tomcat, es posible que no tenga ningún problema. Sin embargo, esto depende de que verifique la ID de SSL asociada con la sesión. Este servicio está disponible utilizando código como

String sslId = (String) req.getAttribute("javax.servlet.request.ssl_session"); 

(Tenga en cuenta que la clave de atributo puede cambiar en el futuro para javax.servlet.request.ssl_session_id - como parte de la especificación Servlet 3.0).

puedo tener un servlet con el doGet método siguiente:

protected void doGet(HttpServletRequest request, HttpServletResponse response) 
        throws ServletException, IOException { 
    HttpSession session = request.getSession(true); 
    String sid = session.getId(); 
    String sslId = (String) request.getAttribute(
       "javax.servlet.request.ssl_session"); 
    String uri = request.getRequestURI(); 
    OutputStream out = response.getOutputStream(); 
    PrintWriter pw = new PrintWriter(out); 
    HashMap<String, Object> secrets; 
    Object secret = null; 
    Object notSecret; 
    Date d = new Date(); 

    notSecret = session.getAttribute("unprotected"); 
    if (notSecret == null) { 
     notSecret = "unprotected: " + d.getTime(); 
     session.setAttribute("unprotected", notSecret); 
    } 
    secrets = (HashMap<String, Object>) session.getAttribute("protected"); 
    if (secrets == null) { 
     secrets = new HashMap<String, Object>(); 
     session.setAttribute("protected", secrets); 
    } 
    if (sslId != null) { 
     if (secrets.containsKey(sslId)) 
      secret = secrets.get(sslId); 
     else { 
      secret = "protected: " + d.getTime(); 
      secrets.put(sslId, secret); 
     } 
    } 
    response.setContentType("text/plain"); 
    pw.println(MessageFormat.format("URI: {0}", new Object[] { uri })); 
    pw.println(MessageFormat.format("SID: {0}", new Object[] { sid })); 
    pw.println(MessageFormat.format("SSLID: {0}", new Object[] { sslId })); 
    pw.println(MessageFormat.format("Info: {0}", new Object[] { notSecret })); 
    pw.println(MessageFormat.format("Secret: {0}", new Object[] { secret })); 
    pw.println(MessageFormat.format("Date: {0}", new Object[] { d })); 
    pw.close(); 
} 

entonces invoca una URL sin protección adecuada usando Firefox y la extensión vivo encabezados HTTP, para obtener la cookie de sesión. Esta fue la respuesta enviada cuando navegaba a

http://localhost:8080/EchoWeb/unprotected 

(mi web.xml, como la suya, sólo protege/usuario/* y/personal/*):

 
URI: /EchoWeb/unprotected 
SID: 9ACCD06B69CA365EFD8C10816ADD8D71 
SSLID: null 
Info: unprotected: 1254034761932 
Secret: null 
Date: 27/09/09 07:59 

A continuación, me trataron de acceder a una URL protegida

http://localhost:8080/EchoWeb/personal/protected 

y, como era de esperar, me redirecciona a

https://localhost:8443/EchoWeb/personal/protected 

y la respuesta fue

 
URI: /EchoWeb/personal/protected 
SID: 9ACCD06B69CA365EFD8C10816ADD8D71 
SSLID: 4abf0d67549489648e7a3cd9292b671ddb9dd844b9dba682ab3f381b462d1ad1 
Info: unprotected: 1254034761932 
Secret: protected: 1254034791333 
Date: 27/09/09 07:59 

en cuenta que el ID de cookie/sesión es el mismo, pero ahora tenemos un nuevo SSLID. Ahora, intentemos falsificar el servidor usando la cookie de sesión.

He creado un script en Python, spoof.py:

import urllib2 

url = "https://localhost:8443/EchoWeb/personal/protected" 
headers = { 
    'Host': 'localhost:8080', 
    'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3', 
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 
    'Accept-Language': 'en-gb,en;q=0.5', 
    'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 
    'Cookie' : 'JSESSIONID=9ACCD06B69CA365EFD8C10816ADD8D71' 
} 
req = urllib2.Request(url, None, headers) 
response = urllib2.urlopen(req) 
print response.read() 

Ahora, usted no necesita saber Python, sobre todo - Sólo estoy tratando de enviar una petición HTTP a un (diferente) recurso protegido con la misma ID de sesión en la Cookie. Aquí está la respuesta cuando me encontré con mi guión parodia dos veces:

 
C:\temp>spoof 
URI: /EchoWeb/personal/protected 
SID: 9ACCD06B69CA365EFD8C10816ADD8D71 
SSLID: 4abf0eafb4ffa30b6579cf189c402a8411294201e2df94b33a48ae7484f22854 
Info: unprotected: 1254034761932 
Secret: protected: 1254035119303 
Date: 27/09/09 08:05 


C:\temp>spoof 
URI: /EchoWeb/personal/protected 
SID: 9ACCD06B69CA365EFD8C10816ADD8D71 
SSLID: 4abf0eb184cb380ce69cce28beb01665724c016903650539d095c671d98f1de3 
Info: unprotected: 1254034761932 
Secret: protected: 1254035122004 
Date: 27/09/09 08:05 

Aviso en las respuestas anteriores de que los datos de la sesión (un valor con una marca de tiempo de 1254034761932) que se estableció en la primera solicitud, sin protección, ha sido enviado a lo largo , porque Tomcat está utilizando la misma sesión porque la ID de la sesión es la misma. Esto es por supuesto no seguro. Sin embargo, tenga en cuenta que los ID de SSL fueron diferentes cada vez y si usa esos para ingresar los datos de la sesión (por ejemplo, como se muestra), debe estar seguro. Si refrescarme la pestaña de Firefox, aquí está la respuesta:

 
URI: /EchoWeb/personal/protected 
SID: 9ACCD06B69CA365EFD8C10816ADD8D71 
SSLID: 4abf0d67549489648e7a3cd9292b671ddb9dd844b9dba682ab3f381b462d1ad1 
Info: unprotected: 1254034761932 
Secret: protected: 1254034791333 
Date: 27/09/09 08:05 

en cuenta que la SSLID es la misma como para la solicitud anterior de Firefox. Entonces, el servidor puede diferenciar las sesiones usando el valor de ID de SSL. Observe especialmente que los "datos protegidos" son los mismos para cada solicitud hecha desde la sesión de Firefox, pero diferente para cada una de las sesiones falsificadas y también diferente de la sesión de Firefox.

+0

¡Grandes explicaciones! Pero todavía tengo una pregunta: ¿cómo funciona, que su última llamada con Firefox envía el antiguo SSLID. ¿Cómo se construye esa identificación? ¿Esta característica está documentada en alguna parte? – Marcel

2

Creo que funciona así por diseño. No puede basar su control de acceso en la sesión. Necesita usar otros parámetros. Necesita agregar autenticación y usar control basado en roles.

En Tomcat, hay protección, pero exactamente lo contrario. Si obtiene una sesión en un área segura, esa sesión no se transfiere al área desprotegida. Tomcat logra esto estableciendo un indicador "seguro" en la cookie para que la cookie no se envíe a las conexiones HTTP.

+0

No base control de acceso en la sesión. El web.xml anterior es solo una pequeña parte de todo el web.xml. Por supuesto, tengo implementado un método de autenticación basado en JAAS. Intento ser un poco más claro en mi descripción. – Marcel

2

Sugiero que cambie el sessionId cuando autentique la sesión.
De esta manera, el viejo sessionId se vuelve inútil y el secuestro de sesión es imposible.
Para cambiar el sessionId en un contenedor de servlets:

  • copiar todos los atributos de la sesión actual en una colección temp
  • session.invalidate()
  • sesión = req.getSession (verdadero)
  • rellene la nueva sesión con los atributos de la colección temporal

Acerca de SSLID, tenga en cuenta que tanto el cliente como el servidor pueden cerrar la conexión en cualquier momento hora. Cuando se cierra, se produce un nuevo protocolo de enlace SSL y se genera un nuevo SSID. Por lo tanto, IMO SSLID no es una manera confiable de rastrear (o ayudar a rastrear) las sesiones.

Cuestiones relacionadas