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.
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.