cryptofreemedium

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/
hash_length_extension_attackcookie_signing_bypassparse_qs_parameter_override

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 — serves flag.pdf, but protected by verify_login decorator which checks isLoggedIn == '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:

  1. ✅ Known H = SHA-512(secret || original_data) — from cookie
  2. ✅ Known secret length: len(secret) = 16 — from source code (os.urandom(16))
  3. ✅ Known original data: username=guest&isLoggedIn=False
  4. ✅ 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 existsparse_qs, &key=value override, 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