jueves, 11 de agosto de 2016

Un XSS olímpico

Llegaron los Juegos Olímpicos. Millones de personas en todo el mundo siguen con expectación el progreso de los participantes en la competición deportiva más global y multitudinaria del planeta. Y, cómo no, un evento de tales características cuenta con una web oficial dedicada a ofrecer toda la información detallada. Todo esto, y el tiempo libre extra que siempre ofrecen las vacaciones, me animaron a darme una vuelta por www.olympic.org en busca de algún fallo de seguridad. Como resultado encontré una interesante vulnerabilidad de tipo XSS que intenté reportar a la organización. Desgraciadamente, no localicé ningún medio de contacto para hacerlo a excepción de la cuenta oficial de Twitter @Olympics.

¿Dónde reporto esto?
Como era de esperar, no obtuve ninguna respuesta.

El punto de inyección vulnerable a XSS se encuentra en el buscador de la sección de FAQ situado en http://registration.olympic.org/en/faq/search. En este dominio (registration.olympic.org) los usuarios pueden registrarse y autenticarse para configurar la recepción de noticias sobre novedades y eventos.


Punto de inyección
El valor del parámetro "search" (que puede ser enviado por método POST o GET) es reflejado sin escapar en el atributo "placeholder", por lo que después de insertar una comilla " podríamos inyectar código HTML malicioso.  Sin embargo, la web cuenta con un WAF (Web Application Firewall) que nos hace la vida un poco más difícil. Vectores del tipo " onfocus=alert(1) autofocus ", "><script>alert(1)</script>, <img src=1 onerror=alert(1)>, etc. son fácilmente detectados y anulados por el WAF. De hecho, el sistema parece que cuenta con dos niveles de WAF. El primero y más determinístico, muestra un mensaje de error cuando detecta cualquier tag o atributo HTML sospechoso de poder ejecutar código javascript como <script>, <iframe>, href=, onfocus=, onerror=, src=, etc.

Mensaje "Access Denied" del WAF

El segundo nivel de WAF parece seguir un patrón más heurístico, ya que el simple hecho de usar un número determinado de caracteres concretos, o usar caracteres numéricos o albéticos en cierto punto de la inyección, puede despertarlo y cortarnos el paso con otro mensaje de error distinto del anterior:

Mensaje "Security error" del WAF
Llegados a este punto, toca practicar uno de nuestros deportes olímpicos favoritos: El salto de WAF. El objetivo será conseguir la típica ejecución de una alerta javascript como prueba de concepto. Como siempre en estos casos, seguro que existirán múltiples soluciones igualmente válidas a la descrita en este post.

Lo primero, las comprobaciones de rigor: no es sensible a mayúsculas, caracteres de separación, inserción de nulos, fallos de parseo y demás bypasses básicos. Como norma general, para estos test iniciales suelo seguir el patrón descrito por Rafay Baloch en el paper que puedes encontrar pulsando en  este link.

Como hemos comentado anteriormente, todos los atributos HTML de eventos interesantes como on*, href, etc. son detectados por el WAF. Sin embargo, el atributo formaction como parte de una etiqueta <input type=submit> es considerado válido. Este atributo permite especificar la URL a la que se enviarán los datos del formulario, sobrescribiendo la ya proporcionada en el atributo action del form principal. Con suerte, podremos usar este atributo para especificar una URL de tipo javascript: o data: que nos permita ejecutar código.

Ya tenemos nuestro punto de partida:

"><input type=submit formaction="

Tras intentar proporcionar una URL de tipo javascript: o data: en el atributo formaction, el WAF detecta nuestras intenciones y muestra el mensaje de error correspondiente. Intentamos ofuscar la cadena javascript: usando codificaciones HTML tales como j&#x61;vascript:javascr&#105;pt: pero el WAF sigue parándonos los pies. Tras jugar un poco a prueba y error, me doy cuenta de que el WAF detecta codificaciones que incluyen específicamente el carácter almohadilla "#". El carácter dos puntos ":" tiene su propia entidad HTML especial &colon; y no necesita hacer uso de su representación numérica con almohadilla. Por tanto, salvamos este obstáculo codificando el principio de nuestra URL como javascript&colon;.

"><input type=submit formaction="javascript&colon;

Ok, estamos en la mitad de la carrera. ¡Recordad! ¡Estamos en los Juegos Olímpicos! Todavía nos quedan obstáculos para llegar a la meta. Las funciones como alert, prompt o confirm también son detectadas y denegadas por el WAF heurístico. Necesitamos una manera de ejecutar la función alert sin llamarla explícitamente. Las llamadas a window.alert y top.alert son igualmente denegadas, incluso cualquier llamada del tipo window.*, top.* o document.*. Como vimos en el post de este blog correspondiente al fallo reportado a Facebook, la notación de corchetes de javascript puede ser muy útil para ofuscar llamadas a métodos. Esto es así porque nos permite realizarlas a través de cadenas javascript. Por ejemplo, window.alert puede ser reescrito igualmente como window['alert']. A pesar de que el WAF detectaba también las cadenas window[*, top[* o document[*, existía una alternativa que no era detectada por el WAF: this[*. Esto nos permite llamar a la función alert de la forma this['alert']. Sin embargo, la cadena alert sigue siendo detectada como maliciosa en este contexto. ¡No hay problema! Ahora es una cadena javascript y podemos ofuscarla, por ejemplo, separándola en varias cadenas: this['a'+'lert'].

¿Algo más? Sí, el WAF tampoco dejaba pasar los corchetes ni el símbolo "+" como parte del vector XSS. Por suerte, las codificaciones a entidades HTML de los corchetes (&lsqb; y &rsqb;) y del símbolo "+" (&plus;) acudieron al rescate. Con todo esto, llegados a este punto nuestro vector tiene esta pinta:

"><input type=submit formaction="javascript&colon;this&lsqb;'a'&plus;'lert'&rsqb;

Si al WAF no le gustaban los corchetes, no es ninguna sorpresa que tampoco le gustasen los paréntesis necesarios para ejecutar nuestro alert(1). Al igual que con los corchetes, podríamos aplicar la solución de usar las entidades HTML correspondientes de &lpar; y &rpar;. Sin embargo, existe otra posibilidad más sencilla que consiste en usar el carácter de acento grave ` en lugar de los paréntesis. Finalmente, nuestro vector quedaría de la siguiente manera:

"><input type="submit" formaction="javascript&colon;this&lsqb;'a'&plus;'lert'&rsqb;`1`"

Ya solo nos queda codificar correctamente todos los caracteres especiales para incluir el vector de ataque en el valor del parámetro search de la URL:

http://registration.olympic.org/en/faq/search?search=Type%20here%22%3E%3Cinput%20type=submit%20formaction=%22javascript%26colon%3Bthis%26lsqb%3B%27a%27%26plus%3B%27lert%27%26rsqb%3B%601%60%22


Y, por fin, después de visitar la URL anterior en un navegador sin filtro de XSS reflejado e introducir cualquier texto en el buscador, obtenemos nuestra medalla olímpica en forma de alerta javascript:

Ejecución exitosa del XSS en Firefox
¡Fin de la carrera! ¡Un saludo!

miércoles, 9 de diciembre de 2015

Robando credenciales de Sony mediante redirección abierta

Las vulnerabilidades de redirección abierta (Open redirection) suelen ser consideradas de baja importancia. Sin embargo, uno de los factores que elevan la criticidad de este tipo de fallos es su presencia en procesos de registro o de autenticación, ya que el usuario parte de una predisposición para facilitar sus credenciales de acceso. En este post veremos la protección que implementaba Sony contra este tipo de ataques y cómo fue posible evadirla con un vector que puede ser muy útil en diversas situaciones, no necesariamente relacionadas con la explotación de una redirección abierta.

Este fallo fue reportado a Sony en mayo de 2015 y solucionado cuatro meses más tarde. Finalmente, fui añadido en agradecimiento a su "Hall of Thanks".

Si intentamos registrarnos en cualquiera de las webs oficiales de Sony (www.sony.es, www.sony.de, www.sony.fr, etc.) veremos que, tras pulsar el botón correspondiente para crear una cuenta, somos redirigidos a una URL de este tipo:

https://www.sony.es/mysony/register?email=email%40dominio.com&check=true&returnUri=%2Fmysony

Examinando el código HTML de la página se puede apreciar cómo el valor del parámetro "returnUri" se refleja en un campo oculto del formulario:

Figura 1. Campo oculto "returnUri" en formulario de registro
Tras completar toda la información requerida y enviarla, recibimos en nuestro email el típico correo electrónico para confirmar el registro con un enlace parametrizado que incluye un token aleatorio y la ruta facilitada en el parámetro "returnUri". Finalmente, tras pulsar en este enlace somos redirigidos a la página que nos confirma la activación de la cuenta:

Figura 2. Página de confirmación de activación de la cuenta y código HTML

En la imagen anterior podemos apreciar dos cosas interesantes. La primera es que Sony invita a iniciar sesión con la cuenta recién creada, por lo que el usuario estará predispuesto a introducir sus credenciales de acceso en el siguiente paso. La segunda es que el botón "Continuar" contiene un enlace a la dirección que se introdujo inicialmente en el parámetro "returnUri". Por tanto, si este parámetro aceptara cualquier URL sería un escenario ideal para un ataque de redirección abierta con el cual enviar un enlace de registro manipulado y conseguir que una víctima introdujera sus credenciales en un dominio controlado por el atacante.

El primer intento básico fue introducir en el parámetro "returnUri" distintas URL del tipo http://dominio.atacante.com, https://..., HtTp://..., HttPs://..., data:, javascript:, etc. Sin embargo, cualquier valor que no empezara por un caracter "/" (%2F codificado en URL) hacía que el parámetro oculto del formulario HTML de la "Figura 1" fuera eliminado automáticamente, asignándose posteriormente el valor por defecto %2F en el enlace recibido en el email de confirmación.

El segundo intento, como algunos estaréis pensando, fue probar con una doble barra una URL del tipo //dominio.atacante.com, ya que esto sería equivalente a https://dominio.atacante.com. El resultado era el mismo que en el caso anterior.

Llegados a este punto, ¿existe alguna manera de reflejar una URL absoluta en el código HTML comenzando con un y solo un caracter "/" (%2F)? Sorprendentemente, se puede conseguir esto utilizando cadenas del tipo:

 %2F%09%2Fdominio.atacante.com

Los caracteres %09, %10 y %13 (tabulador, nueva línea y retorno de carro respectivamente) pueden ser introducidos en medio de los dos caracteres "/" y el resultado es equivalente a //dominio.atacante.com y, por tanto en este caso, a https://dominio.atacante.com. Utilizando este método el ataque no era detectado por Sony y el parámetro "returnUri" no era eliminado del formulario, con lo que nuestra redirección abierta funcionó perfectamente:


Este vector suele resultar muy efectivo en cualquier contexto web en el que deseamos romper la "jaula" que nos impone un path relativo. Si por otro lado estás en el bando defensivo como programador, conviene recordar que para evitar el uso de URL externas no será suficiente comprobar que la cadena introducida por el usuario comience por una sola barra, ya que habrá que tener en cuenta la posible presencia de los caracteres de separación mencionados anteriormente.

¡Un saludo! 

viernes, 1 de mayo de 2015

Hackeando eBay (Validación javascript + XSS + CSRF)

Como todos los sistemas que basan su actividad principal en el movimiento de dinero entre sus clientes, eBay es un lugar muy atractivo para un ciberdelincuente. Un XSS persistente como el que se va a detallar en este post es todo lo que se necesitaría para poder comprometer las acciones y la información de otros usuarios, y sacar provecho material con ello. A pesar de esto, eBay es ya famoso por tomarse con calma los reportes de seguridad y a su equipo le llevó unos ocho meses arreglar el problema desde su comunicación en agosto de 2013. En abril de 2014, después de solucionarse, fui finalmente añadido a su página de agradecimientos "Responsible Disclosure Acknowledgements".

El fallo de seguridad se debe a la concatenación de tres errores en serie. En mi opinión, es un buen ejemplo de cómo algunos problemas pueden no ser demasiado peligrosos ni complicados de explotar de forma aislada, pero juntos pueden dar un buen dolor de cabeza.

Validación javascript

En busca de algún fallo XSS, lo primero que intenté fue introducir algunos caracteres como ", <, >, ', etc. en la creación de nuevas carpetas del sistema de mensajes privados de eBay. Al hacer esto, se mostraba el error "Escribe un nombre de carpeta válido". Sin embargo, llamaba la atención que al enviar uno de estos caracteres problemáticos no se reflejaba ninguna petición web en el historial del navegador, lo que indicaba que la validación se estaba produciendo en el lado del cliente por medio de javascript. Aunque tengamos constancia de que se está produciendo una validación javascript que podríamos saltarnos fácilmente, esto no quiere decir que no haya una segunda validación en el servidor que nos pare los pies.

(¿Self?) XSS

Armado con un proxy, capturé la petición que enviaba mi navegador tras especificar un nombre de carpeta válido, y modifiqué el valor del parámetro por algo “inválido” más interesante como <script>alert(/XSS/)</script>. Ante mi sorpresa, al refrescar la bandeja de entrada apareció la alerta javascript, lo que confirmaba que no se producía ninguna validación del parámetro en el servidor:

XSS en nombres de carpetas
Ok, tenemos un fallo XSS, pero de momento es un triste Self-XSS que solo podemos explotar en nuestra propia cuenta. Para que esto fuera realmente peligroso, necesitaríamos poder crear carpetas de mensajes en las cuentas de otros usuarios sin su permiso y colarles nuestro código javascript en los nombres de carpeta. Uff, ¿estaba pidiendo mucho? Merecía la pena comprobarlo...

Self-XSS + CSRF = Owned!!

Una posibilidad para poder crear carpetas en cuentas ajenas, es que existiera un fallo de tipo CSRF. Si analizábamos la petición, nos fijamos en que existían dos candidatos a aguarnos la fiesta como tokens anti-CSRF: stok y pId:


Petición de creación de carpeta
Sin embargo, para mi segunda sorpresa, cualquier valor de estas variables era válido en una petición exitosa.

Debido a los tres fallos (validación de parámetros en cliente, self-XSS y CSRF), finalmente podemos construir una página maliciosa que fuerce la creación de una carpeta en la cuenta de otro usuario, y ejecutar código javascript arbitrario en su navegador:


eBay solucionó el problema de XSS mostrando las entidades html correspondientes para los caracteres especiales, e incluyendo un nuevo parámetro "srt" como token anti-CSRF dentro de la cadena JSON codificada del parámetro "request".

¡Un saludo!

jueves, 8 de enero de 2015

Salvando obstáculos en ataques XXE: explotación en atributos, ficheros mal formados y técnicas OOB

Aunque los ataques XXE (XML External Entity) son bastante conocidos, hay ciertas limitaciones que se nos suelen presentar a la hora de explotarlos, y cuyas técnicas para salir airoso, a día de hoy no están tan extendidas. Por esta razón, me ha parecido interesante escribir un post sobre algunas de estas limitaciones y cómo superarlas.

Un fallo de seguridad XXE es uno de esos fallos que te hacen saltar de la silla cuando te encuentras con él. No es para menos, ya que permite maldades tales como acceso al sistema de ficheros, escaneo de la Intranet, denegaciones de servicio o, incluso, ejecución remota de código. Para entender básicamente cómo funciona, antes de seguir leyendo puedes hacer click en este post de nuestros amigos de Security by default.

Para que después de saltar de la silla no te acabes dando con la lámpara en la cabeza, aquí te presentamos un resumen de los problemas más comunes y cómo abordarlos. En los ejemplos, el objetivo del atacante será leer el fichero /etc/passwd del servidor.

[ Explotación en atributos ]


Si intentamos explotar un XXE de la forma habitual dentro de un atributo XML, veremos que el ataque no tendrá éxito, ya que las entidades no son procesadas directamente en este contexto.

- Lo que NO funciona:


- Lo que funciona:


Donde el contenido del fichero evil.xml es el siguiente:


Por suerte, los parámetros XML acuden al rescate. Estos parámetros cumplen la misma función que las entidades, pero se usan en la definición del documento (DTD). En el ejemplo anterior, definimos la entidad "xxe" de manera indirecta haciendo uso de un parámetro que se resuelve hacia una URL externa controlada por nosotros en nuestro "evilserver", y que a su vez hace uso de parámetros que resuelven el contenido del fichero que queremos leer del servidor.

[ Ficheros mal formados ]


Otro problema habitual es que una vez que se expande una entidad XML con el contenido de un fichero que contiene caracteres como <, >, ", etc., el XML original puede acabar mal formado, por lo que el parser no podrá completar su proceso y nuestro ataque no funcionará. Una solución es intentar que el contenido del fichero quede dentro de un bloque <![CDATA[...]]>. Sin embargo, no podemos incluir este bloque directamente, ni siquiera con entidades definidas en el XML.

- Lo que NO funciona:



- Lo que funciona:


Donde el contenido del fichero join.dtd es el siguiente:


Como vemos, finalmente logramos construir nuestro bloque CDATA haciendo uso, de nuevo, de parámetros XML, y apoyándonos en una web externa controlada por nosotros que compone las partes definidas en el XML original. Aunque esta técnica funcionará en la mayoría de los casos, puede que falle si el fichero que queremos leer del servidor contiene los caracteres & o % o si intentamos leer ficheros binarios.

[ Técnicas OOB (Out of Band) ]


En ocasiones, aunque tenemos indicios de que la aplicación es vulnerable a ataques XXE, ningún dato del XML que enviamos es finalmente devuelto al usuario tras el parseo. Al igual que ocurre con la explotación de otro tipo de vulnerabilidades como SQL inyection, también existen técnicas OOB para hacer que el servidor víctima envíe el contenido hacia un servidor controlado por el atacante:


Donde el contenido del fichero send.dtd es el siguiente:


Como en esta ocasión construimos una URL externa dinámicamente con el contenido del fichero extraído, es posible que tengamos problemas si el parser es delicado y no acepta todos los caracteres del contenido que se van a incluir automáticamente en la URL. Pero incluso en esta situación nos quedan algunas cartas bajo la manga por jugar. Haciendo uso del protocolo FTP y de un servidor falso, probablemente podamos acabar leyendo el fichero siguiendo los estupendos ejemplos que detalla Ivan Novikov en este enlace.

Aparte de las técnicas anteriores, existe un abanico de posibilidades de explotación bastante interesante dependiendo de los protocolos disponibles en el servidor. gopher://, jar://,  php:// o expect:// por ejemplo, nos abrirían las puertas para poder leer ficheros binarios sin restricción de caracteres, incluso poder directamente subir ficheros al servidor o ejecutar código arbitrario.
Para más  información sobre estas y otras técnicas, podéis consultar las fuentes de documentación que os dejo al final del post.

¡Un saludo!

Fuentes:
Black Hat EU 2013 - XML Out-of-Band Data Retrieval by Alexey Osipov & Timur Yunusov
AppSec USA 2013 - What You Didn't Know About XML External Entities Attacks by Timothy Morgan
XML Schema, DTD, and Entity Attacks by Timothy Morgan & Omar Al Ibrahim
XXE OOB exploitation at Java 1.7+

sábado, 25 de octubre de 2014

Atrapando bugs CSRF con peticiones JSON en Atrapalo.com

Las razones

En el momento de publicar esta entrada, el fallo de seguridad que voy a exponer a continuación sigue siendo explotable. Fue reportado a través y por la motivación que recibí de un intermediario que disponía de contactos con la empresa. Después de más de tres meses, y a pesar de volver a preguntar por el estado del reporte tanto al intermediario como directamente a la empresa (info@atrapalo.com), no he vuelto a obtener respuesta de ninguna de las dos partes. Desde mi punto de vista, revelar un fallo de seguridad que aún no está solucionado es totalmente ético si se dan circunstancias de este tipo. Por un lado, los usuarios son conscientes de los ataques y pueden intentar tomar medidas de precaución y, por otro, se presiona de algún modo a la empresa para que lo corrija. Aparte, y para qué os voy a engañar, otra de las razones de publicarlo es que odio que me hagan emplear mi tiempo sin ningún tipo de consideración o agradecimiento, ya que este análisis no surgió por iniciativa propia. Así, que si las horas que dediqué a encontrar este y otros curiosos fallos de seguridad (que quizá den para más post) sirven al menos para entretener a unos lectores, también me daré por satisfecho.

El fallo

Si tienes experiencia explotando fallos de tipo CSRF, es posible que te hayas topado con el inconveniente de que las peticiones web que intentas explotar son de tipo JSON. Esto puede hacer que un atacante olvide la posibilidad de llevar a cabo esta clase de ataques, ya que los navegadores, haciendo uso de la Same Origin Policy (SOP), bloquearán las peticiones cuyo "Content-Type" se establece a "application/json" cuando se lanzan desde una web controlada por el atacante hacia un dominio distinto. Es decir, los navegadores no permiten llamadas cross-domain de tipo JSON. Para permitir a los programadores lidiar con esta restricción, nació JSONP, pero desgraciadamente estamos en el rol del atacante y no tenemos esta opción.

Echemos un vistazo a las peticiones de modificación de datos de atrapalo.com, por ejemplo a las de la sección "Mi Perfil":



Como vemos en la imagen, las peticiones usan el método PUT para lanzar peticiones JSON. Aparte, no usan ningún tipo de token anti-CSRF, ni se realiza validación del parámetro "id". Tal como comentamos anteriormente, nuestro ataque no va a tener éxito si intenta replicar este tipo de peticiones desde un dominio externo, ya que serán bloqueadas por el navegador. El problema que suelen tener muchas webs en este punto, es que no comprueban en el servidor que la petición que les llega del cliente sea del tipo correcto, por lo que si tenemos suerte, nuestra petición seguirá funcionando igualmente si cambiamos el método PUT por POST y el Content-Type a otro formato distinto de "application/json". Ahora bien, ¿qué Content-Type escogemos y cómo hacemos esta petición desde nuestra web maliciosa? Lo primero, es que el Content-Type debe contener un valor que nuestro navegador permita usar en peticiones cross-domain, y lo segundo es que, a priori, no nos vale crear el típico formulario HTML para enviar la petición, ya que los valores se enviarían con el formato "id=17769328&nombre=MyName&apellido=MyApel..." que no es un formato JSON válido.

Una solución es establecer el Content-Type a "text/plain" y realizar la petición de la siguiente forma:


Estando logueados en atrapalo.com, cuando lanzamos este script desde nuestra web maliciosa, podemos notar que el navegador muestra el siguiente error en consola:


Sin embargo, la petición se ha enviado correctamente al servidor, y si nos logueamos de nuevo en la web, comprobaremos que nuestros datos de perfil han sido modificados:


Si queremos llevar a cabo el ataque mediante un formulario HTML con enctype="text/plain", podemos hacerlo siempre que nos aseguremos que los datos a enviar queden en formato JSON (recordemos que un formulario normal con parámetros input envía los datos en formato "id=17769328&nombre=MyName&apellido=MyApel..."). Para ello, podemos "trucarlo" enviando en un solo parámetro del formulario todos los parámetros JSON incluyéndolos en el atributo "name" y añadiendo un nuevo parámetro ficticio (clave) que consuma el signo "=" sin romper el formato de JSON. Quedaría de la siguiente forma:


Para evitar ser víctimas de estos ataques y hasta que este fallo se arregle, recomendaría a los usuarios de atrapalo.com no visitar ninguna otra web externa mientras estén autenticados.

¡Saludos!

jueves, 27 de febrero de 2014

Bypass de la protección contra Clickjacking en Facebook Studio

Facebook Studio es una aplicación web propiedad de Facebook y, como tal, está dentro del ámbito de su programa de recompensas. Después de unos cuantos tiros a puerta contra el poste, decidí probar suerte revisando su protección frente a ataques de Clickjacking. A priori, esta protección parecía cumplir su función, pero como suele ocurrir, no sabes lo fuerte que es el muro hasta que intentas romperlo. Este fallo de seguridad fue reportado y parcheado en enero, por lo que recibí una recompensa de 500 dólares.

Las protecciones contra ataques de Clickjacking persiguen siempre un mismo objetivo: impedir que una página que realiza acciones sensibles pueda acabar dentro de un iframe en una página maliciosa. Básicamente existen tres formas de hacer esto. Las más comunes, seguras y sencillas, están basadas en el uso de las cabeceras X-FRAME-OPTIONS o Content-Security-Policy en las respuestas HTTP. Sin embargo, hay casos en los que se requiere cierta versatilidad si queremos que la página pueda ser iframada o no dependiendo de condiciones más complejas. En estos casos, a veces se opta por una segunda opción que consiste en asegurarse mediante javascript de que la página se encuentra en el nivel más alto de la jerarquía de iframes (window.top) y que, por lo tanto, no está iframada.

Revisando las cabeceras de respuesta HTTP de Facebook Studio, lo primero que podíamos notar es que no estaba haciendo uso de X-FRAME-OPTIONS. Aún así, si intentábamos incrustar cualquier página de la web dentro de un iframe, también nos resultaba imposible, ya que se resistía a ser "enjaulada" haciendo una redirección de la página contenedora a la propia web de Facebook Studio. Lo vemos de un vistazo revisando el javascript:

<script>
  if (self == top || lc != -1) {
    var theBody = document.getElementsByTagName('body')[0];
    theBody.style.display = "block";
  }
  if (self != top && lc == -1) {
     top.location = self.location;
  }
</script>

Nos interesa el segundo "if", ya que self != top es evaluado a verdadero cuando intentamos iframar la web. Por tanto, si no queremos que esa redirección se produzca, ¿ya sabéis qué toca ahora no?........ ¡Exacto!, ¿qué demonios es esa variable "lc" y cómo podemos hacer que tome un valor distinto de -1?

Rebuscando por el código me encontré con este curioso fragmento codificado:

var _0x1c60=["\x74\x6F\x6B\x65\x6E","\x69\x6E\x64\x65\x78\x4F\x66","\x68\x72\x65\x66","\x6C\x6F\x63\x61\x74\x69\x6F\x6E"];

var lc=window[_0x1c60[3]][_0x1c60[2]][_0x1c60[1]](_0x1c60[0]);

Como vemos, en la primera línea aparece una variable "_0x1c60" que se establece a un array de cadenas codificadas. ¡A estas alturas no nos va a asustar un poco de codificación! Después de pasar cada uno de los valores del array por la función "unescape" de javascript obtenemos lo siguiente:

_0x1c60[3] = "location"
_0x1c60[2] = "href"
_0x1c60[1] = "indexOf"
_0x1c60[0] = "token"

¡¡Uuff!! Esto empieza a tomar forma. Así que la instrucción que establece el valor de la variable "lc" realmente es:

var lc=window["location"]["href"]["indexOf"]("token");

Para los que no estén familiarizados con la notación de corchetes en javascript, esto es equivalente a:

var lc=window.location.href.indexOf("token");

Es decir, si queremos que la variable "lc" tome un valor distinto de -1, ¡simplemente tenemos que usar una URL que contenga la palabra "token"!

¿¿En serio?? Probemos de nuevo a iframar una página de Facebook Studio, pero esta vez añadiendo por ejemplo un parámetro ficticio llamado "token" a la URL:

<iframe width="600" height="500" src='https://www.facebook-studio.com/dashboard/?token'></iframe>

Y el resultado:


Como podemos comprobar en la imagen, la web quedó correctamente incrustada dentro de un iframe al que añadí opacidad para la prueba de concepto.

Facebook corrigió el fallo cambiando la protección al método habitual, añadiendo en las respuestas HTTP la cabecera X-FRAME-OPTIONS con el valor SAMEORIGIN, y todo acabó con un final feliz:



¡Saludos!

martes, 10 de septiembre de 2013

Login y ataques CSRF: Espiando a un usuario en Tuenti

[Artículo cedido como contribución a Flu-Project]

Si alguien se pregunta para qué iba a querer usar un ataque CSRF para forzar a un usuario a loguearse en una cuenta de mi propiedad de la que ya poseo las credenciales, espero que este post pueda despejar sus dudas. El pasado mes de julio reporté esta vulnerabilidad a Tuenti (ya solucionada), por lo que fui incluido como agradecimiento en su Hall of Fame.

El ejemplo más sencillo de este tipo de ataques lo podíamos encontrar con Google, cuyo equipo hace tiempo que solucionó el problema en todos sus servicios de login. Entre las opciones de configuración del buscador se encuentra "Activar historial web" que se encarga de almacenar todas nuestras búsquedas cuando estamos dentro de una sesión. Imaginemos a un atacante cuya intención es trazar la personalidad, las aficiones, intereses, etc. de su víctima en base al uso que hace del buscador. Si el atacante es capaz de loguear a la víctima en una sesión cualquiera de Google que tenga esta opción activada, sólo tendrá que esperar a que el usuario realice sus búsquedas normalmente para luego entrar en la cuenta y espiar el historial generado.

Dependiendo de la naturaleza de la aplicación web, el ataque puede necesitar algún punto de apoyo y tener distintas consecuencias. En una red social como Tuenti, si nos lo curramos un poco, se pueden llegar a espiar mensajes privados, peticiones de amistad, publicaciones privadas, etc.

Como no hay mejor explicación que una prueba de concepto, aquí van los pasos para reproducir el ataque con vídeo incluido (se distinguirá mejor si subís la calidad a "HD"):


1. El atacante modifica el perfil de una cuenta cualquiera (fakeaccount@gmail.com) imitando el perfil de la víctima. Este es el punto de apoyo del que hablábamos anteriormente. Se podría imitar superficialmente con el nombre, foto de perfil, etc., o con más detalle usando contactos falsos, álbumes, intereses, eventos... Todo depende del grado de interés del atacante.

2. El atacante crea una página web que fuerza automáticamente un logueo en Tuenti usando la cuenta falsa. Para ello, aprovecha que el sistema de login es vulnerable a CSRF y no incluye la validación habitual de un token aleatorio que evite una autenticación desde una página externa. Por otra parte, apunta el target del formulario hacia un iframe oculto para que el acceso se produzca de forma silenciosa:


3. El atacante convence a la víctima para visitar la página anterior y le sugiere realizar alguna acción en Tuenti.

4. La víctima visita la página, lo que fuerza un logueo silencioso en la cuenta falsa.

5. Posteriormente, la víctima visita Tuenti para realizar alguna acción. Al haber sido ya logueada en el paso anterior, no se le solicitarán sus credenciales.

6. Al cabo de un tiempo, el atacante entra en la cuenta falsa y espía las acciones que ha realizado la víctima.

Por supuesto, la víctima puede llegar a notar que echa de menos mensajes y demás recursos dependiendo de lo minucioso que haya sido el atacante al clonar el perfil y de la información que disponga de él. Sin embargo, os aseguro que lo habitual es que un usuario medio siga realizando acciones sin alarmarse demasiado, atribuyendo la falta a algún error puntual. Más aún si lo mantenemos entretenido en una conversación con algún contacto falso que hayamos añadido a la cuenta clonada.

Quiero dar mi enhorabuena al equipo de Tuenti, ya que después de recibir la notificación solucionó el problema en menos de 24 horas añadiendo la validación del token aleatorio en el formulario de acceso:

<input type="hidden" name="csfr" value="2a996033">

¡No marginéis a vuestros sistemas de login a la hora de protegerlos contra ataques CSRF!

¡Saludos!