Vulnerabilidades CSRF en aplicaciones web
Hoy voy a hablar sobre un tipo de vulnerabilidades de aplicaciones web llamado Cross Site Request Forgery (CSRF). Aunque su nombre guarde cierta similitud con otro tipo de vulnerabilidades como Cross Site Scripting (XSS), hay importantes diferencias entre ellas. A diferencia de los ataques XSS, que se basan en explotar la confianza que tiene un usuario en un determinado sitio web o aplicación, los ataques CSRF, también denominados como Cross Site Reference Forgery o XSRF, se basan en explotar la confianza que los sitios web tienen con sus usuarios.
Veámoslo con un ejemplo. Imaginemos que estamos conectados a un web que requiere autenticación, y que por ejemplo estamos manteniendo una conversación por chat con otra persona. Esta persona podría enviarnos un enlace a una página que contuviera una imagen oculta que apuntase a una url del web en el que estamos autenticado. Cuando entrásemos en esa página, la imagen oculta haría que solicitásemos al web una determinada acción sin ser conscientes de ello, por ejemplo cambiando nuestros datos de registro, publicando información en un foro a nuestro nombre, o cualquier otra cosa que se nos ocurra.
El problema por tanto es que el web en el que estamos autenticado, es incapaz de saber si la petición que le llega la hemos realizado nosotros de forma voluntaria o si ha sido trampeada mediante algún ataque CSRF. Normalmente muchos webs son vulnerables a este tipo de ataques, aunque los mecanismos de protección ante ellos son relativamente sencillos, siempre y cuando los enmarquemos también dentro de otro tipo de protección, como ante ataques XSS.
Mecanismos de ataque
A continuación presento algunos de los mecanismos más frecuentes mediante los que se realiza este tipo de ataques, como los métodos basados en la utilización de imágenes, iframes, scripts de javascript, o XMLHttpRequest.
Los más sencillos se basan en la utilización de los atributos src de las etiquetas img, script e iframe. Este tipo de ataques son suficientes para aquellos webs que aceptan los parámetros de la acción mediante GET. Por ejemplo, podría ser suficiente con indicar una imagen de tamaño nulo con el atributo src http://www.example.com/pagina?parametros
.
Otra forma de ataque es la utilización de código JavaScript que nos permite realizar ataques a páginas con parámetros GET como las anteriores, tal como se muestra en el siguiente ejemplo, pero que también permite de forma muy sencilla atacar a páginas que utilicen parámetros vía POST.
<script> var foo = new Image(); foo.src = "http://www.example.com/pagina?parametros"; </script> |
Por último, es posible utilizar también los objetos XMLHttpRequest para realizar peticiones a la página atacada, pudiendo hacerlo utilizando parámetros GET, POST, o incluso procesos más complejos que requieran de varios pasos. A continuación se muestra un ataque a una página utilizando parámetros enviados por POST.
<script> var post_data = 'name=value'; var xmlhttp=new XMLHttpRequest(); xmlhttp.open("POST", 'http://www.example.com/pagina', true); xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState == 4) { // en función del tipo de página web sería suficiente con llegar aquí // o se podría programar algo específico para páginas con varios pasos por ejemplo. } }; xmlhttp.send(post_data); </script> |
Mecanismos de defensa
Ya hemos visto en qué consiste este tipo de ataques y los mecanismos que se pueden utilizar para implementarlos, que son realmente sencillos, por lo que es hora de ver qué mecanismos de defensa existen y qué nos ofrece cada uno de ellos, con sus ventajas y desventajas.
Uso de un token de sesión personal
Este es uno de los mecanismos más utilizados frecuentemente, el cual aporta un buen nivel de seguridad si se hace correctamente. Se basa en la generación y codificación de un número aleatorio (token) tras el login del usuario en la aplicación, que se almacena en la sesión del usuario. En cada formulario que se le presente al usuario se incluye un campo oculto en el que se escribe este token. A la recepción del formulario en el servidor se comprueba que el token se haya recibido y coincida con el almacenado para el usuario. Si el token no coincide se aborta la acción del formulario.
Con el fin de evitar que el token pueda ser fácilmente visible en la barra de direcciones del navegador o que llegue a otras páginas vía la cabecera HTTP_REFERER, es aconsejable enviar siempre el token mediante POST. En aplicaciones en las que se utilice una conexión automática si el usuario ya se ha autenticado alguna vez anterior, también es conveniente hacer que el valor de token expire, bien de forma independiente o con la sesión, con el objetivo de que si alguien obtiene el token éste caduque pasado un tiempo.
Uso de un token de sesión personal por acción
De forma similar a la opción anterior se utilizan tokens generados de forma aleatoria y codificados, pero sin embargo, no se utiliza únicamente un token diferente por cada usuario, sino que cada usuario utiliza un token independiente por cada posible acción que la aplicación le ofrece. Estos tokens se generan previamente al uso de las acciones y se guardan en un mapa (un Hashtable por ejemplo) en la sesión del usuario, asociando cada acción con su token correspondiente. De esta forma cada vez que el usuario solicite una acción deberá incluir un parámetro, preferiblemente vía POST, que coincida con el valor almacenado para esa acción en la sesión.
En el caso de que un atacante obtuviera acceso a uno de estos tokens su margen de actuación sería menor, quedando limitado únicamente a la acción sobre la que esté definido el token y no pudiendo acceder al resto.
Uso de un token encriptado personal por acción
En esta alternativa se amplia la opción anterior haciendo que los token generados para cada acción estén encriptados a partir de un código o nombre de acción, el identificador de sesión del usuario y una palabra secreta común. De la misma forma que en los casos anteriores este token se envía vía POST en cada acción y a su recepción se comprueba que el valor recibido coincida con el resultante de realizar la misma operación de encriptación. De esta forma no es necesario disponer de un mapa en sesión con las acciones y el token asociado a cada una, ya que estos tokens no son aleatorios sino que se generan a partir de una función aplicada sobre el código de la acción, el identificador de sesión y la palabra secreta.
Esta dependencia funcional de estos 3 valores hace que los tokens sean independientes para cada usuario, para cada acción, y que estén asociados a la sesión del usuario, de forma que cuando ésta expire también lo haga el token. Por otra parte permite innutilizar todos los tokens en uso en un determinado momento simplemente cambiando la palabra secreta.
Falsa sensación de seguridad
Las alternativas anteriores permiten alcanzar buenas cotas de seguridad sin ser demasiado compleja su implementación, sin embargo hay otras alternativas que habitualmente se aceptan como buenas y que dan una falsa sensación de seguridad al no protegernos tanto como en un primer principio mucha gente pudiera pensar.
- Formularios POST: los ataques CSRF no afectan únicamente a las páginas que aceptan parámetros vía GET. Bien es cierto que estas páginas son más sencillas de atacar, pero también es cierto que es relativamente sencillo atacar a cualquier página basada en POST mediante JavaScript o XMLHttpRequest.
- Formularios multipágina: al igual que es posible y fácil atacar a páginas con parámetros POST, también es sencillo realizar ataques sobre formularios multipágina utilizando por ejemplo XMLHttpRequest.
- Comprobación del Referer: una técnica habitual para evitar ataques CSRF es comprobar que la cabecera HTTP_REFERER coincide con la que se esperaría para esa acción. Esto que a primera impresión puede parecer buena idea no lo es tanto ya que esta cabecera se puede manipular de forma trivial, haciendo que contenga lo que se desee y permitiendo saltar fácilmente este control.
Aparte de estos mecanismos de falsa seguridad, hay una serie de ideas muchas veces aceptadas que hacen que este tipo de ataques proliferen.
- Responsabilizar al usuario: al contrario que en otras situaciones, el usuario no suele tener la culpa de padecer este tipo de ataques ni puede hacer nada para impedirlos, sino que es tarea de los desarrolladores poner los medios para ello.
- Escaso impacto de los ataques: el impacto que puede generar un ataque CSRF es directamente proporcional a las posibles acciones que pueda generar una aplicación, modificar datos personales, publicar información en nombre de otro, comprar productos, modificar contraseñas, etc.
- Ni el firewall del servidor, ni la encriptación SSL, ni el framework de programación que se utilice defiende a la aplicación frente a estos ataques. Es tarea del desarrollador hacer que la aplicación sea lo menos vulnerable posible.
Más información
IMHO, el uso de XMLHttpRequest no sirve mucho para realizar ataques CSRF, debido a las restricciones que aplican los navegadores a este tipo de objetos.
Muy bueno el articulo. Lo he probado y descubri q falta una linea en el ejemplo de XMLHttpRequest, antes de hacer xmlhttp.send(post_data);
debe ir la siguiente la linea:
xmlhttp.setRequestHeader(‘Content-Type’,’application/x-www-form-urlencoded’);
ahora si, funciona correctamente.
Pero si se trabaja con el objeto xmlhttprequest no es posible salir del contexto de la página en cuestión, osea que no se puede enviar información fuera del sitio con el que se ejecuta el código JavaScript.
yo_yo, en efecto con XMLHttpRequest no es posible salir del dominio de la página, pero imagina que el código se ha introducido mediante XSS en ella, en ese caso te lo descargarías y ejecutarías dentro del mismo dominio.
Pues me has dejado con la duda. Creía que $_POST era una forma segura.
Me puedes escribir algún código javascript u otro para comprobar en mi web(local) si es vulnerable ?
Gracias!.