Shambles
Shambles is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator
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.
Walkthrough flow
Audited the provided Python source and identified...
Confirmed the session material login path leaks...
Built a reusable CBC padding-oracle decryptor and...
Batched oracle probes against the remote interactive...
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.
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:
- Parse the bundled credentials from
files/extracted/crypto_shambles/creds.txt. - Login to the remote service and collect a valid encrypted JWT token.
- Request encrypted account data from option 2.
- Batch token-oracle probes over the same interactive socket to make CBC padding recovery practical.
- Recover the first encrypted JWT block intermediate value and derive the fixed IV using the known PyJWT HS256 header prefix.
- Decrypt the two account-data ciphertext blocks.
- Parse candidate
card_numberand integerbalancesplits. - 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
| File | Size | SHA256 | Type | Notes |
|---|---|---|---|---|
files/a12c7390-d411-48fe-ba62-1cd9a12c0b10.zip | 2548 | <hash redacted> | Zip archive data, at least v1.0 to extract, compression method=store | zip entries: 4 shown in artifact inventory JSON |
files/extracted/crypto_shambles/creds.txt | 66 | <hash redacted> | ASCII text | |
files/extracted/crypto_shambles/db.py | 1631 | <hash redacted> | Python script text executable, ASCII text, with CRLF line terminators | |
files/extracted/crypto_shambles/server.py | 3859 | <hash redacted> | Python script text executable, ASCII text |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-12T06:23:22Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-12T06:23:51Z | artifact inventory | analysis/artifact-inventory.json | 4 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-12T06:25:04Z | hypothesis recorded | hypothesis-board.md | CBC padding oracle on encrypted token login recovers encrypted account data; decrypted card number and balance drive exact withdrawal to zero | Medium | Build 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:17Z | research task | analysis/research/task-20260612T062517862047Z-878fc314.md | Research task created for advisory investigation | Medium | Record research output |
| 2026-06-12T06:25:19Z | local memory search | analysis/research/local-memory-search-20260612T062519403009Z-1213c7cc.md | Found 8 safe prior-note result(s) | Medium | Record useful result or skip |
| 2026-06-12T06:25:19Z | checkpoint recorded | analysis/checkpoint-triage-20260612T062519404747Z-23103a02.md | Checkpoint for TRIAGE | High | Use checkpoint to drive next decision |
| 2026-06-12T06:25:28Z | source audit | analysis/source-audit.md | Source audit recorded | High | Gate before exploit |
| 2026-06-12T06:26:02Z | research record | analysis/research/research-records.md | Research tagged GENERIC | Medium | Validate against current evidence |
| 2026-06-12T06:26:02Z | instrumentation plan | analysis/instrumentation-plan.md | Prove that the encrypted-token login path is a CBC padding oracle that can decrypt the account-data ciphertext and recover card_number+balance | High | Stop 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:55Z | local memory record | analysis/local-memory-records.md | Prior local notes reviewed as fallback/advisory context | Medium | Validate against current evidence |
| 2026-06-12T06:30:04Z | evaluator | analysis/evaluator-20260612T063004956288Z-2f1f8191.md | Proceed | High | Execute solve.py against <TARGET>:32105 through the harness wrapper and capture loot/flag-candidate.txt if returned |
| 2026-06-12T06:37:05Z | flag capture | loot/flag.txt | HTB-format flag captured; raw value kept in loot only | High | Write solution and run completion gate |
| 2026-06-12T06:38:01Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional sanitized memory summary approval |
Key Findings
-
RAG / Advisory Memory
RAG output is advisory only. Record evaluated retrievals with:
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.
- Audited the provided Python source and identified AES-CBC key/IV reuse across encrypted JWT tokens and encrypted account-data output.
- Confirmed the token login path leaks padding-valid versus padding-invalid behavior before JWT validation.
- Built a reusable CBC padding-oracle decryptor and validated it locally.
- Batched oracle probes against the remote interactive menu to recover the encrypted account-data plaintext.
- 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, andpwntoolswere missing from Crypto readiness, but this solve only required Python sockets and PyCryptodome.
Evidence Paths
analysis/source-audit.mdanalysis/local-padding-oracle-selftest.txtanalysis/remote/batched-oracle-probe.txtanalysis/remote/full-solver-run.txtsolve/solve.pyloot/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.
| Rank | Path | Evidence | Missing Proof | Cheapest Validation | Confidence | Status |
|---|---|---|---|---|---|---|
| 1 | CBC padding oracle on encrypted token login recovers encrypted account data; decrypted card number and balance drive exact withdrawal to zero | server.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 zero | Build a local oracle wrapper around server.py behavior, prove arbitrary ciphertext block decryption, then run against the live token oracle at <TARGET>:32105 | Medium | Active |
Closed Branches
| Branch | Evidence Tested | Failure Output | Reason Closed | Revisit 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.