Challenge / Misc

Broken Shell

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

EasyPublished 2024-11-24Sanitized local writeup

Scenario

Broken Shell attack path

Broken Shell 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.

Broken Shell sanitized attack graph

Walkthrough flow

01

Remote service exposes a Bash-like filtered prompt...

02

The wrapper validates the raw input against a regex...

03

The wrapper path is available through $0.

04

Bash substring expansion using only allowed...

05

Executing that substring starts an unfiltered child...

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/Broken-Shell/writeup.md
  • htb-challenge/Misc/Broken-Shell/notes.md
  • htb-challenge/Misc/Broken-Shell/memory-summary.md
  • htb-challenge/Misc/Broken-Shell/hypothesis-board.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Misc__Broken-Shell__memory-summary.md.e177ac4058.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Misc__Broken-Shell__notes.md.57442f04b1.md

Technical Walkthrough

Writeup

Challenge

  • Name: Broken-Shell
  • Category: Misc
  • Difficulty: Easy
  • Mode: remote

Summary

Broken Shell is a remote shell-sandbox challenge. The service rejects commands unless every character matches a narrow allowlist, but the wrapper then passes the accepted string to eval.

The solve uses only allowed characters to expand a substring of $0 into sh. That starts a child shell outside the regex loop. Normal shell commands can then search for and print the flag file.

Artifact Inventory

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

  • analysis/remote/initial-socket-probe.txt: captured the banner, prompt, and allowed-character regex.
  • analysis/remote/benign-behavior-probe.txt: confirmed the backend is Bash-like and leaks the wrapper path in errors.
  • analysis/remote/wrapper-source-via-cp.txt: recovered the wrapper source using a filtered cp primitive.
  • analysis/remote/substring-sh-escape-probe.txt: validated that ${0:35:2} starts sh.
  • analysis/remote/find-cat-flag-via-sh.txt: redacted transcript of the successful flag search.
  • solve/solve.py: reproducible socket solver.

Analysis

The service banner states the allowed-character regex:

text
^[0-9${}/?"[:space:]:&>_=()]+$

That allowlist still permits Bash parameter expansion syntax. The benign probe showed invalid commands are evaluated from /home/restricted_user/broken_shell.sh, and copying that wrapper source confirmed the core logic is:

bash
if [[ $command =~ $<secret redacted> ]]; then
    eval "$command"
fi

Because $0 is the wrapper path, the substring expression ${0:35:2} expands to sh. The string uses only allowed characters, passes the regex, and eval executes it. The spawned child shell reads from the same socket without the wrapper's regex gate, which was validated by running id and pwd in analysis/remote/substring-sh-escape-probe.txt.

Solve

Run:

bash
cd <local workspace>
python3 solve/solve.py

The solver:

  1. Connects to the remote TCP service.
  2. Sends ${0:35:2} to start sh.
  3. Runs a focused find command for flag-like files from the child shell.
  4. Extracts an HTB-format flag candidate into loot/flag-candidate.txt.
  5. Saves only a redacted transcript under analysis/remote/.

The harness then captures the flag from loot/flag-candidate.txt.

Flag

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

Lessons

  • A character allowlist does not make eval safe if shell expansion syntax remains available.
  • $0 can become an unintended string oracle for command construction.
  • Copying the wrapper source through allowed shell primitives was useful, but the shortest final path was direct substring expansion into sh.
  • Keep remote transcripts redacted and store the raw flag only under loot/.

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: Broken-Shell
  • Category: Misc
  • Difficulty: Easy
  • Mode: remote
  • Remote instance: <TARGET>:30573
  • Start time: 2026-06-08T01:14:46Z
  • 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
0remote-only or no provided filesNo local artifacts found under files/

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-08T01:14:46Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-08T01:15:00Zartifact inventoryanalysis/artifact-inventory.json0 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-08T01:15:15Zhypothesis recordedhypothesis-board.mdInteract with the remote sandbox as a restricted shell; enumerate allowed characters and evaluator behavior, then construct a command expression using only permitted symbols/numbers to read the flag.MediumConnect once, capture banner/prompt, test harmless arithmetic/symbol-only inputs, and infer whether the backend is bash/sh/python/eval before attempting flag read.
2026-06-08T01:15:15Zresearch skipanalysis/research/research-skip.mdResearch intentionally skipped with recorded reasonMediumGate before exploit
2026-06-08T01:16:26Zcheckpoint recordedanalysis/checkpoint-triage-20260608T011626638598Z-220205ce.mdCheckpoint for TRIAGEHighUse checkpoint to drive next decision
2026-06-08T01:17:12Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-08T01:17:28Zevaluatoranalysis/evaluator-20260608T011728399568Z-19f12459.mdProceedHighRun array-glob bash escape probe, then build solve script if it yields an unfiltered shell.
2026-06-08T01:26:52Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-08T01:27:03Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • Remote service is a Bash-like filtered shell on <TARGET>:30573.
  • Banner exposes allowed characters: digits, $, braces, slash, question mark, double quote, whitespace, colon, ampersand, greater-than, underscore, equals, and parentheses.
  • Error output revealed the wrapper path as /home/restricted_user/broken_shell.sh.
  • The wrapper source was recovered through an allowed cp to /dev/fd/1 primitive and confirms the sink is eval "$command".
  • ${0:35:2} uses only allowed characters and expands to sh from the wrapper path.
  • The spawned sh shell accepts normal commands, confirmed by id and pwd in the escaped shell.
  • solve/solve.py reproduces the escape and stores the flag candidate under loot/.
  • Raw flag material is kept only in loot/; remote transcripts are 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.

Memory Summary

Metadata

  • Platform: HackTheBox Challenges
  • Category: Misc
  • Challenge: Broken-Shell
  • 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. Remote service exposes a Bash-like filtered prompt and accepts only symbols, digits, whitespace, and a few shell metacharacters.
  2. The wrapper validates the raw input against a regex and then executes accepted input through eval.
  3. The wrapper path is available through $0.
  4. Bash substring expansion using only allowed characters can extract sh from $0.
  5. Executing that substring starts an unfiltered child shell on the same socket.
  6. The child shell can run normal commands to locate and print the flag file.
  7. The solve script extracts the flag candidate and stores it in loot/ for harness capture.

Reusable Lessons

  • Shell allowlists fail when expansion syntax can synthesize blocked words after validation.
  • $0, positional parameters, and shell special variables are useful string sources in heavily restricted shells.
  • eval after regex validation should be treated as the primary sink.
  • Save transcripts redacted when a command may print a flag.

Dead Ends

  • Broad glob command execution selected unintended binaries, including cp and apt.
  • /flag.txt did not exist at the root path.
  • Trying to execute a glob-selected Bash binary through array first-match did not produce an unfiltered shell because the first match was not Bash.

Tool Quirks

  • The harness expects capture-flag --from paths relative to the workspace.
  • The wrapper disables terminal echo, so socket transcripts show commands only when the child shell echoes them.

Evidence Paths

  • analysis/remote/initial-socket-probe.txt
  • analysis/remote/benign-behavior-probe.txt
  • analysis/remote/wrapper-source-via-cp.txt
  • analysis/remote/substring-sh-escape-probe.txt
  • analysis/remote/find-cat-flag-via-sh.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
1Interact with the remote sandbox as a restricted shell; enumerate allowed characters and evaluator behavior, then construct a command expression using only permitted symbols/numbers to read the flag.Challenge scenario says only specific symbols and numbers are allowed; remote-only Misc service on TCP <TARGET>:30573.Connect once, capture banner/prompt, test harmless arithmetic/symbol-only inputs, and infer whether the backend is bash/sh/python/eval before attempting flag read.MediumActive

Closed Branches

BranchEvidence TestedFailure OutputReason ClosedRevisit Condition

Memory Summary

approval_required: true

Sanitized Memory Summary

Metadata

  • Platform: HackTheBox Challenges
  • Category: Misc
  • Challenge: Broken-Shell
  • 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. Remote service exposes a Bash-like filtered prompt and accepts only symbols, digits, whitespace, and a few shell metacharacters.
  2. The wrapper validates the raw input against a regex and then executes accepted input through eval.
  3. The wrapper path is available through $0.
  4. Bash substring expansion using only allowed characters can extract sh from $0.
  5. Executing that substring starts an unfiltered child shell on the same socket.
  6. The child shell can run normal commands to locate and print the flag file.
  7. The solve script extracts the flag candidate and stores it in loot/ for harness capture.

Reusable Lessons

  • Shell allowlists fail when expansion syntax can synthesize blocked words after validation.
  • $0, positional parameters, and shell special variables are useful string sources in heavily restricted shells.
  • eval after regex validation should be treated as the primary sink.
  • Save transcripts redacted when a command may print a flag.

Dead Ends

  • Broad glob command execution selected unintended binaries, including cp and apt.
  • /flag.txt did not exist at the root path.
  • Trying to execute a glob-selected Bash binary through array first-match did not produce an unfiltered shell because the first match was not Bash.

Tool Quirks

  • The harness expects capture-flag --from paths relative to the workspace.
  • The wrapper disables terminal echo, so socket transcripts show commands only when the child shell echoes them.

Evidence Paths

  • analysis/remote/initial-socket-probe.txt
  • analysis/remote/benign-behavior-probe.txt
  • analysis/remote/wrapper-source-via-cp.txt
  • analysis/remote/substring-sh-escape-probe.txt
  • analysis/remote/find-cat-flag-via-sh.txt
  • solve/solve.py
  • loot/flag.txt

Ingestion Decision

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

Notes

Notes

Scope

  • Challenge: Broken-Shell
  • Category: Misc
  • Difficulty: Easy
  • Mode: remote
  • Remote instance: <TARGET>:30573
  • Start time: 2026-06-08T01:14:46Z
  • 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
0remote-only or no provided filesNo local artifacts found under files/

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-08T01:14:46Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-08T01:15:00Zartifact inventoryanalysis/artifact-inventory.json0 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-08T01: <REDACTED>, then construct a command expression using only permitted symbols/numbers to read the flag.MediumConnect once, capture banner/prompt, test harmless arithmetic/symbol-only inputs, and infer whether the backend is bash/sh/python/eval before attempting flag read.
2026-06-08T01:15:15Zresearch skipanalysis/research/research-skip.mdResearch intentionally skipped with recorded reasonMediumGate before exploit
2026-06-08T01:16:26Zcheckpoint recordedanalysis/checkpoint-triage-20260608T011626638598Z-220205ce.mdCheckpoint for TRIAGEHighUse checkpoint to drive next decision
2026-06-08T01:17:12Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-08T01:17:28Zevaluatoranalysis/evaluator-20260608T011728399568Z-19f12459.mdProceedHighRun array-glob bash escape probe, then build solve script if it yields an unfiltered shell.
2026-06-08T01: <REDACTED>
2026-06-08T01:27:03Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • Remote service is a Bash-like filtered shell on <TARGET>:30573.
  • Banner exposes allowed characters: digits, $, braces, slash, question mark, double quote, whitespace, colon, ampersand, greater-than, underscore, equals, and parentheses.
  • Error output revealed the wrapper path as /home/restricted_user/broken_shell.sh.
  • The wrapper source was recovered through an allowed cp to /dev/fd/1 primitive and confirms the sink is eval "$command".
  • ${0:35:2} uses only allowed characters and expands to sh from the wrapper path.
  • The spawned sh shell accepts normal commands, confirmed by id and pwd in the escaped shell.
  • solve/solve.py reproduces the escape and stores the flag candidate under loot/.
  • Raw flag material is kept only in loot/; remote transcripts are 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 Broken Shell, 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.