¿Por qué es común poner tokens de prevención CSRF en las cookies?

283

Estoy tratando de entender todo el problema con CSRF y las formas adecuadas de prevenirlo. (Recursos que he leído, entiendo y estoy de acuerdo con: OWASP CSRF Prevention CHeat Sheet , Preguntas sobre CSRF ).

Según tengo entendido, la vulnerabilidad en torno a CSRF se introduce por el supuesto de que (desde el punto de vista del servidor web) una cookie de sesión válida en una solicitud HTTP entrante refleja los deseos de un usuario autenticado. Pero todas las cookies para el dominio de origen se unen mágicamente a la solicitud del navegador, por lo que realmente todo el servidor puede inferir de la presencia de una cookie de sesión válida en una solicitud que la solicitud proviene de un navegador que tiene una sesión autenticada; no puede asumir nada más sobre el códigoejecutándose en ese navegador, o si realmente refleja los deseos del usuario. La forma de evitar esto es incluir información de autenticación adicional (el "token CSRF") en la solicitud, transportada por algún otro medio que no sea el manejo automático de cookies del navegador. En términos generales, la cookie de sesión autentica al usuario / navegador y el token CSRF autentica el código que se ejecuta en el navegador.

En pocas palabras, si está utilizando una cookie de sesión para autenticar a los usuarios de su aplicación web, también debe agregar un token CSRF a cada respuesta y requerir un token CSRF coincidente en cada solicitud (mutante). El token CSRF luego realiza un viaje de ida y vuelta desde el servidor al navegador de regreso al servidor, demostrando al servidor que la página que realiza la solicitud es aprobada por (generado por, incluso) ese servidor.

En cuanto a mi pregunta, que trata sobre el método de transporte específico utilizado para ese token CSRF en ese viaje de ida y vuelta.

Parece común (por ejemplo, en AngularJS , Django , Rails ) enviar el token CSRF del servidor al cliente como una cookie (es decir, en un encabezado Set-Cookie), y luego tener Javascript en el cliente para eliminarlo de la cookie y adjuntarlo como un encabezado XSRF-TOKEN separado para enviar de vuelta al servidor.

(Un método alternativo es el recomendado por , por ejemplo , Express , donde el token CSRF generado por el servidor se incluye en el cuerpo de la respuesta a través de la expansión de la plantilla del lado del servidor, adjunta directamente al código / marcado que lo devolverá al servidor, por ejemplo como una entrada de formulario oculta. Ese ejemplo es una forma más web 1.0-ish de hacer las cosas, pero generalizaría bien a un cliente más pesado de JS).

¿Por qué es tan común usar Set-Cookie como transporte descendente para el token CSRF / por qué es una buena idea? Me imagino que los autores de todos estos marcos consideraron sus opciones cuidadosamente y no se equivocaron. Pero a primera vista, el uso de cookies para evitar lo que es esencialmente una limitación de diseño de las cookies parece una tontería. De hecho, si utilizó cookies como transporte de ida y vuelta (Set-Cookie: encabezado aguas abajo para que el servidor le diga al navegador el token CSRF, y Cookie: encabezado aguas arriba para que el navegador lo devuelva al servidor) volvería a introducir la vulnerabilidad están tratando de arreglarlo

Me doy cuenta de que los marcos anteriores no usan cookies para todo el viaje de ida y vuelta para el token CSRF; usan Set-Cookie en sentido descendente, luego algo más (por ejemplo, un encabezado X-CSRF-Token) en sentido ascendente, y esto elimina la vulnerabilidad. Pero incluso el uso de Set-Cookie como transporte descendente es potencialmente engañoso y peligroso; el navegador ahora adjuntará el token CSRF a cada solicitud, incluidas las solicitudes XSRF maliciosas genuinas; en el mejor de los casos, eso hace que la solicitud sea más grande de lo que debe ser y, en el peor de los casos, un código de servidor bien intencionado pero equivocado podría intentar usarlo, lo que sería realmente malo. Y además, dado que el destinatario real del token CSRF es Javascript del lado del cliente, eso significa que esta cookie no se puede proteger con http-only.

metamatt
fuente
Es una gran pregunta golpear el lugar correcto.
kta

Respuestas:

262

Una buena razón, que ya mencionó, es que una vez que se recibe la cookie CSRF, está disponible para su uso en toda la aplicación en el script del cliente para su uso en formularios regulares y POST AJAX. Esto tendrá sentido en una aplicación pesada de JavaScript, como la empleada por AngularJS (el uso de AngularJS no requiere que la aplicación sea una aplicación de una sola página, por lo que sería útil donde el estado necesita fluir entre diferentes solicitudes de página donde el valor CSRF normalmente no puede persistir en el navegador).

Considere los siguientes escenarios y procesos en una aplicación típica para algunos pros y contras de cada enfoque que describa. Estos se basan en el patrón de token de sincronizador .

Enfoque del cuerpo de solicitud

  1. El usuario inicia sesión correctamente.
  2. El servidor emite la cookie de autenticación.
  3. El usuario hace clic para navegar a un formulario.
  4. Si aún no se generó para esta sesión, el servidor genera un token CSRF, lo almacena en la sesión del usuario y lo envía a un campo oculto.
  5. El usuario envía el formulario.
  6. El servidor verifica que el campo oculto coincida con el token almacenado de la sesión.

Ventajas:

  • Simple de implementar.
  • Funciona con AJAX.
  • Trabaja con formas.
  • La cookie en realidad puede ser solo HTTP .

Desventajas

  • Todos los formularios deben generar el campo oculto en HTML.
  • Cualquier POST AJAX también debe incluir el valor.
  • La página debe saber de antemano que requiere el token CSRF para poder incluirlo en el contenido de la página, por lo que todas las páginas deben contener el valor del token en algún lugar, lo que podría llevar mucho tiempo implementarlo en un sitio grande.

Encabezado HTTP personalizado (aguas abajo)

  1. El usuario inicia sesión correctamente.
  2. El servidor emite la cookie de autenticación.
  3. El usuario hace clic para navegar a un formulario.
  4. La página se carga en el navegador, luego se realiza una solicitud AJAX para recuperar el token CSRF.
  5. El servidor genera un token CSRF (si aún no se ha generado para la sesión), lo almacena en la sesión del usuario y lo envía a un encabezado.
  6. El usuario envía el formulario (el token se envía a través de un campo oculto).
  7. El servidor verifica que el campo oculto coincida con el token almacenado de la sesión.

Ventajas:

  • Funciona con AJAX.
  • La cookie puede ser solo HTTP .

Desventajas

  • No funciona sin una solicitud AJAX para obtener el valor del encabezado.
  • Todos los formularios deben tener el valor agregado a su HTML dinámicamente.
  • Cualquier POST AJAX también debe incluir el valor.
  • La página debe hacer una solicitud AJAX primero para obtener el token CSRF, por lo que significará un viaje de ida y vuelta adicional cada vez.
  • También podría simplemente enviar el token a la página que guardaría la solicitud adicional.

Encabezado HTTP personalizado (ascendente)

  1. El usuario inicia sesión correctamente.
  2. El servidor emite la cookie de autenticación.
  3. El usuario hace clic para navegar a un formulario.
  4. Si aún no se ha generado para esta sesión, el servidor genera un token CSRF, lo almacena en la sesión del usuario y lo muestra en algún lugar del contenido de la página.
  5. El usuario envía el formulario a través de AJAX (el token se envía a través del encabezado).
  6. El servidor verifica que el encabezado personalizado coincida con el token almacenado de la sesión.

Ventajas:

  • Funciona con AJAX.
  • La cookie puede ser solo HTTP .

Desventajas

  • No funciona con formularios.
  • Todos los POST de AJAX deben incluir el encabezado.

Encabezado HTTP personalizado (ascendente y descendente)

  1. El usuario inicia sesión correctamente.
  2. El servidor emite la cookie de autenticación.
  3. El usuario hace clic para navegar a un formulario.
  4. La página se carga en el navegador, luego se realiza una solicitud AJAX para recuperar el token CSRF.
  5. El servidor genera un token CSRF (si aún no se ha generado para la sesión), lo almacena en la sesión del usuario y lo envía a un encabezado.
  6. El usuario envía el formulario a través de AJAX (el token se envía a través del encabezado).
  7. El servidor verifica que el encabezado personalizado coincida con el token almacenado de la sesión.

Ventajas:

  • Funciona con AJAX.
  • La cookie puede ser solo HTTP .

Desventajas

  • No funciona con formularios.
  • Todos los POST de AJAX también deben incluir el valor.
  • La página debe hacer una solicitud AJAX primero para obtener el token CRSF, por lo que significará un viaje de ida y vuelta adicional cada vez.

Set-Cookie

  1. El usuario inicia sesión correctamente.
  2. El servidor emite la cookie de autenticación.
  3. El usuario hace clic para navegar a un formulario.
  4. El servidor genera un token CSRF, lo almacena en la sesión del usuario y lo envía a una cookie.
  5. El usuario envía el formulario a través de AJAX o mediante un formulario HTML.
  6. El servidor verifica que el encabezado personalizado (o el campo de formulario oculto) coincida con el token almacenado de la sesión.
  7. La cookie está disponible en el navegador para su uso en AJAX adicionales y solicitudes de formularios sin solicitudes adicionales al servidor para recuperar el token CSRF.

Ventajas:

  • Simple de implementar.
  • Funciona con AJAX.
  • Trabaja con formas.
  • No necesariamente requiere una solicitud AJAX para obtener el valor de la cookie. Cualquier solicitud HTTP puede recuperarla y se puede agregar a todos los formularios / solicitudes AJAX a través de JavaScript.
  • Una vez que se ha recuperado el token CSRF, ya que se almacena en una cookie, el valor se puede reutilizar sin solicitudes adicionales.

Desventajas

  • Todos los formularios deben tener el valor agregado a su HTML dinámicamente.
  • Cualquier POST AJAX también debe incluir el valor.
  • La cookie se enviará para cada solicitud (es decir, todos los GET para imágenes, CSS, JS, etc. que no estén involucrados en el proceso CSRF) aumentando el tamaño de la solicitud.
  • La cookie no puede ser solo HTTP .

Por lo tanto, el enfoque de cookies es bastante dinámico y ofrece una manera fácil de recuperar el valor de la cookie (cualquier solicitud HTTP) y usarla (JS puede agregar el valor a cualquier formulario automáticamente y puede emplearse en solicitudes AJAX como encabezado o como valor de forma). Una vez que se ha recibido el token CSRF para la sesión, no hay necesidad de regenerarlo ya que un atacante que emplea un exploit CSRF no tiene ningún método para recuperar este token. Si un usuario malintencionado intenta leer el token CSRF del usuario en cualquiera de los métodos anteriores, esto se evitará mediante la Política del mismo origen . Si un usuario malintencionado intenta recuperar el lado del servidor de token CSRF (por ejemplo, a través decurl), entonces este token no se asociará a la misma cuenta de usuario ya que la cookie de sesión de autenticación de la víctima no se incluirá en la solicitud (sería del atacante, por lo tanto, no se asociará al servidor con la sesión de la víctima).

Además del patrón de token de sincronizador, también existe la cookie de envío dobleMétodo de prevención CSRF, que por supuesto utiliza cookies para almacenar un tipo de token CSRF. Esto es más fácil de implementar ya que no requiere ningún estado del lado del servidor para el token CSRF. De hecho, el token CSRF podría ser la cookie de autenticación estándar cuando se utiliza este método, y este valor se envía a través de las cookies como de costumbre con la solicitud, pero el valor también se repite en un campo oculto o en un encabezado, del cual un atacante no puede replicarse como no pueden leer el valor en primer lugar. Sin embargo, se recomienda elegir otra cookie, que no sea la cookie de autenticación, de modo que la cookie de autenticación se pueda asegurar marcando HttpOnly. Entonces, esta es otra razón común por la que encontraría la prevención CSRF utilizando un método basado en cookies.

SilverlightFox
fuente
77
No estoy seguro de entender cómo "la solicitud AJAX se realiza para recuperar el token CSRF" (paso 4 en ambas secciones "encabezado personalizado: aguas abajo") se puede hacer de forma segura; Como se trata de una solicitud por separado, el servidor no sabe de quién viene; ¿Cómo sabe que es seguro divulgar el token CSRF? Me parece que si no puede sacar el token de la carga de la página inicial, pierde (lo que hace que el encabezado de respuesta descendente personalizado no sea un iniciador, desafortunadamente).
metamatt
66
Porque el falsificador no tendrá la cookie de sesión. Es posible que tengan su propia cookie de sesión, pero como el token CSRF está asociado a una sesión, su token CSRF no coincidirá con el de la víctima.
SilverlightFox
32
Según tengo entendido sobre el ataque CSRF, el falsificador tiene mi cookie de sesión. Bueno, en realidad no pueden ver la cookie, pero tienen la capacidad de proporcionarla en sus solicitudes falsificadas, porque las solicitudes provienen de mi navegador y mi navegador proporciona mi cookie de sesión. Entonces, desde el punto de vista del servidor, la cookie de sesión por sí sola no puede distinguir una solicitud legítima de una solicitud falsificada. De hecho, este es el ataque que estamos tratando de prevenir. Por cierto, gracias por su paciencia para hablar de esto, especialmente si estoy confundido acerca de esto.
metamatt
8
Tienen la capacidad de suministrar la cookie de autenticación, pero no pueden leer la respuesta que contiene el token CSRF.
SilverlightFox
8
@metamatt Perdón por el necro, pero lo haré por las personas que deambulan. Según tengo entendido, el atacante no suele tener acceso a la respuesta. CSRF se utiliza principalmente para causar efectos secundarios , en lugar de recopilar datos directamente. Por ejemplo, un script de ataque CSRF podría obligar a un usuario privilegiado a escalar los privilegios del atacante, deshabilitar una configuración de seguridad u obligar a un usuario de PayPal conectado a enviar una transferencia a una dirección de correo electrónico específica. En ninguno de estos casos, el atacante se preocupa por la respuesta, que aún se envía al navegador de la víctima; solo el resultado del ataque.
jonathanbruder
61

El uso de una cookie para proporcionar el token CSRF al cliente no permite un ataque exitoso porque el atacante no puede leer el valor de la cookie y, por lo tanto, no puede colocarla donde la validación CSRF del lado del servidor lo requiere.

El atacante podrá causar una solicitud al servidor con la cookie de token de autenticación y la cookie CSRF en los encabezados de solicitud. Pero el servidor no está buscando el token CSRF como una cookie en los encabezados de las solicitudes, está buscando en la carga útil de la solicitud. E incluso si el atacante sabe dónde colocar el token CSRF en la carga útil, tendrían que leer su valor para colocarlo allí. Pero la política de origen cruzado del navegador impide leer cualquier valor de cookie del sitio web de destino.

La misma lógica no se aplica a la cookie de token de autenticación, porque el servidor la espera en los encabezados de solicitud y el atacante no tiene que hacer nada especial para colocarla allí.

Tongfa
fuente
Sin embargo, un atacante no necesita leer la cookie en primer lugar. ¿Pueden simplemente insertar una imagen en el sitio pirateado con src='bank.com/transfer?to=hacker&amount=1000el que solicitará el navegador, completa con las cookies asociadas para ese sitio ( bank.com)?
desarrollo
2
CSRF es para validar al usuario en el lado del cliente, y no para proteger el sitio en general de un compromiso del lado del servidor como usted sugiere.
Tongfa
2
@developius enviar la cookie no es suficiente para satisfacer la protección CSRF. La cookie contiene el token csrf, tal como lo envió el servidor. El cliente legítimo debe leer el token csrf de la cookie y luego pasarlo a la solicitud en algún lugar, como un encabezado o en la carga útil. La protección CSRF verifica que el valor de la cookie coincida con el valor de la solicitud; de lo contrario, la solicitud se rechaza. Por lo tanto, el atacante necesita leer la cookie.
Will M.
1
Esta respuesta fue muy precisa para la pregunta del póster original y fue muy clara. +1 gracias.
java-addict301
@Tongfa: gracias, esto me ayudó a entender mejor. ¿Estoy en lo cierto al suponer que el token CSRF NO debe colocarse en el encabezado? debe estar en algún lugar del cuerpo?
zerohedge
10

Mi mejor conjetura en cuanto a la respuesta: considere estas 3 opciones para obtener el token CSRF del servidor al navegador.

  1. En el cuerpo de la solicitud (no es un encabezado HTTP).
  2. En un encabezado HTTP personalizado, no Set-Cookie.
  3. Como cookie, en un encabezado Set-Cookie.

Creo que el primero, el cuerpo de la solicitud (aunque lo demostró el tutorial Express que vinculé en la pregunta ), no es tan portátil para una amplia variedad de situaciones; no todos generan dinámicamente todas las respuestas HTTP; donde terminas necesitando poner el token en la respuesta generada puede variar ampliamente (en una entrada de forma oculta; en un fragmento de código JS o una variable accesible por otro código JS; tal vez incluso en una URL, aunque generalmente parece un mal lugar para poner tokens CSRF). Por lo tanto, mientras se puede trabajar con cierta personalización, # 1 es un lugar difícil para hacer un enfoque único para todos.

El segundo, el encabezado personalizado, es atractivo pero en realidad no funciona, porque si bien JS puede obtener los encabezados de un XHR que invocó, no puede obtener los encabezados de la página desde la que se cargó .

Eso deja al tercero, una cookie transportada por un encabezado Set-Cookie, como un enfoque que es fácil de usar en todas las situaciones (el servidor de cualquier persona podrá establecer encabezados de cookies por solicitud, y no importa qué tipo de los datos están en el cuerpo de la solicitud). Entonces, a pesar de sus desventajas, fue el método más fácil para que los marcos se implementen ampliamente.

metamatt
fuente
77
Podría estar diciendo lo obvio, ¿esto significa que la cookie no puede ser httponly correcta?
Fotón
1
solo para solicitudes ajax (donde JS necesita conocer el valor de la cookie csrf para reenviarla en la próxima solicitud en el segundo canal (ya sea como datos de formulario o encabezado)). No hay ninguna razón para exigir que el token csrf sea HttpOnly si la cookie de sesión ya es HttpOnly (para proteger contra XSS) ya que el token csrf no es valioso por sí solo sin una sesión asociada.
cowbert
2

Además de la cookie de sesión (que es una especie de estándar), no quiero usar cookies adicionales.

Encontré una solución que funciona para mí al crear una aplicación web de página única (SPA), con muchas solicitudes AJAX. Nota: Estoy usando Java del lado del servidor y JQuery del lado del cliente, pero no hay cosas mágicas, así que creo que este principio se puede implementar en todos los lenguajes de programación populares.

Mi solución sin cookies adicionales es simple:

Lado del cliente

Almacene el token CSRF que devuelve el servidor después de un inicio de sesión exitoso en una variable global (si quiere usar almacenamiento web en lugar de un global, eso está bien, por supuesto). Indique a JQuery que proporcione un encabezado X-CSRF-TOKEN en cada llamada AJAX.

La página principal de "índice" contiene este fragmento de JavaScript:

// Intialize global variable CSRF_TOKEN to empty sting. 
// This variable is set after a succesful login
window.CSRF_TOKEN = '';

// the supplied callback to .ajaxSend() is called before an Ajax request is sent
$( document ).ajaxSend( function( event, jqXHR ) {
    jqXHR.setRequestHeader('X-CSRF-TOKEN', window.CSRF_TOKEN);
}); 

Lado del servidor

En el inicio de sesión exitoso, cree un token CSRF aleatorio (y lo suficientemente largo), almacénelo en la sesión del lado del servidor y devuélvalo al cliente. Filtre ciertas solicitudes entrantes (confidenciales) comparando el valor del encabezado X-CSRF-TOKEN con el valor almacenado en la sesión: estos deben coincidir.

Las llamadas AJAX sensibles (datos de formulario POST y datos JET GET), y el filtro del lado del servidor que las atrapa, están bajo una ruta / dataservice / *. Las solicitudes de inicio de sesión no deben golpear el filtro, por lo que están en otra ruta. Las solicitudes de recursos HTML, CSS, JS e imágenes tampoco están en la ruta / dataservice / *, por lo tanto, no se filtran. Estos no contienen nada secreto y no pueden hacer daño, por lo que está bien.

@WebFilter(urlPatterns = {"/dataservice/*"})
...
String sessionCSRFToken = req.getSession().getAttribute("CSRFToken") != null ? (String) req.getSession().getAttribute("CSRFToken") : null;
if (sessionCSRFToken == null || req.getHeader("X-CSRF-TOKEN") == null || !req.getHeader("X-CSRF-TOKEN").equals(sessionCSRFToken)) {
    resp.sendError(401);
} else
    chain.doFilter(request, response);
}   
Stephan van Hoof
fuente
Creo que querría CSRF en una solicitud de inicio de sesión. Parece que está utilizando el token CSRF también como un token de sesión de inicio de sesión. También funciona tenerlos como tokens separados, y luego puede usar CSRF en cualquier punto final, ya sea que el usuario haya iniciado sesión o no.
Tongfa