EB Secret, c0r0n4con (ii/iii)

Tenemos un binario ELF para x86. Vamos a analizar strings:

VIP ACCESS y /etc/shadow en la misma pantalla suena peligroso 🙂

Vemos las cadenas características de éxito/fracaso y también extrañas cadenas kill, /etc/shadow, /etc, nonexist.txt

¿Qué dira ghidra al respecto? Curiosamente si intentamos decompilar la función main veremos que se queda prácticamente colgado. Al cabo de un minuto o dos vuelve a la vida y vemos que el proceso de decompilación las está pasando canutas:

Decompilando main… no le ha gustado mucho

¿Qué estará ocurriendo con ese binario? Creamos un docker para ejecutarlo con prudencia. chmod +x ./decoded y antes de lanzarlo nos acordamos del «Don’t run as root» y viendo la cadena /etc/shadow en el binario, igual es una buena idea hacerlo como usuario no-root, sí.

No parece que haya nada raro a simple vista…

Vamos a trazarlo con strace ./decoded

«Tracer detected!»… vaya

No le gusta tampoco el strace. Mientras lo hacíamos, ghidra a vuelto a la vida y al menos nos deja curiosear por el desensamblado de main (el decompilador sigue loco)

Decenas de llamadas a funciones para generar errores artificialmente

Esas funciones generate_error_X sí que son descompilables. Por ejemplo, generate_error_1 es una simpática función que cambia los permisos de /etc/shadow

Tu /etc/shadow será legible para todo el mundo si ejecutas el binario como root

La cadena nonexist.txt se debía a esto otro:

Provocando otro error al abrir un fichero no-existente

Como ejercicio interesante vamos a ver dónde detecta el tracer y parchear el binario para que no lo haga. Copiamos el binario en cracked, lo abrimos en radare con radare2 -w cracked. Analizamos el mismo con el comando aaa. Pulsamos V (modo visual) y p para ver el desensamblado. Pulsamos ahora _ para ver referencias a cadenas, tecleamos tracer y pulsamos intro . Pulsamos x (cross-references) para que nos lleve a la línea de código donde se usa la cadena tracer y pusalmos intro.

En 0x0125d vemos la llamada a call sym.imp.ptrace y un jne a 0x01296 si no lo detecta. Debemos invertir la lógica del jne a je.

Nos situamos en 0x0000126a y pulsamos A para empezar a añadir el código que queremos (je 0x1296). El mismo radare2 generará el código de operación necesario (742a). Pulsamos intro y guardarmos el resultado. Salimos con q y quit.

radare2 nos permite parchear un binario con el comando A

Ahora sí, strace nos informa de multitud de llamadas a funciones para generar errores:

wow… kill(-9999,SIG_0), open(«nonexist.txt»), chmod(«/etc/shadow»,0754) a cascoporro…

Al haber eliminado esa protección anti-tracer también conseguiremos facilitar el análisis del binario con radare2, esta vez en modo debug. Veamos cómo hacerlo en el siguiente post.

EB Secret, c0r0n4con (i/iii)

La gente de fwhibbit (Follow the White Rabbit) sigue publicando retos en el CTF de la c0r0n4con aún tras haber finalizado la conferencia. Ya comenté en su día que Mike’s Dungeon (Web) de @jorge_ctf me pareció una pequeña obra de arte. Hoy le toca el turno a otra joya, EB Secret (Misc), de @naivenom.

EB Secret entra en la categoría Misc, agrupando técnicas web y reversing

EB Secret comienza con un sencillo mensaje que abre la caja de Pandora.

«The new platform for premium video is vulnerable. Do you know the way to obtain the obsolete program and recover the secret key? DON`T RUN AS ROOT.
http://167.172.187.39:8004/»

El «Don’t run as root» ahora no lo terminamos de entender, pero tomará sentido dentro de poco… Así que nos adentramos en la madriguera del conejo, y nos encontramos con el primer pasadizo:

http://167.172.187.39:8004/secret?name=admin

admin como parámetro en la URL y admin reflejado en el contenido de la página. Mmmh…

http://167.172.187.39:8004/secret?name=trololo refleja el nombre trololo. http://167.172.187.39:8004/secret?name=`ls` refleja directamente, sin interpretar las comillas (¡hey! tenía que intentarlo). También podemos comprobar que hay limitación de tamaño en el parámetro, admite 30 caracteres. Si metemos un valor de 31, el contenido reflejado vuelve a tomar el valor admin: http://167.172.187.39:8004/secret?name=1234567890123456789012345678901

Probar, probar y probar… Costó demasiado que llegara la inspiración:

SSTI FTW!

http://167.172.187.39:8004/secret?name={{4*4}}

SSTI, Server-Side Template Injection. Bingo. Vamos a darle calor:

http://167.172.187.39:8004/secret?name={{config}}

El autor nos saluda con un Holi en las variables de entorno 🙂

Con 30 caracteres máximo no podemos hacer piruetas de (J)(N)inja, pero podemos intentar gatear:

http://167.172.187.39:8004/secret?name={{url_for.__globals__}}

¿Qué hemos aprendido? La current_app se llama ssti (lo que viene a confirmar, una vez más, el server-side template injection)

http://167.172.187.39:8004/secret?name={{self.__dict__}}

¿qué sacamos en claro? Hay un secret en base64 en la última línea:

echo "bm90X3ByZWQxY3RhYmxlX2Z1bmN0MTBu" |base64 -D
not_pred1ctable_funct10n

y una función con el mismo nombre: not_pred1ctable_funct10n ….

¿Qué ocurrirá si ejecutamos la función?

http://167.172.187.39:8004/secret?name={{not_pred1ctable_funct10n()}}

Jackpot!

Copiamos el contenido en un archivo s.64 y lo parseamos.

import base64

f=open("s.b64", "r")
contents =f.read()
splited = contents.split(',')

res = ''
for i in splited:
   res = res + i.strip().replace('\'','').replace('\\n','').replace('[','').replace(']','')


base64_img_bytes = res.encode('utf-8')
with open('decoded', 'wb') as file_to_save:
    decoded_image_data = base64.standard_b64decode(base64_img_bytes)
    file_to_save.write(decoded_image_data)

Veamos qué tenemos por aquí:

$ file decoded
decoded: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=33e62720986f5c9ad3c036ef7ec5118f7e19fd09, not stripped

Yeah, ¿bajamos un nivel más en la madriguera del conejo? Esto se pone interesante…

#GeratuEtxean: HackIt! Level 3

Para terminar, un clásico.

Un ELF para arm64 con mensaje para w0pr / Ramandi incluido 🙂

Abrimos con Ghidra y vemos que hay una función encargada de pedirnos 16 caracteres y comprobar que forman una key correcta.

Toca generar el programa que revierta las comprobaciones… Pero antes habrá que arreglar ese monstruo de código del descompilador…

$ file cambridge_technology
cambridge_technology: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, stripped

$ uname -a
Linux ip.ec2.internal 5.0.10-300.fc30.aarch64 #1 SMP Tue Apr 30 16:06:13 UTC 2019 aarch64 aarch64 aarch64 GNU/Linux

$ ./cambridge_technology
Password: 12312312313123131
FAIL!

w0pr (@abeaumont en concreto) fue el primer y único equipo que consiguió hacer ingeniería inversa de ese monstruo ¯\_(ツ)_/¯ Alfredo ha publicado el write-up, merece la pena leerlo (y replicarlo) con detalle.

#GeratuEtxean: HackIt! Level 2

Nuestros espias han localizado la flag de este nivel, pero… ¡Alguien ha destruido la caja de password! Afortunadamente, nos han proporcionado instrucciones para enviarla, pero no acabamos de entenderlas… ¿Nos ayudas?

— BEGIN TRANSMISSION —
PAYLOAD FOUND ON trololo@54.171.128.20:34342
ACCESS b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACD48tA2UHkNwf1gjzFoefbSiiA3s0+FIYWYOlxHuwDAVAAAALhSf19rUn9f awAAAAtzc2gtZWQyNTUxOQAAACD48tA2UHkNwf1gjzFoefbSiiA3s0+FIYWYOlxHuwDAVA AAAECeukBbUT2Vlozfd98BRRvKGCFRc0mdvRhAItlDfp1U7vjy0DZQeQ3B/WCPMWh59tKK IDezT4UhhZg6XEe7AMBUAAAALnJvb3RAaXAtMTcyLTMxLTYtNjUuZXUtd2VzdC0xLmNvbX B1dGUuaW50ZXJuYWwBAgMEBQYH
IDENTIFY USING 20ce8a7cc776a39ad291d4648e3e39ae.hax
SEND FLAG AS TEXT
— END TRANSMISSION —

Si nos intentamos conectar por ssh trololo@54.171.128.20 -p 34342 vemos que el servidor responde, pidiendo clave pública. Descodificando el string de ACCESS de base64, vemos una referencia a una máquina remota (AWS) y esta keyword: openssh-key-v1nonenone3

Haciendo pruebas, detectamos que la clave sigue el algoritmo DSA (no RSA). La preparamos:

$ cat id_dsa
—–BEGIN OPENSSH PRIVATE KEY—–
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACD48tA2UHkNwf1gjzFoefbSiiA3s0+FIYWYOlxHuwDAVAAAALhSf19rUn9f
awAAAAtzc2gtZWQyNTUxOQAAACD48tA2UHkNwf1gjzFoefbSiiA3s0+FIYWYOlxHuwDAVA
AAAECeukBbUT2Vlozfd98BRRvKGCFRc0mdvRhAItlDfp1U7vjy0DZQeQ3B/WCPMWh59tKK
IDezT4UhhZg6XEe7AMBUAAAALnJvb3RAaXAtMTcyLTMxLTYtNjUuZXUtd2VzdC0xLmNvbX
B1dGUuaW50ZXJuYWwBAgMEBQYH
—–END OPENSSH PRIVATE KEY—–

Y conectamos:

$ ssh -i id_dsa -p 34342 trololo@54.171.128.20
Last login: Sat Mar 28 14:34:31 2020 from xxxxxxx
-sh-4.2$

Es una shell restringida (ni ls, ni gaitas). Además, si intentas borrar algo, te añade un espacio en blanco… En fin… El autocompletamiento con tabulador funciona. Vemos que existen algunos directorios, entre ellos /bin. Añadimos /bin al PATH y disponemos de cat, dig, ls y nsupdate.

El comando dig nos da una pista…. recordemos también que no hemos usado esta parte del enunciado:

IDENTIFY USING 20ce8a7cc776a39ad291d4648e3e39ae.hax

Investigando un poco vemos que existe un proyecto con TLD .hax para DNS dinámicos. La otra pista «SEND FLAG AS TEXT » nos parece indicar que necesitamos crear un registro TXT con la Flag. ¿Pero dónde está la flag? Bueno, no había muchos directorios en la máquina restringida donde nos encontramos, así que rastreando carpetas nos encontramos con /var/tmp/secret,

-sh-4.2$ cat /var/tmp/secret
Is0latI0nFl4w3dNetW0rkz!

Pedimos un dig del TLD hax:

dig -t txt 20ce8a7cc776a39ad291d4648e3e39ae.hax

y vemos que vamos bien:

-sh-4.2$ dig -t txt 20ce8a7cc776a39ad291d4648e3e39ae.hax

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-9.P2.amzn2.0.2 <<>> -t txt 20ce8a7cc776a39ad291d4648e3e39ae.hax
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12895
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 2

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;20ce8a7cc776a39ad291d4648e3e39ae.hax. IN TXT

;; AUTHORITY SECTION:
hax. 60 IN NS ns1.hax.

;; ADDITIONAL SECTION:
ns1.hax. 60 IN A 127.0.0.1

;; Query time: 0 msec
;; SERVER: 172.31.6.65#53(172.31.6.65)
;; WHEN: Sat Mar 28 16:41:53 UTC 2020
;; MSG SIZE rcvd: 136

Nos queda hacer el nsupdate … pero antes, veamos quién resuelve los DNS en esta máquina:

-sh-4.2$ cat /etc/resolv.conf
nameserver 172.31.6.65

$ nsupdate

server 172.31.6.65

update add 20ce8a7cc776a39ad291d4648e3e39ae.hax. 300 TXT «Is0latI0nFl4w3dNetW0rkz!»

send

Y listo! Pulsamos en el botón que nos ha preparado la ORG en ese level y pasamos de nivel

#GeratuEtxean: HackIt! Level 1

Esta edición de la GipuzkoaEncounter ha sido un tanto extraña. Confinados en casa, hemos intentado llevarlo lo mejor posible. Aunque no es para nada lo mismo y todos preferimos la presencial, esta edición online no ha estado nada mal. Y para no perder las buenas costumbres, vayamos con el write-up.

¡Ayuda! ¡Nos han hackeado la cuenta de Discord! Parece que han hecho cosas raras. Hemos conseguido un log de lo que han hecho…

Nos pasan un fichero .har (un dump de una conexión HTTP). Aunque internamente es simplemente un enorme fichero JSON, lo mejor para visualizarlo de forma rápida es abrirlo directamente con el editor de Chrome DevTools, pestaña Network. Importamos el fichero HAR para echarle un vistazo y vemos lo siguiente.

Podemos ver puntos verdes en la línea del timeline, donde van varios POST

Seleccionando uno de esos puntos:

Mensajes POST hacia el servidor de Discord. Se ve que el usuario está tecleando contenido («0ur53»)

El problema es que no vale con recorrer esos mensajes de tipo POST. Hay una complicación: el autor del reto ha editado algunos trozos (mensajes con método HTTP PATCH) e incluso borrado otros (mensajes DELETE).

Solucionamos con un pequeño script en nodejs