Challenge / Misc

Pydome

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

MediumPublished 2024-12-18Sanitized local writeup

Scenario

Pydome attack path

Pydome 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 Misc evidence, validation, and reusable operator lessons.

Pydome sanitized attack graph

Walkthrough flow

01

Artifact review

02

Hypothesis

03

Validated solve path

04

Proof captured

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.

  • Misc/Pydome/writeup.md
  • htb-challenge/Misc/Pydome/notes.md
  • htb-challenge/Misc/Pydome/memory-summary.md
  • htb-challenge/Misc/Pydome/hypothesis-board.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Misc__Pydome__memory-summary.md.fb5f489735.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Misc__Pydome__notes.md.5ce3fa2bb8.md

Technical Walkthrough

Writeup

Challenge

  • Name: Pydome
  • Category: Misc
  • Difficulty: Medium
  • Mode: hybrid

Summary

Pydome is a Python input-filter challenge. The provided source hides the real ANACONDA environment seed, so direct seed recovery from the archive is not possible. The solve uses two service behaviors instead: SHA mismatch output leaks story.txt, and the non-integer conversion branch leaks deterministic random.randrange(100) outputs. A second leak plan with an initial pad-only attempt reveals the first hidden random value, allowing the first numeric-only secret and SHA target to be reconstructed.

Artifact Inventory

  • files/a12c7371-a13a-4bc2-abcc-0565c45c4eb9.zip

- Contains only misc_pydome/server.py.

- The archive does not include story.txt, flag.txt, Docker metadata, or the ANACONDA environment value.

  • Remote instance: <TARGET>:32587.
  • Main evidence:

- analysis/extracted/misc_pydome/server.py

- analysis/remote/story.txt

- analysis/remote/random-leak-n100-repeat-indexes.json

- analysis/remote/prng-plan-20260610T235310Z.json

- analysis/remote/observed-prng-solve.json

Analysis

Source review showed four important facts:

  1. The service requires exactly 100 comma-separated tokens.
  2. Valid numeric tokens are used as indexes into story.txt; invalid numeric tokens can pad the 100-token length.
  3. On SHA mismatch, the service prints the generated user_input, which turns story.txt into a remote oracle.
  4. The suffix check uses zip(forest, user_input[0x40:]), so an empty suffix passes once the SHA target is correct.

The hidden SHA target is generated from Python random seeded with ','.join(os.getenv("ANACONDA").split("A")). The seed is not present in the provided archive, so broad seed guessing was closed after source/story guesses, system dictionary, and rockyou candidates did not match the live randrange(100) fingerprint.

The decisive path was to exploit observable random behavior. A leak100 attempt with 100 single-character non-integer tokens prints 100 chosen indexes from random.randrange(100). A separate plan beginning with pad,leak100,leak100,... produced a later printed window that aligned with the first leak at offset 50. The alignment equation identifies the first hidden random value as 31. For a numeric-only first solve attempt, that value is the secret length, and the first 31 observed leak values are the secret bytes. Hashing those bytes gives the required SHA target.

Solve

Reproducible scripts:

  • solve/prng_oracle.py collects structured random leak windows.
  • solve/solve_observed_prng.py infers the first hidden secret length from the leak alignment, builds the SHA target from observed PRNG values, encodes the SHA hex digest using indexes from the leaked story.txt, and submits the final numeric-only payload.

Final run:

bash
cd <local workspace>
python3 scripts/challenge_exec.py Misc/Pydome -- python3 Misc/Pydome/solve/solve_observed_prng.py
python3 scripts/challenge_harness.py capture-flag Misc/Pydome --from loot/flag-candidate.txt

The solve transcript is saved at analysis/remote/observed-prng-solve.txt; raw flag material is stored only in loot/.

Flag

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

Lessons

  • Do not keep guessing a hidden environment seed when the challenge exposes deterministic runtime behavior.
  • A failed comparison that prints attacker-controlled reconstructed data can become a file oracle.
  • Truncated PRNG leaks do not always require full MT19937 recovery. Here, one alignment between two controlled leak plans was enough to infer the first hidden length and reconstruct the needed first-attempt secret.

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: Pydome
  • Category: Misc
  • Difficulty: Medium
  • Mode: hybrid
  • Remote instance: <TARGET>:32587
  • Start time: 2026-06-10T22:40: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/a12c7371-a13a-4bc2-abcc-0565c45c4eb9.zip1682<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 2 shown in artifact inventory JSON

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-10T22:40:26Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-10T22:40:48Zartifact inventoryanalysis/artifact-inventory.json1 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-10T22:42:51Zhypothesis recordedhypothesis-board.mdUse the failed SHA comparison as a story.txt oracle, map story characters to indexes, then craft a 100-token input whose first 64 generated characters equal the service's deterministic SHA target and whose suffix satisfies the weak forest zip check.MediumLeak story.txt chunks from the remote after harness gate, build char-to-index map, then determine whether the deterministic random SHA target can be made known or bypassed.
2026-06-10T22:42:51Zcheckpoint recordedanalysis/checkpoint-triage-20260610T224251744282Z-37116995.mdCheckpoint for TRIAGEHighUse checkpoint to drive next decision
2026-06-10T22:43:04Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-10T22:43:05Zlocal memory searchanalysis/research/local-memory-search-20260610T224305894888Z-72381fc8.mdFound 8 safe prior-note result(s)MediumRecord useful result or skip
2026-06-10T22:43:47ZRAG queryanalysis/rag/rag-query-20260610T224315062546Z-ff87ac1d.txtRAG helper exited 0; output savedMediumRecord retrieval tag and validation
2026-06-10T22:44:14ZRAG recordanalysis/rag-records.mdRetrieved memory tagged GENERICMediumValidate or reject with live evidence
2026-06-10T22:44:25Zresearch recordanalysis/research/research-records.mdResearch tagged GENERICMediumValidate against current evidence
2026-06-10T22:44:26Zevaluatoranalysis/evaluator-20260610T224426102030Z-2e678cd5.mdValidate firstHighgate_before_remote_oracle_probe
2026-06-10T22:45:10Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-10T22:45:19Zevaluatoranalysis/evaluator-20260610T224519341832Z-d0504787.mdProceedHighgate_before_remote_probe_then_run_solver
2026-06-10T22:52:41Zbranch closedhypothesis-board.mdAll tested empty-secret random-shift and story/source-derived seed hash candidates returned SHA mismatch or crashed before hash; no flag returned.HighRerank hypotheses
2026-06-10T23:00:02Zevaluatoranalysis/evaluator-20260610T230002507106Z-da861f2a.mdProceedHighrun_bounded_state_search_solver
2026-06-10T23:14:56Zbranch closedhypothesis-board.mdExact 100-output randrange(100) fingerprint did not match story substrings, obvious source/story clues, system dictionary, or rockyou candidates.HighRerank hypotheses
2026-06-10T23:14:56Zbranch closedhypothesis-board.mdSHA256(empty) failed at attempts 1-5 with pad-only prep; broader live state searches were too slow/noisy and produced no Level 4 or flag hit in saved transcripts.HighRerank hypotheses
2026-06-10T23:15:08Zcheckpoint recordedanalysis/checkpoint-analysis-20260610T231508873810Z-7d352e2b.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-10T23:52:09Zresearch taskanalysis/research/task-20260610T235209831479Z-80843d5b.mdResearch task created for advisory investigationMediumRecord research output
2026-06-10T23:52:09Zcheckpoint recordedanalysis/checkpoint-analysis-20260610T235209871385Z-8a681c90.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-10T23:52:48Zresearch recordanalysis/research/research-records.mdResearch tagged PARTIALMediumValidate against current evidence
2026-06-10T23:52:58Zhypothesis recordedhypothesis-board.mdUse the non-integer print path as a runtime PRNG oracle, collect deterministic leak windows with solve/prng_oracle.py, and attempt only source-backed state/seed recovery before submitting any final hash.MediumAnalyze whether collected windows provide enough constraints to recover the next secret SHA; if underconstrained, ask for a narrow ANACONDA/Docker/env hint before more live attempts.
2026-06-10T23:54:40Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-10T23:55:42Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • Extracted source is a single Python service: analysis/extracted/misc_pydome/server.py.
  • The service requires exactly 100 comma-separated tokens.
  • Valid numeric tokens index into remote story.txt; invalid large numeric tokens pad the token count without contributing output.
  • On SHA mismatch, the service prints the generated user_input, which makes story.txt remotely leakable. The leaked story is saved at analysis/remote/story.txt.
  • The leaked story is 1001 characters and contains all lowercase hexadecimal characters, so any SHA256 hex digest can be encoded as story indexes once the target digest is known.
  • The forest check is weak: all(zip(forest, user_input[0x40:])) means an empty suffix passes if the SHA target is correct.
  • The SHA target is generated from Python random seeded with ','.join(os.getenv("ANACONDA").split("A")); the ANACONDA value is not present in the provided files.
  • Live PRNG fingerprinting works. analysis/remote/random-leak-n100.txt captures 100 randrange(100) outputs from the non-integer print path.
  • Closed branches: obvious story/source seed guesses, system dictionary, rockyou, SHA256(empty) at attempts 1-5, and broad/noisy empty-state searches. See hypothesis-board.md and analysis/remote/*summary.json.
  • Solved path: use solve/prng_oracle.py to collect observable PRNG windows, align the pad,leak100,... window against the first leak, infer the first hidden random value as the numeric-only secret length, hash the first observed leak values for that length, and submit the digest with solve/solve_observed_prng.py.
  • Completion evidence: analysis/remote/observed-prng-solve.json shows flag_captured: true; the raw flag is stored only in 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: Misc
  • Challenge: Pydome
  • 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.

Reusable Lessons

-

Dead Ends

-

Tool Quirks

-

Evidence Paths

-

Ingestion Decision

  • Proposed for LightRAG: yes/no
  • 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
1Use the failed SHA comparison as a story.txt oracle, map story characters to indexes, then craft a 100-token input whose first 64 generated characters equal the service's deterministic SHA target and whose suffix satisfies the weak forest zip check.server.py builds user_input from story_data indexes, prints user_input on SHA mismatch, pads ignored numeric indexes, and uses all(zip(forest, suffix)) instead of enforcing full forest length.Leak story.txt chunks from the remote after harness gate, build char-to-index map, then determine whether the deterministic random SHA target can be made known or bypassed.MediumActive
2Use the non-integer print path as a runtime PRNG oracle, collect deterministic leak windows with solve/prng_oracle.py, and attempt only source-backed state/seed recovery before submitting any final hash.analysis/remote/random-leak-n100-repeat-indexes.json proves deterministic first-window leakage; analysis/remote/prng-plan-20260610T235011-reparsed.json captures later same-connection windows; public hint research points toward observable behavior rather than guessing ANACONDA.Analyze whether collected windows provide enough constraints to recover the next secret SHA; if underconstrained, ask for a narrow ANACONDA/Docker/env hint before more live attempts.MediumActive

Closed Branches

BranchEvidence TestedFailure OutputReason ClosedRevisit Condition
Known empty-secret and story/source seed guessesanalysis/remote/attempt-summary.jsonAll tested empty-secret random-shift and story/source-derived seed hash candidates returned SHA mismatch or crashed before hash; no flag returned.Only revisit if a new clue identifies the ANACONDA seed value or a reliable way to force zero-length secret.
Dictionary/story seed recoveryanalysis/remote/random-leak-n100.txtExact 100-output randrange(100) fingerprint did not match story substrings, obvious source/story clues, system dictionary, or rockyou candidates.Only revisit with a narrower seed hint, Dockerfile/env leak, or a stronger method to recover Python random seed/state from truncated outputs.
Empty-secret PRNG state searchanalysis/remote/empty-attempt-position-summary.jsonSHA256(empty) failed at attempts 1-5 with pad-only prep; broader live state searches were too slow/noisy and produced no Level 4 or flag hit in saved transcripts.Revisit only with a local model of the exact PRNG seed or a more efficient state advancement strategy that avoids large non-integer print output.

Memory Summary

approval_required: true

Sanitized Memory Summary

Metadata

  • Platform: HackTheBox Challenges
  • Category: Misc
  • Challenge: Pydome
  • 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.

Reusable Lessons

-

Dead Ends

-

Tool Quirks

-

Evidence Paths

-

Ingestion Decision

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

Notes

Notes

Scope

  • Challenge: Pydome
  • Category: Misc
  • Difficulty: Medium
  • Mode: hybrid
  • Remote instance: <TARGET>:32587
  • Start time: 2026-06-10T22:40: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/a12c7371-a13a-4bc2-abcc-0565c45c4eb9.zip1682<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 2 shown in artifact inventory JSON

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-10T22:40:26Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-10T22:40:48Zartifact inventoryanalysis/artifact-inventory.json1 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-10T22: <REDACTED>, map story characters to indexes, then craft a 100-token input whose first 64 generated characters equal the service's deterministic SHA target and whose suffix satisfies the weak forest zip check.MediumLeak story.txt chunks from the remote after harness gate, build char-to-index map, then determine whether the deterministic random SHA target can be made known or bypassed.
2026-06-10T22:42:51Zcheckpoint recordedanalysis/checkpoint-triage-20260610T224251744282Z-37116995.mdCheckpoint for TRIAGEHighUse checkpoint to drive next decision
2026-06-10T22:43:04Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-10T22:43:05Zlocal memory searchanalysis/research/local-memory-search-20260610T224305894888Z-72381fc8.mdFound 8 safe prior-note result(s)MediumRecord useful result or skip
2026-06-10T22:43:47ZRAG queryanalysis/rag/rag-query-20260610T224315062546Z-ff87ac1d.txtRAG helper exited 0; output savedMediumRecord retrieval tag and validation
2026-06-10T22:44:14ZRAG recordanalysis/rag-records.mdRetrieved memory tagged GENERICMediumValidate or reject with live evidence
2026-06-10T22:44:25Zresearch recordanalysis/research/research-records.mdResearch tagged GENERICMediumValidate against current evidence
2026-06-10T22:44:26Zevaluatoranalysis/evaluator-20260610T224426102030Z-2e678cd5.mdValidate firstHighgate_before_remote_oracle_probe
2026-06-10T22:45:10Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-10T22:45:19Zevaluatoranalysis/evaluator-20260610T224519341832Z-d0504787.mdProceedHighgate_before_remote_probe_then_run_solver
2026-06-10T22: <REDACTED>
2026-06-10T23:00:02Zevaluatoranalysis/evaluator-20260610T230002507106Z-da861f2a.mdProceedHighrun_bounded_state_search_solver
2026-06-10T23:14:56Zbranch closedhypothesis-board.mdExact 100-output randrange(100) fingerprint did not match story substrings, obvious source/story clues, system dictionary, or rockyou candidates.HighRerank hypotheses
2026-06-10T23: <REDACTED>
2026-06-10T23:15:08Zcheckpoint recordedanalysis/checkpoint-analysis-20260610T231508873810Z-7d352e2b.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-10T23:52:09Zresearch taskanalysis/research/task-20260610T235209831479Z-80843d5b.mdResearch task created for advisory investigationMediumRecord research output
2026-06-10T23:52:09Zcheckpoint recordedanalysis/checkpoint-analysis-20260610T235209871385Z-8a681c90.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-10T23:52:48Zresearch recordanalysis/research/research-records.mdResearch tagged PARTIALMediumValidate against current evidence
2026-06-10T23: <REDACTED>, collect deterministic leak windows with solve/prng_oracle.py, and attempt only source-backed state/seed recovery before submitting any final hash.MediumAnalyze whether collected windows provide enough constraints to recover the next secret SHA; if underconstrained, ask for a narrow ANACONDA/Docker/env hint before more live attempts.
2026-06-10T23: <REDACTED>
2026-06-10T23:55:42Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • Extracted source is a single Python service: analysis/extracted/misc_pydome/server.py.
  • The service requires exactly 100 comma-separated tokens.
  • Valid numeric tokens index into remote story.txt; invalid large numeric tokens pad the token count without contributing output.
  • On SHA mismatch, the service prints the generated user_input, which makes story.txt remotely leakable. The leaked story is saved at analysis/remote/story.txt.
  • The leaked story is 1001 characters and contains all lowercase hexadecimal characters, so any SHA256 hex digest can be encoded as story indexes once the target digest is known.
  • The forest check is weak: all(zip(forest, user_input[0x40:])) means an empty suffix passes if the SHA target is correct.
  • The SHA target is generated from Python random seeded with ','.join(os.getenv("ANACONDA").split("A")); the ANACONDA value is not present in the provided files.
  • Live PRNG fingerprinting works. analysis/remote/random-leak-n100.txt captures 100 randrange(100) outputs from the non-integer print path.
  • Closed branches: obvious story/source seed guesses, system dictionary, rockyou, SHA256(empty) at attempts 1-5, and broad/noisy empty-state searches. See hypothesis-board.md and analysis/remote/*summary.json.
  • Solved path: <REDACTED>, align the pad,leak100,... window against the first leak, infer the first hidden random value as the numeric-only secret length, hash the first observed leak values for that length, and submit the digest with solve/solve_observed_prng.py.
  • Completion evidence: <REDACTED>

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 timed puzzle booth. If the task is too fast or repetitive for a person, the intended move is usually to write a small helper that performs the simple action perfectly.

For Pydome, 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.