El libro de Django 1.0

14.5. Protección contra CSRF

El paquete django.contrib.csrf provee protección contra Cross-site request forgery (CSRF) (falsificación de peticiones inter-sitio).

CSRF, también conocido como "session riding" (montado de sesiones) es un exploit de seguridad en sitios Web. Se presenta cuando un sitio Web malicioso induce a un usuario a cargar sin saberlo una URL desde un sitio al cual dicho usuario ya se ha autenticado, por lo tanto saca ventaja de su estado autenticado. Inicialmente esto puede ser un poco difícil de entender así que en esta sección recorreremos un par de ejemplos.

14.5.1.  Un ejemplo simple de CSRF

Supongamos que posees una cuenta de webmail en example.com. Este sitio proveedor de webmail tiene un botón Log Out que apunta a la URL example.com/logout — esto es, la única acción que necesitas realizar para desconectarte (log out) es visitar la página example.com/logout.

Un sitio malicioso puede coercerte a visitar la URL example.com/logout incluyendo esa URL como un <iframe> oculto en su propia página maliciosa. De manera que si estás conectado (logged in) a tu cuenta de webmail del sitio example.com y visitas la página maliciosa, el hecho de visitar la misma te desconectará de example.com.

Claramente, ser desconectado de un sitio de webmail contra tu voluntad no es un incidente de seguridad aterrorizante, pero este tipo de exploit puede sucederle a cualquier sitio que "confía" en sus usuarios, tales como un sitio de un banco o un sitio de comercio electrónico.

14.5.2. Un ejemplo más complejo de CSRF

En el ejemplo anterior, el sitio example.com tenía parte de la culpa debido a que permitía que se pudiera solicitar un cambio de estado (la desconexión del sitio) mediante el método HTTP GET. Es una práctica mucho mejor el requerir el uso de un POST HTTP para cada petición que cambie el estado en el servidor. Pero aun los sitios Web que requieren el uso de POST para acciones que signifiquen cambios de estado son vulnerables a CSRF.

Supongamos que example.com ha mejorado su funcionalidad de desconexión de manera que "Log Out" es ahora un botón de un <form> que es enviado vía un POST a la URL example.com/logout. Adicionalmente, el <form> de desconexión incluye un campo oculto:

<input type="hidden" name="confirm" value="true" />

Esto asegura que un simple POST a la URL example.com/logout no desconectará a un usuario; para que los usuarios puedan desconectarse, deberán enviar una petición a example.com/logout usando POST y enviar la variable POST confirm con el valor 'true'.

Bueno, aun con dichas medidas extra de seguridad, este esquema también puede ser atacado mediante CSRF — la página maliciosa sólo necesita hacer un poquito más de trabajo. Los atacantes pueden crear un formulario completo que envíe su petición a tu sitio, ocultar el mismo en un <iframe> invisible y luego usar JavaScript para enviar dicho formulario en forma automática.

14.5.3.  Previniendo la CSRF

Entonces, ¿Cómo puede tu sitio defenderse de este exploit?. El primer paso es asegurarse que todas las peticiones GET no posean efectos colaterales. De esa forma, si un sitio malicioso incluye una de tus páginas como un <iframe>, esto no tendrá un efecto negativo.

Esto nos deja con las peticiones POST. El segundo paso es dotar a cada <form> que se enviará vía POST un campo oculto cuyo valor sea secreto y sea generado en base al identificador de sesión del usuario. Entonces luego, cuando se esté realizando el procesamiento del formulario en el servidor, comprobar dicho campo secreto y generar un error si dicha comprobación no es exitosa.

Esto es precisamente lo que hace la capa de prevención de CSRF de Django, tal como se explica en la siguiente sección.

14.5.3.1. Usar el middleware CSRF

El paquete django.contrib.csrf contiene sólo un módulo: middleware.py. Este módulo contiene una clase middleware Django: CsrfMiddleware la cual implementa la protección contra CSRF.

Para activar esta proteccion, agrega 'django.contrib.csrf.middleware.CsrfMiddleware' a la variable de configuración MIDDLEWARE_CLASSES en tu archivo de configuración. Este middleware necesita procesar la respuesta después de SessionMiddleware, así que CsrfMiddleware debe aparecer antes que SessionMiddleware en la lista (esto es debido que el middleware de respuesta es procesado de atrás hacia adelante). Por otra parte, debe procesar la respuesta antes que la misma sea comprimida o alterada de alguna otra forma, de manera que CsrfMiddleware debe aparecer después de GZipMiddleware. Una vez que has agregado eso a tu MIDDLEWARE_CLASSES ya estás listo. Revisa la sección "Orden de MIDDLEWARE_CLASSES_" en el Capítulo 13_ si necesitas conocer más sobre el tema.

En el caso en el que estés interesado, así es como trabaja CsrfMiddleware. Realiza estas dos cosas:

  1. Modifica las respuestas salientes a peticiones agregando un campo de formulario oculto a todos los formularios POST, con el nombre csrfmiddlewaretoken y un valor que es un hash del identificador de sesión más una clave secreta. El middleware no modifica la respuesta si no existe un identificador de sesión, de manera que el costo en rendimiento es despreciable para peticiones que no usan sesiones.
  2. Para todas las peticiones POST que porten la cookie de sesión, comprueba que csrfmiddlewaretoken esté presente y tenga un valor correcto. Si no cumple estas condiciones, el usuario recibirá un error HTTP 403. El contenido de la página de error es el mensaje "Cross Site Request Forgery detected. Request aborted."

Esto asegura que solamente se puedan usar formularios que se hayan originado en tu sitio Web para enviar datos vía POST al mismo.

Este middleware deliberadamente trabaja solamente sobre peticiones HTTP POST (y sus correspondientes formularios POST). Como ya hemos explicado, las peticiones GET nunca deberían tener efectos colaterales; es tu responsabilidad asegurar eso.

Las peticiones POST que no estén acompañadas de una cookie de sesión no son protegidas simplemente porque no tiene sentido protegerlas, un sitio Web malicioso podría de todas formas generar ese tipo de peticiones.

Para evitar alterar peticiones no HTML, el middleware revisa la cabecera Content-Type de la respuesta antes de modificarla. Sólo modifica las páginas que son servidas como text/html o application/xml+xhtml.

14.5.3.2. Limitaciones del middleware CSRF

CsrfMiddleware necesita el framework de sesiones de Django para poder funcionar. (Revisa el Capítulo 12 para obtener más información sobre sesiones). Si estás usando un framework de sesiones o autenticación personalizado que maneja en forma manual las cookies de sesión, este middleware no te será de ayuda.

Si tu aplicación crea páginas HTML y formularios con algún método inusual (por ej. si envía fragmentos de HTML en sentencias JavaScript document.write), podrías estár salteandote el filtro que agrega el campo oculto al formulario. De presentarse esta situación, el envío del formulario fallará siempre. (Esto sucede porque CsrfMiddleware usa una expresión regular para agregar el campo csrfmiddlewaretoken a tu HTML antes de que la página sea enviada al cliente, y la expresión regular a veces no puede manejar código HTML muy extravagante). Si sospechas que esto podría estar sucediendo, sólo examina el código en tu navegador Web para ver si es que csrfmiddlewaretoken ha sido insertado en tu <form>.

Para más información y ejemplos sobre CSRF, visita http://en.wikipedia.org/wiki/CSRF.