[Bug-Bounty] Encadenando 5 vulnerabilidades hasta conseguir shell a partir de app android.

Buenas

En esta entrada explicaré como ha sido posible encadenar cinco vulnerabilidades en una aplicación de contactos con una base de usuarios de ~8 millones de usuarios para llegar a conseguir una shell en el entorno de pre-producción de una forma relativamente sencilla explotando una serio de fallos de seguridad en la misma (IDOR (Insecure Direct Object Reference) + Race Condition + XSS + Directory listing + XXE = pwned)

Primero de todo agradecer a la compañía el buen trato recibido y que pese a no tener un programa de recompensas hayan reconocido el trabajo con una suma cercana a las cinco cifras; además de permitirme escribir un writeup, eso sí, redactando de una forma que no se identifique a la compañía, por tanto sin fotos (para censurar todo mejor no poner nada) +parrafada inside +acto de fe.

Todas las vulnerabilidades fueron parcheadas en las 3 horas siguientes al primer contacto.

Resumen

Antes de entrar en el asunto de la explotación, un breve sumario de como fue el proceso.

  1. Buscar un IDOR en la API que comunica la app con el servidor para modificar el emisor y receptor de los mensajes.
  2. Explotar una condición de carrera en el identificador de mensaje y hacer funcionar el IDOR para impersonar al usuario .
  3. Enumeración de usuarios a través de fuzzing a las IDs para conseguir cuentas con permisos elevados.
  4. Explotar XSS a través de chat con un administrador para robar las cookies.
  5. Acceder al panel de pre-producción con las cookies robadas.
  6. Conseguir un listado en un directorio con archivos de configuración en xml.
  7. Ejecutar código en el servidor mediante XXE.
  8. Spawnear una shell (sin root) en el servidor.

Explotación

Resultado de imagen de pwn time

La APP como ya he comentado es la típica para «conocer gente», por tanto las acciones disponibles son basicamente, subir fotos, dar likes y mandar mensajes, toda esta información se envía al servidor a través de una API en formato json + base64.

Además tiene un dashboard web donde realizar algunas de las opciones de las que dispone la app.

Es bastante común que las APIs no tengan demasiados controles de seguridad, hay múltiples writeups por ahí de IDORs en APIs con resultados muy diversos, borrar comentarios, editar información de otros usuarios etc. En este caso no encontré forma de explotar un IDOR para editar información en otras cuentas dummy por la presencia de un token bearer, en cambio si era posible impersonar usuarios en el envío de mensajes, editando  el ID de usuario de origen y el ID de usuario destino, ya que dada la naturaleza de estas aplicaciones los usuarios deben poder comunicarse entre ellos vía mensajes de texto (siempre que haya coincidencias de me gusta) sea como fuere, a través del mencionado dashboard web es posible saber el ID de nuestro usuario para poder contrastarlo con la petición desde la APP, y asegurarnos de quién somos para el servidor (un número), ademas en este caso los identificadores son autoincrementales y por tanto se puede fuzzear hasta dar con la primera cuenta (id 0 ||id 1) que en buena lógica sera de un desarrollador (la primera persona que usa la app).

Recreación de la información enviada al servidor entre dos cuentas controladas a traves de la APP.

{ «idmsg»: 111111, «from»: 12345, «to»: 4567, «message»: «mensaje dummy» } (sin b64)

  • Primero el identificador único del mensaje
  • ID de quién envía el mensaje
  • ID de quién recibe el mensaje
  • Mensaje

En este punto la forma de explotar es mas o menos clara (cambiar idmsg, from y to), salvo por un pequeño detalle, necesitamos un idmsg válido, que no haya sido usado (el de la petición dummy ya no nos sirve, y que nos deje enviar un mensaje desde un id que no es el nuestro, cosa que obviamente comprueba y devuelve error), pero claro, mediante la app se envían muchos mensajes, así que en el intervalo desde que enviamos un mensaje nosotros y queremos enviar el siguiente no sabemos que id asignarle a ese campo que no haya sido usado ya y que por tanto nos devuelva un error el servidor; en este punto se abre un abanico de varias opciones para probar.

  1. Darle un id al mensaje de 999999999 (o un numero muy superior al ultimo que enviamos para asegurarnos que ese id no se ha usado).
  2. Intentar explotar a ciegas un integer overflow en el backend.
  3. Intentar una condición de carrera para enviar mas de un mensaje con el mismo id.

Opción 1:

Dando al mensaje un identificador lo suficientemente alto el servidor nos responde con error por petición mal formada, así que primer fail y descartamos esta opción.

Opción 2:

Se me ocurrió que quizás se podría dar al id de mensaje un numero lo suficientemente alto como para provocar un integer overflow, para los que desconozcan esta vulnerabilidad, un ejemplo en c++:

#include <iostream>
#include <climits>
using namespace std;
int main()
{
int n = 0;
int num = INT_MAX;
cout << num << endl;
n = num + 1;
cout << n << endl;
return 0;
}

Salida:

2147483647
-2147483648

De este modo se podría, sin conocer el id del último mensaje procesado, asignar un id al mensaje que sea menor (por que mayor ya ha dado error) pero que no este en uso. El problema es que no se sabe que tecnología esta corriendo en el backend, así que un integer overflow a ciegas se paga 1000 a 1 que funcione (aparte del riesgo de crear una denegación de servicio lo cual seria cagar muy fuera del tiesto), y como era de esperar no funcionó.

Opción 3:

Intentar explotar una condición de carrera para *enviar dos mensajes diferentes con el mismo id*, (vulne bien explicada).

Para probar necesitamos 3 cuentas dummy, una que envía el mensaje y dos para jugar con los IDs del to. A nivel teórico como explotarlo es sencillo con burp -> curl && curl > comprobar respuesta y mirar la app. A nivel práctico, una condición de carrera es de lo más inestable que hay a nivel de exploiting, asi que tocaba cruzar los dedos para que funcionase algún intento.. y funcionó después de unos cuantos. Había recibido el mismo mensaje en las dos cuentas dummy.

Una vez validada la condición de carrera, lo que realmente nos interesa es hacer un user impersonation a través de un IDOR. La idea es conseguir de este modo que un administrador me escriba a la cuenta dummy y poder usarle como vector para escalada de privilegios. Como tenemos el dashboard web podemos fuzzear IDs hasta encontrar la mas baja, siendo esta la primera persona en darse de alta en el servicio, que tiene bastantes papeletas de ser un dev.

Efectivamente al acceder al perfil, la miniatura (foto de perfil del usuario) es el logo de la aplicación, asi que tiene buena pinta el asunto.

Pero saber el id de admin y saber que podemos mandarnos mensajes desde su cuenta no nos va a dar una shell. Dado que es una app random, sin bug bounty ni siquiera un responsive disclosure, no esta la presion que existe siempre en el resto de programas de «Voy a reportar esto ya a ver si se me va a adelantar alguien.» quien haya visto alguna vez esa etiqueta o puntito morado en un report sabrá de lo que hablo XD.

Asi que sin prisa se puede pensar como aprovechar esta vulnerabilidad, que de base ya es crítico el poder impersonar a cualquier usuario de la plataforma de forma completamente arbitraria.

Yendo al grano, en el dashboard de la web se recibian notificaciones del tipo «Has recibido un mensaje de X: blablalba» (tambien se podria leer el chat). de modo que si somos capaces de inyectar un XSS en ese campo estaremos ejecutando codigo en el lado del cliente a traves del dashboard en cuanto le salte la notificacion al usuario y pinche encima para leerla. Esto lógicamente tiene una gran pega, tenemos que mandarle un payload por el chat a un administrador, que lo leera y saltaran todas las alarmas, pero no solo eso, es que hay que impersonarle con el IDOR para que el inicie la conversación él(nosotros no podemos porque no tenemos «match»), en fin, lo de siempre, pruebas con cuentas dummy, y para mi sorpresa, no solo funciona, si no que lo que la otra persona recibe como mensaje un mensaje vacio, valga la redundancia, como si hubiéramos mandado un espacio.

Resultado de imagen de wtf meme

Repetimos el proceso con el usuario que creemos que es admin y a esperar que este conectado, le salte la notificación y abra el chat.

Una vez tenemos sus cookies, me doy cuenta de que no coincide la IP de la petición con la IP del dashboard donde yo puedo hacer login, asi que nmap a esa IP a ver que nos dice y puerto 8080, 22 y algunos otros que no interesan abiertos.

En el 8080 un panel de login muy similar al del dashboard web, pero a la vez diferente, como una version mejorada (mas bonita, en le login; y con mas opciones dentro). Inyectamos las cookies y para dentro.

Escalada de privilegios.

Resultado de imagen de privilege escalation

Aquí si que pensé mas seriamente, mandar reporte anónimo y a dormir, por que tocar algo ahi puede suponer una interrupción del servicio y sería pasarse. Pero bueno, decidi explorar un poco, ir pasando por las diferentes opciones sin cambiar nada para generarme un arbol de directorios y archivos en burp que pueda mirar después si por lo que sea pierdo la sesión (se caduca).

Revisando al cabo de un rato, en una de las visitas por los apartados de la web, habia leido */config/service-cfg.xml , asi que me paso al directorio /config y pum, directory listing. Vuelvo a recorrer todas las opciones hasta que encuentro un formulario de subida de archivos, intento subir un txt para probar tema filtros y no me deja, pruebo otra serie de extensiones y tampoco, asi que XML, que no me deja subir, pero ya cambia el error de un «unsupported file type» a «format error». esta claro que el servidor verifica la integridad de la estructura del archivo, situación que se pudo solucionar fácilmente usando la misma estructura de uno de los archivos del directorio*/config/ que tuviera alguno de los campos vacíos para poder hacer ahí la llamada a una entidad que me ejecute código en el servidor, sin alterar la configuración original. En este caso no era capaz de ejecutar código para poder pillar una shell reversa, en cambio me acordé de una situación exactamente igual que habia tenido hace un par de semanas en una maquina boot2root que puede solucionar leyendo la clave privada de conexión por ssh, y así conseguir una shell a través de dicho servicio.

Ya no había mucho mas que hacer, reporte al email que figuraba en la play store sin detallar demasiado, con un par de fotos de la shell y el dashboard de dev, con las ips utilizadas,  las cuentas, y a jugar unas merecidas partidas al csgo, hasta que responden, luego skype con un dev y un sysadmin, se parchea todo y a dormir que iba siendo hora.

Fin de la explotación.

Ahora algunas cosas ya a titulo personal dejando temas técnicos a un lado y respondiendo alguna pregunta que pueda surgir y/o que me hayan hecho.

¿Por qué explotas una vulnerabilidad en un entorno real con el riesgo que eso conlleva existiendo tantas plataformas de CTFs y de pentest en infraestructura muy buenas como hackthebox, ihacklabs o vulnhub?

Por que no es lo mismo. Los CTFs están bien para aprender cosas nuevas que no sabes ni que existen pero la sensación una vez completado no es la misma. No es la misma sensación la que se consigue al hacer un keygen sobre un keygenme que sobre un software comercial, lo mismo con exploits… etc. También yo me pregunto a veces ¿Por qué la gente sale a andar en bici o a correr por la calle si pueden hacerlo en una cinta o en una bicicleta estática? Me imagino que la respuesta en el 90% de los casos sera la misma «no es lo mismo» aunque lo parezca. Pues eso.

Sobre los riesgos. Bueno, es evidente que la reacción de una compañía ante esta situación puede ser adversa, pueden coger sin mas el email y reenviarlo a su cert gubernamental, o a sus respectivas FCSE, tú decides si corres o no el riesgo, en mi opinión cualquier persona con dos dedos de frente se tomará estas situaciones como una oportunidad de oro para minimizar el riesgo de sufrir un ataque de una persona o grupo con intenciones muy diferentes que pueden suponer su quiebra económica o la pérdida absoluta de la confianza de sus clientes que les puede llevar a lo primero.

¿Qué esperas a cambio cuando reportas algo a una organización que no tiene una política definida a este respecto?

La respuesta es fácil. Nada. No estás en condiciones de exigir nada (nadie te ha pedido que toques nada, lo haces por diversión, entrenamiento o el motivo que sea), es más bajo mi punto de vista, que exijas algo (lo que sea) a cambio es extorsión. La empresa está en todo su derecho de no responder, responder con un gracias, de responder que lo pondrán en manos de sus abogados, o como en este caso agradecerlo y dar una compensación económica (más o menos generosa, es solamente decisión suya y hay que tomarlo siempre como un gesto muy positivo). En este caso he tenido la suerte de toparme con una empresa concienciada, con un par de personas en nómina que saben de seguridad, y que saben lo que es un bug-bounty, de hecho ya estaban barajando sacar un programa de recompensas antes de que yo contactara con ellos. Además la valoracion de esta experiencia en concreto es 10/10, no solo he recibido un trato perfecto si no que he podido aportar ideas para solucionar los problemas, algo que no es nada común y que por lo menos para mi, que nunca he trabajado en una empresa, ver cómo gestionan un servicio con tanta demanda y el curro que hay detrás me ha parecido una experiencia muy enriquecedora.

No es el mismo caso que con una empresa que si tiene activo un programa de recompensas, en el cual si esperas algo a cambio de tu trabajo(ya sea dinero o hall of fame), y si no lo recibes o crees que la recompensa no esta en concordancia con lo que «prometen» (como hace starbucks sistemáticamente, por poner un ejemplo) tienes todo el derecho de reclamar de forma argumentada a través del equipo intermediario, sea bugcrowd, sea h1 etc.

¿Qué medidas se han puesto en marcha para solucionar estos bugs?

Para la primera parte se randomizan los IDs de usuario y no es posible acceder a los perfiles por ID para la enumeración de usuarios via web.

Para el XSS se sanitiza correctamente el contenido de los chats en la web.

Para el XXE se sanitiza el documento.

¿Qué app es?

Como he mencionado la principio la empresa prefiere mantener su privacidad, algo totalmente legítimo igual que otras muchas empresas con programas de recompensa activos no permiten el «public disclosure», o censuran el 90% del reporte, o directamente llevan a cabo programas privados con un número de personas muy reducido y unas políticas respecto a la difusión muy estrictas. Si finalmente lanzan un programa público editare la entrada.

¿Es esto «hacking ético»?

Es hacking, a secas. El tema de «hacking ético» siempre me ha parecido una invención de puro marketing, añadirle ético a la palabra para poder comercializar el concepto sin que dinosaurios digitales lo miren mal,  el hacking es hacking, lo que es ético o no ético es el comportamiento de las personas; del mismo modo que cuándo vas al médico no vas a una suerte de «médico ético» por que la medicina es medicina sin más, a una persona puede no parecerle ético que un cirujano plástico haga X tipo de operación pero no por eso al resto de médicos les vas a llamar «médico ético» y al resto lo contrario. Lo mismo se puede aplicar a muchas mas profesiones por ejemplo soldado-contratista privado etc.

Hasta aquí la entrada.

 

 

 

 

 

 

 

 

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s