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!