Challenge / Crypto

Protein Cookies

Protein Cookies is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator

EasyPublished 2024-04-24Sanitized local writeup

Scenario

Protein Cookies attack path

Protein Cookies is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator

Objective

Challenge walkthrough focused on Crypto evidence, validation, and reusable operator lessons.

Protein Cookies sanitized attack graph

Walkthrough flow

01

Extracted the provided Flask/Python 2 source bundle...

02

Found a custom login_info cookie containing...

03

Confirmed the secret length is fixed at 16 bytes in...

04

Observed that decoded cookie payloads are parsed with...

05

Implemented a self-contained SHA-512 length-extension...

Source coverage

High source coverage

Status: complete. This article is generated from 6 sanitized Markdown sources and keeps raw flags, credentials, keys, cookies, and reusable secrets out of the rendered blog.

100% coverage
Evidence verdict

High confidence: the page is reconstructed from a primary walkthrough plus multiple supporting notes or evidence sources. Treat the chain as source-backed, while still checking the listed source files for sensitive values.

  • Crypto/Protein-Cookies/writeup.md
  • htb-challenge/Crypto/Protein-Cookies/notes.md
  • htb-challenge/Crypto/Protein-Cookies/memory-summary.md
  • htb-challenge/Crypto/Protein-Cookies/hypothesis-board.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Crypto__Protein-Cookies__memory-summary.md.59ab7db0dd.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Crypto__Protein-Cookies__notes.md.07bb4267e8.md

Technical Walkthrough

Writeup

Challenge

  • Name: Protein-Cookies
  • Category: Crypto
  • Difficulty: Easy
  • Mode: hybrid

Summary

Protein Cookies is a Flask app with a custom authenticated cookie. The cookie MAC is built as sha512(secret + payload), which is vulnerable to SHA-512 length extension. Because the payload parser accepts duplicate keys and keeps the last value, a forged cookie can append a second isLoggedIn=True parameter and access /program.

Artifact Inventory

Reference analysis/artifact-inventory.json and summarize the relevant files or remote surface.

  • Remote service: http://<TARGET>:31106.
  • Local source bundle includes the Flask application, Dockerfile, static assets, and flag.pdf.
  • Relevant files:

- application/models.py: custom cookie creation and validation.

- application/util.py: guest cookie setup and login gate.

- application/blueprints/routes.py: /program sends flag.pdf behind verify_login.

Analysis

The app issues a guest cookie when login_info is absent. Its payload is:

text
username=guest&isLoggedIn=False

The signature is:

text
base64(sha512(secret + payload).hexdigest())

The source fixes the secret length at 16 bytes. Since SHA-512 is Merkle-Damgard based, the known digest of secret || payload can be extended to a valid digest for:

text
secret || payload || sha512_padding || &isLoggedIn=True

After base64-decoding the forged payload, Python 2 urlparse.parse_qs treats the appended key as a second isLoggedIn value. The app keeps v[-1], so the effective login state becomes True.

One implementation detail mattered during exploitation: sending the forged cookie through requests.Session(..., cookies=...) can leave the original guest cookie in the jar. The working solver sends a manually constructed HTTP cookie header containing only the forged value.

Solve

Run:

bash
python3 solve/solve.py

The script:

  1. Fetches a fresh guest cookie.
  2. Performs SHA-512 length extension with the known 16-byte secret length.
  3. Appends &isLoggedIn=True.
  4. Sends the forged cookie to /program.
  5. Saves the returned PDF to loot/program.pdf.
  6. Extracts the HTB flag from the PDF text.

Flag

Raw flag is stored in loot/flag.txt and intentionally not reproduced here.

Lessons

  • hash(secret || message) is not a safe MAC for Merkle-Damgard hashes.
  • Duplicate query parameters can turn a length-extension primitive into an auth bypass when the parser keeps the last value.
  • Cookie jar behavior can invalidate a correct exploit if the original cookie is sent alongside the forged one.

Source-Backed Dossier

The sections below are merged from companion Markdown notes for the same case. They are rendered after sanitization so the article stays precise without publishing raw flags, credentials, or target-specific secrets.

Notes

Scope

  • Challenge: Protein-Cookies
  • Category: Crypto
  • Difficulty: Easy
  • Mode: hybrid
  • Remote instance: none
  • Start time: 2026-06-10T09:42:26Z
  • Operator: harness
  • State file: challenge-state.json

Harness Status

  • Current phase: see challenge-state.json
  • Next allowed actions: see next-action.json
  • Raw flags and sensitive material stay in loot/ only. Do not paste them here.

Artifact Inventory

FileSizeSHA256TypeNotes
files/a12c7331-260f-4f58-aac8-c0c5933eda11.zip2268362<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 33 shown in artifact inventory JSON
files/extracted/crypto_protein_cookies/Dockerfile574<hash redacted>ASCII text
files/extracted/crypto_protein_cookies/build_docker.sh179<hash redacted>Bourne-Again shell script text executable, ASCII text
files/extracted/crypto_protein_cookies/challenge/application/__init__.py0<hash redacted>empty
files/extracted/crypto_protein_cookies/challenge/application/app.py636<hash redacted>Python script text executable, ASCII text, with CRLF line terminators
files/extracted/crypto_protein_cookies/challenge/application/blueprints/__init__.py0<hash redacted>empty
files/extracted/crypto_protein_cookies/challenge/application/blueprints/routes.py844<hash redacted>Python script text executable, ASCII text
files/extracted/crypto_protein_cookies/challenge/application/config.py209<hash redacted>ASCII text, with CRLF line terminators
files/extracted/crypto_protein_cookies/challenge/application/flag.pdf47147<hash redacted>PDF document, version 1.4, 1 pages
files/extracted/crypto_protein_cookies/challenge/application/models.py1269<hash redacted>Python script text executable, ASCII text
files/extracted/crypto_protein_cookies/challenge/application/static/favicon.png105798<hash redacted>PNG image data, 300 x 300, 8-bit/color RGBA, non-interlaced
files/extracted/crypto_protein_cookies/challenge/application/static/images/card.png1748<hash redacted>PNG image data, 48 x 48, 8-bit/color RGBA, non-interlaced
files/extracted/crypto_protein_cookies/challenge/application/static/images/logo.png189644<hash redacted>PNG image data, 900 x 308, 8-bit/color RGBA, non-interlaced
files/extracted/crypto_protein_cookies/challenge/application/static/images/promo.png948421<hash redacted>PNG image data, 1920 x 660, 8-bit/color RGBA, non-interlaced
files/extracted/crypto_protein_cookies/challenge/application/static/images/tr1.jpg268643<hash redacted>JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 490x490, components 3
files/extracted/crypto_protein_cookies/challenge/application/static/images/tr2.jpg258795<hash redacted>JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 490x490, components 3
files/extracted/crypto_protein_cookies/challenge/application/static/images/tr3.png446751<hash redacted>PNG image data, 490 x 490, 8-bit/color RGBA, non-interlaced
files/extracted/crypto_protein_cookies/challenge/application/static/js/main.js1081<hash redacted>ASCII text
files/extracted/crypto_protein_cookies/challenge/application/templates/index.html6631<hash redacted>HTML document text, Unicode text, UTF-8 text, with CRLF line terminators
files/extracted/crypto_protein_cookies/challenge/application/templates/login.html4818<hash redacted>HTML document text, Unicode text, UTF-8 text, with CRLF line terminators
files/extracted/crypto_protein_cookies/challenge/application/templates/register.html6044<hash redacted>HTML document text, Unicode text, UTF-8 text, with CRLF line terminators
files/extracted/crypto_protein_cookies/challenge/application/util.py768<hash redacted>Python script text executable, ASCII text
files/extracted/crypto_protein_cookies/challenge/requirements.txt5<hash redacted>ASCII text, with no line terminators
files/extracted/crypto_protein_cookies/challenge/run.py99<hash redacted>Python script text executable, ASCII text
files/extracted/crypto_protein_cookies/config/supervisord.conf270<hash redacted>ASCII text

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-10T09:42:26Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-10T09:42:26Zartifact inventoryanalysis/artifact-inventory.json25 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-10T09:43:43Zhypothesis recordedhypothesis-board.mdForge login_info with SHA-512 length extension to append a second isLoggedIn=True parameter, then request /program.MediumUse a locally tested SHA-512 length-extension implementation against a known secret, then apply it to a live guest cookie.
2026-06-10T09:43:43Zresearch recordanalysis/research/research-records.mdResearch tagged MATCHEDMediumValidate against current evidence
2026-06-10T09:43:44Zcheckpoint recordedanalysis/checkpoint-hypothesis_ready-20260610T094344016843Z-5cffec02.mdCheckpoint for <secret redacted>HighUse checkpoint to drive next decision
2026-06-10T09:43:44Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-10T09:43:44Zevaluatoranalysis/evaluator-20260610T094344126612Z-dfb0007e.mdProceedHighImplement local length-extension forge, fetch guest cookie, request /program, and extract flag from returned PDF.
2026-06-10T09:43:55Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-10T09:46:18Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-10T09:47:46Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • Remote target: http://<TARGET>:31106.
  • The application is Python 2 Flask and sets a guest login_info cookie on first request.
  • models.py implements a custom cookie MAC as sha512(secret + payload) with a 16-byte random prefix.
  • The cookie format is base64(payload).base64(sha512(secret + payload).hexdigest()).
  • The decoded payload is parsed with urlparse.parse_qs, and duplicate keys resolve to the last value through v[-1].
  • A SHA-512 length extension can append &isLoggedIn=True to the guest payload while preserving MAC validity.
  • The forged cookie must be sent in a manually constructed HTTP cookie header; using a requests.Session cookie jar can merge/prefer the original guest cookie.
  • The forged request to /program returns a PDF, and the HTB flag was extracted from that PDF into loot/flag.txt.

RAG / Advisory Memory

RAG output is advisory only. Record evaluated retrievals with:

bash
scripts/challenge_harness.py rag-record <workspace> --query "..." --tag MATCHED|PARTIAL|MISSING|<secret redacted>|GENERIC --validation "..."

Secrets/Flags

Raw flags and sensitive material stay in loot/ only. Use scripts/challenge_harness.py capture-flag to validate and record flag capture without printing the value.

Memory Summary

Metadata

  • Platform: HackTheBox Challenges
  • Category: Crypto
  • Challenge: Protein-Cookies
  • Difficulty: Easy
  • Source workspace: <local workspace>

Validated Solve Chain

Concepts only. Do not include raw flags, reusable credentials, tokens, cookies, private keys, or live secrets.

  1. Extracted the provided Flask/Python 2 source bundle and inventoried the app.
  2. Found a custom login_info cookie containing base64(payload).base64(sha512(secret + payload).hexdigest()).
  3. Confirmed the secret length is fixed at 16 bytes in source.
  4. Observed that decoded cookie payloads are parsed with parse_qs, and duplicate keys resolve to the last value.
  5. Implemented a self-contained SHA-512 length-extension forge to append a second logged-in parameter.
  6. Sent the forged cookie in a manually constructed HTTP cookie header to avoid session-cookie merging.
  7. Downloaded the protected PDF from /program and extracted the HTB flag.

Reusable Lessons

  • Prefix MACs using Merkle-Damgard hashes are length-extension vulnerable.
  • Parser behavior such as duplicate-key handling can be the second half of an auth bypass.
  • Raw cookie headers may be necessary when a client library maintains an original session cookie.
  • For PDF-backed flags, save the returned PDF under loot/ and extract text with pypdf.

Dead Ends

  • Login and registration APIs intentionally return errors and were not needed.
  • Using requests.Session with cookies={...} still sent or preferred the original guest cookie; a manually constructed HTTP cookie header was required.

Tool Quirks

  • Local hashpumpy, hlextend, and pymd5 modules were unavailable, so the solver implements SHA-512 compression directly.
  • pypdf was available and worked for extracting text from the downloaded PDF.

Evidence Paths

  • files/extracted/crypto_protein_cookies/challenge/application/models.py
  • files/extracted/crypto_protein_cookies/challenge/application/util.py
  • files/extracted/crypto_protein_cookies/challenge/application/blueprints/routes.py
  • analysis/source-audit.md
  • analysis/forge-debug.txt
  • solve/solve.py
  • loot/program.pdf
  • loot/flag.txt

Ingestion Decision

  • Proposed for LightRAG: yes
  • Requires user approval before ingestion: yes

Hypothesis Board

Keep no more than 3 active hypotheses on Easy/Medium and 5 on Hard unless the user explicitly asks for breadth.

RankPathEvidenceMissing ProofCheapest ValidationConfidenceStatus
1Forge login_info with SHA-512 length extension to append a second isLoggedIn=True parameter, then request /program.models.py uses sha512(secret + payload), source defines secret: <redacted> and validate_login returns the last parse_qs value for isLoggedIn.Use a locally tested SHA-512 length-extension implementation against a known secret, then apply it to a live guest cookie.MediumActive

Closed Branches

BranchEvidence TestedFailure OutputReason ClosedRevisit Condition

Memory Summary

approval_required: true

Sanitized Memory Summary

Metadata

  • Platform: HackTheBox Challenges
  • Category: Crypto
  • Challenge: Protein-Cookies
  • Difficulty: Easy
  • Source workspace: <local workspace>

Validated Solve Chain

Concepts only. Do not include raw flags, reusable credentials, tokens, cookies, private keys, or live secrets.

  1. Extracted the provided Flask/Python 2 source bundle and inventoried the app.
  2. Found a custom login_info cookie containing base64(payload).base64(sha512(secret + payload).hexdigest()).
  3. Confirmed the secret length is fixed at 16 bytes in source.
  4. Observed that decoded cookie payloads are parsed with parse_qs, and duplicate keys resolve to the last value.
  5. Implemented a self-contained SHA-512 length-extension forge to append a second logged-in parameter.
  6. Sent the forged cookie in a manually constructed HTTP cookie header to avoid session-cookie merging.
  7. Downloaded the protected PDF from /program and extracted the HTB flag.

Reusable Lessons

  • Prefix MACs using Merkle-Damgard hashes are length-extension vulnerable.
  • Parser behavior such as duplicate-key handling can be the second half of an auth bypass.
  • Raw cookie headers may be necessary when a client library maintains an original session cookie.
  • For PDF-backed flags, save the returned PDF under loot/ and extract text with pypdf.

Dead Ends

  • Login and registration APIs intentionally return errors and were not needed.
  • Using requests.Session with `cookies= <REDACTED>

Tool Quirks

  • Local hashpumpy, hlextend, and pymd5 modules were unavailable, so the solver implements SHA-512 compression directly.
  • pypdf was available and worked for extracting text from the downloaded PDF.

Evidence Paths

  • files/extracted/crypto_protein_cookies/challenge/application/models.py
  • files/extracted/crypto_protein_cookies/challenge/application/util.py
  • files/extracted/crypto_protein_cookies/challenge/application/blueprints/routes.py
  • analysis/source-audit.md
  • analysis/forge-debug.txt
  • solve/solve.py
  • loot/program.pdf
  • loot/flag.txt

Ingestion Decision

  • Proposed for LightRAG: yes
  • Requires user approval before ingestion: yes

Notes

Notes

Scope

  • Challenge: Protein-Cookies
  • Category: Crypto
  • Difficulty: Easy
  • Mode: hybrid
  • Remote instance: none
  • Start time: 2026-06-10T09:42:26Z
  • Operator: harness
  • State file: challenge-state.json

Harness Status

  • Current phase: see challenge-state.json
  • Next allowed actions: see next-action.json
  • Raw flags and sensitive material stay in loot/ only. Do not paste them here.

Artifact Inventory

FileSizeSHA256TypeNotes
files/a12c7331-260f-4f58-aac8-c0c5933eda11.zip2268362<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 33 shown in artifact inventory JSON
files/extracted/crypto_protein_cookies/Dockerfile574<hash redacted>ASCII text
files/extracted/crypto_protein_cookies/build_docker.sh179<hash redacted>Bourne-Again shell script text executable, ASCII text
files/extracted/crypto_protein_cookies/challenge/application/__init__.py0<hash redacted>empty
files/extracted/crypto_protein_cookies/challenge/application/app.py636<hash redacted>Python script text executable, ASCII text, with CRLF line terminators
files/extracted/crypto_protein_cookies/challenge/application/blueprints/__init__.py0<hash redacted>empty
files/extracted/crypto_protein_cookies/challenge/application/blueprints/routes.py844<hash redacted>Python script text executable, ASCII text
files/extracted/crypto_protein_cookies/challenge/application/config.py209<hash redacted>ASCII text, with CRLF line terminators
files/extracted/crypto_protein_cookies/challenge/application/flag.pdf47147<hash redacted>PDF document, version 1.4, 1 pages
files/extracted/crypto_protein_cookies/challenge/application/models.py1269<hash redacted>Python script text executable, ASCII text
files/extracted/crypto_protein_cookies/challenge/application/static/favicon.png105798<hash redacted>PNG image data, 300 x 300, 8-bit/color RGBA, non-interlaced
files/extracted/crypto_protein_cookies/challenge/application/static/images/card.png1748<hash redacted>PNG image data, 48 x 48, 8-bit/color RGBA, non-interlaced
files/extracted/crypto_protein_cookies/challenge/application/static/images/logo.png189644<hash redacted>PNG image data, 900 x 308, 8-bit/color RGBA, non-interlaced
files/extracted/crypto_protein_cookies/challenge/application/static/images/promo.png948421<hash redacted>PNG image data, 1920 x 660, 8-bit/color RGBA, non-interlaced
files/extracted/crypto_protein_cookies/challenge/application/static/images/tr1.jpg268643<hash redacted>JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 490x490, components 3
files/extracted/crypto_protein_cookies/challenge/application/static/images/tr2.jpg258795<hash redacted>JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 490x490, components 3
files/extracted/crypto_protein_cookies/challenge/application/static/images/tr3.png446751<hash redacted>PNG image data, 490 x 490, 8-bit/color RGBA, non-interlaced
files/extracted/crypto_protein_cookies/challenge/application/static/js/main.js1081<hash redacted>ASCII text
files/extracted/crypto_protein_cookies/challenge/application/templates/index.html6631<hash redacted>HTML document text, Unicode text, UTF-8 text, with CRLF line terminators
files/extracted/crypto_protein_cookies/challenge/application/templates/login.html4818<hash redacted>HTML document text, Unicode text, UTF-8 text, with CRLF line terminators
files/extracted/crypto_protein_cookies/challenge/application/templates/register.html6044<hash redacted>HTML document text, Unicode text, UTF-8 text, with CRLF line terminators
files/extracted/crypto_protein_cookies/challenge/application/util.py768<hash redacted>Python script text executable, ASCII text
files/extracted/crypto_protein_cookies/challenge/requirements.txt5<hash redacted>ASCII text, with no line terminators
files/extracted/crypto_protein_cookies/challenge/run.py99<hash redacted>Python script text executable, ASCII text
files/extracted/crypto_protein_cookies/config/supervisord.conf270<hash redacted>ASCII text

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-10T09:42:26Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-10T09:42:26Zartifact inventoryanalysis/artifact-inventory.json25 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-10T09: <REDACTED>, then request /program.MediumUse a locally tested SHA-512 length-extension implementation against a known secret, then apply it to a live guest cookie.
2026-06-10T09:43:43Zresearch recordanalysis/research/research-records.mdResearch tagged MATCHEDMediumValidate against current evidence
2026-06-10T09:43:44Zcheckpoint recordedanalysis/checkpoint-hypothesis_ready-20260610T094344016843Z-5cffec02.mdCheckpoint for <secret redacted>HighUse checkpoint to drive next decision
2026-06-10T09:43:44Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-10T09: <REDACTED>, fetch guest cookie, request /program, and extract flag from returned PDF.
2026-06-10T09:43:55Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-10T09: <REDACTED>
2026-06-10T09:47:46Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • Remote target: http://<TARGET>:31106.
  • The application is Python 2 Flask and sets a guest login_info cookie on first request.
  • models.py implements a custom cookie MAC as sha512(secret + payload) with a 16-byte random prefix.
  • The cookie format is base64(payload).base64(sha512(secret + payload).hexdigest()).
  • The decoded payload is parsed with urlparse.parse_qs, and duplicate keys resolve to the last value through v[-1].
  • A SHA-512 length extension can append &isLoggedIn=True to the guest payload while preserving MAC validity.
  • The forged cookie must be sent in a manually constructed HTTP cookie header; using a requests.Session cookie jar can merge/prefer the original guest cookie.
  • The forged request to /program returns a PDF, and the HTB flag was extracted from that PDF into loot/flag.txt.

RAG / Advisory Memory

RAG output is advisory only. Record evaluated retrievals with:

bash
scripts/challenge_harness.py rag-record <workspace> --query "..." --tag MATCHED|PARTIAL|MISSING|<secret redacted>|GENERIC --validation "..."

Secrets/Flags

Raw flags and sensitive material stay in loot/ only. Use scripts/challenge_harness.py capture-flag to validate and record flag capture without printing the value.

Technical analogy

How to remember this solve

Think of the challenge like a locked box where the lock is mathematical but slightly flawed. The goal is not to smash the box; it is to notice which part of the lock repeats, leaks, or trusts the wrong assumption.

For Protein Cookies, keep the mental model simple: identify the trusted assumption, prove it with the smallest safe test, then automate or repeat only the part that directly leads to the flag.