Challenge / Crypto

Shambles

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

MediumPublished 2024-05-18Sanitized local writeup

Scenario

Shambles attack path

Shambles 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.

Shambles sanitized attack graph

Walkthrough flow

01

Audited the provided Python source and identified...

02

Confirmed the session material login path leaks...

03

Built a reusable CBC padding-oracle decryptor and...

04

Batched oracle probes against the remote interactive...

05

Parsed the recovered card number and integer balance,...

Source coverage

High source coverage

Status: complete. This article is generated from 4 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/Shambles/writeup.md
  • htb-challenge/Crypto/Shambles/notes.md
  • htb-challenge/Crypto/Shambles/memory-summary.md
  • htb-challenge/Crypto/Shambles/hypothesis-board.md

Technical Walkthrough

Writeup

Challenge

  • Name: Shambles
  • Category: Crypto
  • Difficulty: Medium
  • Mode: hybrid

Summary

Shambles is a source-backed remote crypto challenge. The provided Python service encrypts both JWT login tokens and user account data with the same AES-CBC key and IV. The encrypted-token login path leaks whether PKCS#7 unpadding succeeded before JWT validation, which creates a CBC padding oracle.

The solve uses the oracle to decrypt the account-data ciphertext, recover the card number and integer balance for the authenticated account, then withdraw exactly that balance to trigger the flag path.

Artifact Inventory

  • files/a12c7390-d411-48fe-ba62-1cd9a12c0b10.zip: original HTB archive.
  • files/extracted/crypto_shambles/server.py: vulnerable menu service.
  • files/extracted/crypto_shambles/db.py: SQLite helper with the exact zero-balance flag condition.
  • files/extracted/crypto_shambles/creds.txt: bundled target credentials used by the solver without copying them into this writeup.
  • analysis/artifact-inventory.json: hashes, file sizes, and file types.

Analysis

The relevant source facts are recorded in analysis/source-audit.md.

server.py generates a random AES-CBC KEY and IV once, then reuses them for encrypted JWT tokens and encrypted account data. In the token login branch, attacker-controlled ciphertext is decrypted and unpadded before JWT validation. Invalid padding prints Decryption error!, while padding-valid non-JWT plaintext reaches JWT validation and prints Validation error!. That distinction is enough to build a padding oracle.

After authenticating with the bundled account, option 2 returns encrypted account data. The plaintext format is card_number + str(balance). Option 3 compares the submitted card number with the stored card number and returns the flag if balance - amount == 0.

The padding-oracle implementation was validated locally in analysis/local-padding-oracle-selftest.txt, and the live service oracle behavior was confirmed in analysis/remote/batched-oracle-probe.txt.

Solve

The reproducible solver is solve/solve.py.

High-level flow:

  1. Parse the bundled credentials from files/extracted/crypto_shambles/creds.txt.
  2. Login to the remote service and collect a valid encrypted JWT token.
  3. Request encrypted account data from option 2.
  4. Batch token-oracle probes over the same interactive socket to make CBC padding recovery practical.
  5. Recover the first encrypted JWT block intermediate value and derive the fixed IV using the known PyJWT HS256 header prefix.
  6. Decrypt the two account-data ciphertext blocks.
  7. Parse candidate card_number and integer balance splits.
  8. Withdraw the exact recovered balance and store the returned flag candidate under loot/.

The successful live run is recorded in analysis/remote/full-solver-run.txt. It recovered the account payload and wrote the flag candidate to loot/flag-candidate.txt, then the harness captured it to loot/flag.txt.

Flag

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

Lessons

  • A padding oracle can decrypt ciphertext from a different feature when the same CBC key and IV are reused across service functions.
  • For interactive remotes, batching oracle probes avoids one network round trip per guess and can turn an otherwise impractical padding oracle into a fast solve.
  • Research/memory was only advisory here; the decisive evidence came from the provided source, local instrumentation, and live response validation.

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: Shambles
  • Category: Crypto
  • Difficulty: Medium
  • Mode: hybrid
  • Remote instance: none
  • Start time: 2026-06-12T06:23:22Z
  • 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/a12c7390-d411-48fe-ba62-1cd9a12c0b10.zip2548<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 4 shown in artifact inventory JSON
files/extracted/crypto_shambles/creds.txt66<hash redacted>ASCII text
files/extracted/crypto_shambles/db.py1631<hash redacted>Python script text executable, ASCII text, with CRLF line terminators
files/extracted/crypto_shambles/server.py3859<hash redacted>Python script text executable, ASCII text

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-12T06:23:22Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-12T06:23:51Zartifact inventoryanalysis/artifact-inventory.json4 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-12T06:25:04Zhypothesis recordedhypothesis-board.mdCBC padding oracle on encrypted token login recovers encrypted account data; decrypted card number and balance drive exact withdrawal to zeroMediumBuild a local oracle wrapper around server.py behavior, prove arbitrary ciphertext block decryption, then run against the live token oracle at <TARGET>:32105
2026-06-12T06:25:17Zresearch taskanalysis/research/task-20260612T062517862047Z-878fc314.mdResearch task created for advisory investigationMediumRecord research output
2026-06-12T06:25:19Zlocal memory searchanalysis/research/local-memory-search-20260612T062519403009Z-1213c7cc.mdFound 8 safe prior-note result(s)MediumRecord useful result or skip
2026-06-12T06:25:19Zcheckpoint recordedanalysis/checkpoint-triage-20260612T062519404747Z-23103a02.mdCheckpoint for TRIAGEHighUse checkpoint to drive next decision
2026-06-12T06:25:28Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-12T06:26:02Zresearch recordanalysis/research/research-records.mdResearch tagged GENERICMediumValidate against current evidence
2026-06-12T06:26:02Zinstrumentation plananalysis/instrumentation-plan.mdProve that the encrypted-token login path is a CBC padding oracle that can decrypt the account-data ciphertext and recover card_number+balanceHighStop after repeated oracle ambiguity, no token/data retrieval, nonparseable decrypted account payload, or withdrawal not reaching exact zero; record failure instead of blind probing
2026-06-12T06:29:55Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-12T06:30:04Zevaluatoranalysis/evaluator-20260612T063004956288Z-2f1f8191.mdProceedHighExecute solve.py against <TARGET>:32105 through the harness wrapper and capture loot/flag-candidate.txt if returned
2026-06-12T06:37:05Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-12T06:38:01Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

-

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: Shambles
  • Difficulty: Medium
  • Source workspace: <local workspace>

Validated Solve Chain

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

  1. Audited the provided Python source and identified AES-CBC key/IV reuse across encrypted JWT tokens and encrypted account-data output.
  2. Confirmed the token login path leaks padding-valid versus padding-invalid behavior before JWT validation.
  3. Built a reusable CBC padding-oracle decryptor and validated it locally.
  4. Batched oracle probes against the remote interactive menu to recover the encrypted account-data plaintext.
  5. Parsed the recovered card number and integer balance, then withdrew the exact balance to trigger the flag path.

Reusable Lessons

  • CBC padding-oracle solves can often decrypt unrelated ciphertexts when one service reuses the same key/IV across multiple features.
  • For menu-driven services, pipelining or batching oracle probes over a persistent connection can be the difference between a practical and impractical remote exploit.
  • Treat local memory as advisory; direct source audit plus local instrumentation should drive Medium crypto decisions.

Dead Ends

  • A one-query-per-round-trip oracle implementation was too slow for the remote service and was replaced with batched probing.

Tool Quirks

  • PyJWT was not installed locally, but the solver did not need it; the IV derivation only required the known first 16 bytes of the standard JWT header.
  • sympy, z3, sage, and pwntools were missing from Crypto readiness, but this solve only required Python sockets and PyCryptodome.

Evidence Paths

  • analysis/source-audit.md
  • analysis/local-padding-oracle-selftest.txt
  • analysis/remote/batched-oracle-probe.txt
  • analysis/remote/full-solver-run.txt
  • solve/solve.py
  • 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
1CBC padding oracle on encrypted token login recovers encrypted account data; decrypted card number and balance drive exact withdrawal to zeroserver.py uses one AES-CBC KEY/IV for JWT tokens and user data; token login distinguishes unpad failures from JWT validation failures; option 3 reveals flag when the authenticated user balance reaches zeroBuild a local oracle wrapper around server.py behavior, prove arbitrary ciphertext block decryption, then run against the live token oracle at <TARGET>:32105MediumActive

Closed Branches

BranchEvidence TestedFailure OutputReason ClosedRevisit Condition

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 Shambles, 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.