Gavel is a medium-difficulty Linux machine from HackTheBox Season 9 that involves exploiting a PDO SQL injection vulnerability, achieving RCE through PHP rule evaluation, and escalating privileges by manipulating YAML configurations to bypass PHP restrictions.
Examining inventory.php reveals a PDO SQL injection vulnerability:
1
2
3
4
5
6
7
$sortItem=$_POST['sort']??$_GET['sort']??'item_name';$userId=$_POST['user_id']??$_GET['user_id']??$_SESSION['user']['id'];$col="`".str_replace("`","",$sortItem)."`";// Vulnerable query construction
$stmt=$pdo->prepare("SELECT $col FROM inventory WHERE user_id = ? ORDER BY item_name ASC");$stmt->execute([$userId]);
ℹ️
Vulnerability: While user_id uses a prepared statement placeholder, the $col variable (derived from the sort parameter) is directly interpolated into the SQL query. The backtick stripping is insufficient to prevent injection.
$rule=$auction['rule'];$rule_message=$auction['message'];// Dynamically creates a PHP function from user-controlled input!
runkit_function_add('ruleCheck','$current_bid, $previous_bid, $bidder',$rule);$allowed=ruleCheck($current_bid,$previous_bid,$bidder);
ℹ️
Critical Vulnerability: The application uses runkit_function_add() to dynamically create PHP functions from auction rules stored in the database. If we can modify these rules, we can achieve arbitrary code execution.
# Spawn proper TTYpython3 -c 'import pty;pty.spawn("/bin/bash")'# Background shell (Ctrl+Z)# In local terminal:stty raw -echo;fg# Reset and configurereset
exportTERM=xterm
exportSHELL=/bin/bash
Key Discovery: The gaveld daemon processes YAML submissions and executes the rule field as PHP code using a restricted php.ini configuration. However, we can overwrite this configuration file!
auctioneer@gavel:/tmp/priv$ /usr/local/bin/gavel-util submit ini_overwrite.yaml
Item submitted for review in next auction
ℹ️
Why only required fields?: Through trial and error (as mentioned in the chat logs), including only the required YAML keys prevents validation errors. Extra keys cause the submission to be rejected.
auctioneer@gavel:/tmp/priv$ /usr/local/bin/gavel-util submit root_suid.yaml
Item submitted for review in next auction
auctioneer@gavel:/tmp/priv$ sleep 3auctioneer@gavel:/tmp/priv$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1396520 Mar 142024 /bin/bash
# Transfer the script to the targetauctioneer@gavel:~$ cat > root_exploit.sh
# Paste the script above# Press Ctrl+D# Make it executableauctioneer@gavel:~$ chmod +x root_exploit.sh
# Run the exploitauctioneer@gavel:~$ ./root_exploit.sh
graph TD
A[Start Exploit] --> B[Create Working Directory]
B --> C[Generate php.ini Overwrite YAML]
C --> D[Submit via gavel-util]
D --> E[Wait 5 seconds]
E --> F[Generate SUID Payload YAML]
F --> G[Submit via gavel-util]
G --> H{Check /bin/bash SUID?}
H -->|Yes| I[Execute /bin/bash -p]
H -->|No| J[Try Alternative: Copy bash]
J --> K[Submit Copy Payload]
K --> L{Check /tmp/rootbash?}
L -->|Yes| M[Execute /tmp/rootbash -p]
L -->|No| N[Exploit Failed]
I --> O[Root Shell Obtained]
M --> O
O --> P[Read /root/root.txt]
ℹ️
Why Two Methods?
The script includes a fallback mechanism:
Primary: Directly sets SUID bit on /bin/bash
Fallback: Creates a copy of bash in /tmp with SUID permissions
This ensures the exploit works even if there are restrictions on modifying system binaries directly.
Mitigation: Use whitelist validation for column names
1
2
3
4
5
6
7
8
// Vulnerable code
$col="`".str_replace("`","",$sortItem)."`";$stmt=$pdo->prepare("SELECT $col FROM inventory WHERE user_id = ?");// Secure alternative
$allowedColumns=['item_name','quantity'];$col=in_array($sortItem,$allowedColumns)?$sortItem:'item_name';$stmt=$pdo->prepare("SELECT `$col` FROM inventory WHERE user_id = ?");
Mitigation: Never execute user-controlled strings as code
1
2
3
4
5
// Vulnerable pattern
runkit_function_add('ruleCheck','$current_bid, $previous_bid, $bidder',$user_controlled_rule);// Secure alternative
// Use a safe expression evaluator or predefined rule system
PDO Prepared Statements Are Not Magic: While prepared statements prevent traditional SQL injection, column/table names must still be validated separately.
Never Trust User Input in Code Evaluation: Functions like eval(), create_function(), and runkit_function_add() should never process user-controlled data.
Defense in Depth: Multiple security layers failed:
Weak authentication
SQL injection
Unsafe deserialization (YAML)
Improper file permissions
Configuration Management: Runtime configurations (like php.ini) should be immutable by web processes.
Thanks for reading! If you found this writeup helpful, consider sharing it with others learning penetration testing and web exploitation.
HTB Season 9
Medium
Linux
Web
I hope this was helpful
Posted:
Time since posted: calculating...
System.Motivation.Load()
Comments & Discussion
Share your thoughts, analysis, or questions below
0 Comments
Author
Adonijah Kiplimo
Cybersecurity professional specializing in Network & Cloud Security, Digital Forensics, and Penetration Testing. Passionate about sharing knowledge and empowering others through hands-on security training.
HackTheBox Season 9 -
This article is part of a series.
Comments & Discussion
Share your thoughts, analysis, or questions below