Headless — HackTheBox Writeup
Nmap scan revealed SSH (22) and a Werkzeug Python web app on port 5000. Directory fuzzing uncovered /dashboard (401) and /support (contact form).
Triggered hacking detection by injecting XSS in the message body, while the real XSS payload in the User-Agent header stole the admin's is_admin cookie when the admin reviewed the flagged request.
Used the stolen admin cookie to access /dashboard. The date parameter in the report generator was passed directly to a system command, allowing arbitrary command execution as dvir.
dvir could run /usr/bin/syscheck as root via sudo. The script called ./initdb.sh using a relative path. Created a malicious initdb.sh that injects pam_permit.so into /etc/pam.d/su, then used su to escalate to root with any password.
Reconnaissance
::Nmap Scan
Only two ports open — SSH and a Python Werkzeug web application on port 5000. The Werkzeug framework hints at a Flask-based application.
::Web Enumeration
Browsing to http://10.129.41.89:5000 shows an "Under Construction" countdown page with a button linking to the support form.

Key findings:
- ▹
/dashboard— returns 401 Unauthorized (requiresis_admincookie) - ▹
/support— public contact form with fields: first name, last name, email, phone, message
The application sets a cookie is_admin=InVzZXIi... (base64 of "user") without the HttpOnly
flag, making it vulnerable to JavaScript-based cookie theft.

Initial Foothold
::Step 1: Trigger the Hacking Detection
The /support form has an input sanitization feature — when it detects HTML/script tags in the message body, it triggers a "Hacking Attempt Detected" alert. This alert displays the full HTTP request headers and is reviewed by an admin bot.
The key insight: the XSS payload goes in the User-Agent header, while the message body contains a decoy tag just to trigger the detection.
::Step 2: Set Up a Listener
Start a Python HTTP server to catch the exfiltrated cookie:
::Step 3: Send the XSS Payload
Why this works: The message body <script>test</script> triggers the hacking detection
system. The admin bot reviews the flagged request and renders the page containing all request
headers — including our malicious User-Agent. Since the is_admin cookie lacks the HttpOnly
flag, document.cookie captures it and sends it to our listener.
::
Within seconds, the admin bot triggers the XSS and we receive a callback:
Stolen cookie: is_admin=ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0
Decoding the cookie value: ImFkbWluIg is base64 for "admin" — the original user cookie was
InVzZXIi (base64 for "user"). The application uses a signed cookie with a Flask itsdangerous
token.
Command Injection
::Accessing the Admin Dashboard
With the admin cookie, we can now access /dashboard:
The dashboard contains a "Generate Report" feature with a date input field.

::Testing for Injection
The date parameter is passed directly to a system command without sanitization:
Output:
::Getting a Reverse Shell
Start a netcat listener:
Send the reverse shell payload:
We land a shell as dvir and can read the user flag:
Privilege Escalation
::Sudo Enumeration
::Analyzing /usr/bin/syscheck
Vulnerability: The script calls ./initdb.sh using a relative path. Since syscheck runs
as root via sudo, if we control the current working directory and place a malicious initdb.sh
there, it will be executed as root.
We talk about this technique, PATH hijacking, and Linux sudo escalation vectors in depth in our blog & cheatsheet: Linux Privilege Escalation: Basics & Exploitation and Linux Privilege Escalation: Enumeration & Recon.
::Exploitation — PAM Backdoor Injection via Relative Path Hijack
Instead of the typical SUID bash or reverse shell approach, we abuse the relative path vulnerability to inject a PAM authentication backdoor. This modifies the system's authentication stack so that su root succeeds with any password — a stealthier and more elegant escalation.
Step 1: Craft initdb.sh to inject pam_permit.so into /etc/pam.d/su:
Why PAM? The pam_permit.so module unconditionally succeeds authentication. By inserting it
as the first auth rule with sufficient, PAM short-circuits — it never reaches the real
password check. Unlike SUID bash, this leaves no suspicious file permissions behind and survives
reboots.
Step 2: Trigger the payload — run syscheck from our home directory where ./initdb.sh resolves to our backdoor:
Output:
Step 3: Verify the PAM injection took effect:
Step 4: Escalate — su now accepts anything as a valid password:
Why this is dangerous in the real world: A PAM backdoor is persistent — it survives across
reboots and doesn't show up in typical checks like find / -perm -4000 (SUID hunting) or process
monitoring. It only becomes visible if an admin audits /etc/pam.d/ configs directly.
References

Red Team Consultant · Penetration Tester · Bug Bounty Hunter
Offensive security professional with 250+ vulnerabilities reported across 50+ organizations including Atlassian, Vimeo, and AT&T. Sharing research, tools, and field notes.