Cómo prevenir CSRF en una aplicación RESTful?


La falsificación de solicitudes de sitios cruzados (CSRF) generalmente se previene con uno de los siguientes métodos:

  • Check referer-RESTful pero no fiable
  • inserte el token en el formulario y almacene el token en la sesión del servidor - no es realmente RESTful
  • URI crípticos de una sola vez-no RESTful por la misma razón que tokens
  • enviar contraseña manualmente para esta solicitud (no la contraseña almacenada en caché utilizada con HTTP auth) - RESTful pero no conveniente

Mi idea es usar un usuario secreto, un id de formulario críptico pero estático y JavaScript para generar tokens.

<form method="POST" action="/someresource" id="7099879082361234103">
    <input type="hidden" name="token" value="generateToken(...)">
    ...
</form>
  1. GET /usersecret/john_doe obtenido por JavaScript del usuario autenticado.
  2. Respuesta: OK 89070135420357234586534346 Este secreto es conceptualmente estático, pero se puede cambiar cada día/hora ... para mejorar la seguridad. Esto es lo único confidencial.
  3. Leer el críptico (pero estático para todos los usuarios!) id de formulario con JavaScript, procesarlo junto con el secreto de usuario: generateToken(7099879082361234103, 89070135420357234586534346)
  4. Enviar el formulario junto con el generado token al servidor.
  5. Dado que el servidor conoce el secreto del usuario y el id del formulario, es posible ejecutar la misma función generateToken que el cliente antes de enviar y comparar ambos resultados. Solo cuando ambos valores son iguales la acción será autorizada.

¿Hay algo malo con este enfoque, a pesar del hecho de que no funciona sin JavaScript?

Adición:

Author: deamon, 2010-03-06

6 answers

Hay muchas respuestas aquí, y problemas con bastantes de ellas.

Cosas que NO debes hacer:

  1. Si necesita leer el token de sesión de JavaScript, está haciendo algo horriblemente mal. Su cookie de identificador de sesión SIEMPRE debe tener HttpOnly configurado para que no esté disponible para los scripts.

    Esta protección hace que el impacto de XSS se reduzca considerablemente, ya que un atacante ya no podrá obtener usuarios registrados token de sesión, que a todos los efectos son el equivalente de las credenciales en la aplicación. No quieres que un error te dé las llaves del reino.

  2. El identificador de sesión no debe escribirse en el contenido de la página. Esto es por las mismas razones por las que configuró HttpOnly. Esto significa que su token csrf no puede ser su id de sesión. Tienen que ser valores diferentes.

Cosas que debes hacer:

  1. Seguir OWASP's orientación:

  2. Específicamente, si se trata de una aplicación REST, puede requerir el doble envío de tokens CSRF:

Simplemente cree algo criptográficamente aleatorio, guárdelo en ASCII Hex o Base64 encode, y agréguelo como una cookie y a sus formularios cuando el servidor devuelva la página. En el lado del servidor, asegúrese de que el valor de la cookie coincida con el valor del formulario. Voila, has matado a CSRF, has evitado peticiones adicionales para tus usuarios, y no te has abierto a más vulnerabilidades.

 22
Author: Doug,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-19 15:44:51

Definitivamente necesita algún estado en el servidor para autenticarse/autorizar. Sin embargo, no es necesario que sea la sesión http, puede almacenarla en una caché distribuida (como memcached) o en una base de datos.

Si utiliza cookies para la autenticación, la solución más fácil es duplicar el valor de la cookie. Antes de enviar el formulario, lea el id de sesión de la cookie, guárdelo en un campo oculto y luego envíelo. En el lado del servidor, confirme que el valor en la solicitud es el mismo que el id de sesión (que obtuvo de la cookie). Evil script de otro dominio no será capaz de leer el id de sesión de la cookie, evitando así CSRF.

Este esquema utiliza un único identificador en toda la sesión.

Si desea más protección, genere un id único por sesión por formulario.

Además, NO genere tokens en JS. Cualquiera puede copiar el código y ejecutarlo desde un dominio diferente para atacar su sitio.

 9
Author: Sripathi Krishnan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-23 14:56:44

Estoy entendiendo bien:

  • Desea protección contra CSRF para los usuarios que han iniciado sesión a través de cookies.
  • Y al mismo tiempo desea una interfaz RESTful para las solicitudes autenticadas Basic, OAuth y Digest de las aplicaciones.

Entonces, ¿por qué no verificar si los usuarios están conectados a través de una cookiey aplicar CSRF solo entonces?

No estoy seguro, pero ¿es posible que otro sitio falsifique cosas como autenticación básica o encabezados?

Por lo que sé, CSRF es todo sobre las cookies? RESTful auth no sucede con las cookies.

 8
Author: antitoxic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-27 19:18:56

El ID de formulario estático no proporciona protección en absoluto; un atacante puede obtenerlo él mismo. Recuerde, el atacante no está limitado a usar JavaScript en el cliente; puede obtener el ID de formulario estático del lado del servidor.

No estoy seguro de entender completamente la defensa propuesta; ¿de dónde viene el GET /usersecret/john_doe? ¿Es parte de la página JavaScript? ¿Es esa la URL literal propuesta? Si es así, asumo que username no es un secreto, lo que significa que evil.ru puede recuperar secretos de usuario si un navegador o plugin bug permite peticiones GET entre dominios. ¿Por qué no almacenar el secreto del usuario en una cookie al momento de la autenticación en lugar de permitir que cualquiera que pueda hacer cross-domain lo recupere?

Me gustaría leer "Defensas Robustas para la Falsificación de Sitios Cruzados" muy cuidadosamente antes de implementar mi propio sistema de autenticación que quería ser resistente a CSRF. De hecho, reconsideraría la implementación de mi propio sistema de autenticación.

 6
Author: Scott Wolchok,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-21 10:28:43

Hay algunos métodos en la Hoja de trucos de Prevención de CSRF que puede usar el servicio restful. La mitigación CSRF sin estado más restful es usar Origin o HTTP referer para asegurarse de que las solicitudes se originan en un dominio de confianza.

 3
Author: rook,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-12 04:14:37

Es algo malo con este enfoque, a pesar de que no funciona sin JavaScript?

Su secreto de usuario no es un secreto si lo envía al cliente. Usualmente usamos estos secretos para generar hashes y enviarlos con el formulario, y esperarlos para la comparación.

Si desea ser RESTful, la solicitud debe contener toda la información sobre cómo procesarla. Las formas en que puedes hacer esto:

  • Agregue una cookie de token csrf con su REST cliente y enviar el mismo token en entrada oculta con sus formularios. Si el servicio y el cliente están bajo dominios diferentes, debe compartir las credenciales. En el servicio tienes que comparar los 2 tokens, y si son los mismos, la solicitud es válida...

  • Puede agregar la cookie token csrf con su servicio REST y enviar el mismo token con las representaciones de sus recursos (entradas ocultas, etc...). Todo lo demás es lo mismo que el final de la solución anterior. Esta solución está al borde del descanso. (Está bien hasta que el cliente no llame al servicio para modificar la cookie. Si la cookie es solo http, el cliente no debe saberlo, si no lo es, entonces el cliente debe configurarla.) Puede hacer una solución más compleja si agrega diferentes tokens a cada formulario y agrega tiempo de caducidad a las cookies. También puede enviar el tiempo de vencimiento con los formularios, para que sepa la razón cuando falla la validación de un token.

  • Puedes tenga un secreto de usuario (diferente para cada usuario) en el estado del recurso en su servicio. Al crear representaciones, puede generar un token (y tiempo de caducidad) para cada formulario. Puede generar un hash a partir del token real (y el tiempo de caducidad, el método, la url, etc.)...) y el secreto del usuario, y enviar ese hash con el formulario también. Por supuesto, usted mantiene el "secreto del usuario" en secreto, por lo que nunca lo envía con el formulario. Después de eso, si su servicio recibe una solicitud, puede generar el hash a partir de la solicitud parámetros y secreto de usuario de nuevo, y compararlos. Si no coincide, la solicitud no es válida...

Ninguno de ellos le protegerá si su cliente REST es javascript inyectable, por lo que debe verificar todo su contenido de usuario contra entidades HTML, y eliminarlas todas, o usar TextNodes siempre en lugar de innerHTML. También debe protegerse contra la inyección SQL y la inyección de encabezado HTTP. Nunca utilice FTP simple para actualizar su sitio. Y así sucesivamente... Hay muchas maneras de inyecta código maligno en tu sitio...

Casi me olvido de mencionar, que las solicitudes GET son siempre para lectura por el servicio y por el cliente ya sea. Por el servicio esto es obvio, por la configuración del cliente cualquier url en el navegador debe resultar una representación de un recurso o varios recursos, nunca debe llamar a un método POST/PUT/DELETE en un recurso. Por ejemplo, GET http://my.client.com/resource/delete -> DELETE http://my.api.com/resource es una solución muy-muy mala. Pero esta es una habilidad muy básica si quieres dificultar el CSRF.

 0
Author: inf3rno,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-05 12:08:18