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
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.
Walkthrough flow
Remote service exposes a Bash-like filtered prompt...
The wrapper validates the raw input against a regex...
The wrapper path is available through $0.
Bash substring expansion using only allowed...
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.
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 filteredcpprimitive.analysis/remote/substring-sh-escape-probe.txt: validated that${0:35:2}startssh.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:
^[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:
if [[ $command =~ $<secret redacted> ]]; then
eval "$command"
fiBecause $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:
cd <local workspace>
python3 solve/solve.pyThe solver:
- Connects to the remote TCP service.
- Sends
${0:35:2}to startsh. - Runs a focused
findcommand for flag-like files from the child shell. - Extracts an HTB-format flag candidate into
loot/flag-candidate.txt. - 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
evalsafe if shell expansion syntax remains available. $0can 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
| File | Size | SHA256 | Type | Notes |
|---|---|---|---|---|
| — | 0 | — | remote-only or no provided files | No local artifacts found under files/ |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-08T01:14:46Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-08T01:15:00Z | artifact inventory | analysis/artifact-inventory.json | 0 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-08T01:15:15Z | hypothesis recorded | hypothesis-board.md | Interact 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. | Medium | 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. |
| 2026-06-08T01:15:15Z | research skip | analysis/research/research-skip.md | Research intentionally skipped with recorded reason | Medium | Gate before exploit |
| 2026-06-08T01:16:26Z | checkpoint recorded | analysis/checkpoint-triage-20260608T011626638598Z-220205ce.md | Checkpoint for TRIAGE | High | Use checkpoint to drive next decision |
| 2026-06-08T01:17:12Z | local memory record | analysis/local-memory-records.md | Prior local notes reviewed as fallback/advisory context | Medium | Validate against current evidence |
| 2026-06-08T01:17:28Z | evaluator | analysis/evaluator-20260608T011728399568Z-19f12459.md | Proceed | High | Run array-glob bash escape probe, then build solve script if it yields an unfiltered shell. |
| 2026-06-08T01:26:52Z | flag capture | loot/flag.txt | HTB-format flag captured; raw value kept in loot only | High | Write solution and run completion gate |
| 2026-06-08T01:27:03Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional 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
cpto/dev/fd/1primitive and confirms the sink iseval "$command". ${0:35:2}uses only allowed characters and expands toshfrom the wrapper path.- The spawned
shshell accepts normal commands, confirmed byidandpwdin the escaped shell. solve/solve.pyreproduces the escape and stores the flag candidate underloot/.- Raw flag material is kept only in
loot/; remote transcripts are redacted.
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: 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.
- Remote service exposes a Bash-like filtered prompt and accepts only symbols, digits, whitespace, and a few shell metacharacters.
- The wrapper validates the raw input against a regex and then executes accepted input through
eval. - The wrapper path is available through
$0. - Bash substring expansion using only allowed characters can extract
shfrom$0. - Executing that substring starts an unfiltered child shell on the same socket.
- The child shell can run normal commands to locate and print the flag file.
- 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.evalafter 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
cpandapt. /flag.txtdid 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 --frompaths 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.txtanalysis/remote/benign-behavior-probe.txtanalysis/remote/wrapper-source-via-cp.txtanalysis/remote/substring-sh-escape-probe.txtanalysis/remote/find-cat-flag-via-sh.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 | Interact 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. | Medium | Active |
Closed Branches
| Branch | Evidence Tested | Failure Output | Reason Closed | Revisit 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.
- Remote service exposes a Bash-like filtered prompt and accepts only symbols, digits, whitespace, and a few shell metacharacters.
- The wrapper validates the raw input against a regex and then executes accepted input through
eval. - The wrapper path is available through
$0. - Bash substring expansion using only allowed characters can extract
shfrom$0. - Executing that substring starts an unfiltered child shell on the same socket.
- The child shell can run normal commands to locate and print the flag file.
- 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.evalafter 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
cpandapt. /flag.txtdid 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 --frompaths 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.txtanalysis/remote/benign-behavior-probe.txtanalysis/remote/wrapper-source-via-cp.txtanalysis/remote/substring-sh-escape-probe.txtanalysis/remote/find-cat-flag-via-sh.txtsolve/solve.pyloot/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
| File | Size | SHA256 | Type | Notes |
|---|---|---|---|---|
| — | 0 | — | remote-only or no provided files | No local artifacts found under files/ |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-08T01:14:46Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-08T01:15:00Z | artifact inventory | analysis/artifact-inventory.json | 0 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-08T01: <REDACTED>, then construct a command expression using only permitted symbols/numbers to read the flag. | Medium | 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. | |||
| 2026-06-08T01:15:15Z | research skip | analysis/research/research-skip.md | Research intentionally skipped with recorded reason | Medium | Gate before exploit |
| 2026-06-08T01:16:26Z | checkpoint recorded | analysis/checkpoint-triage-20260608T011626638598Z-220205ce.md | Checkpoint for TRIAGE | High | Use checkpoint to drive next decision |
| 2026-06-08T01:17:12Z | local memory record | analysis/local-memory-records.md | Prior local notes reviewed as fallback/advisory context | Medium | Validate against current evidence |
| 2026-06-08T01:17:28Z | evaluator | analysis/evaluator-20260608T011728399568Z-19f12459.md | Proceed | High | Run array-glob bash escape probe, then build solve script if it yields an unfiltered shell. |
| 2026-06-08T01: <REDACTED> | |||||
| 2026-06-08T01:27:03Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional 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
cpto/dev/fd/1primitive and confirms the sink iseval "$command". ${0:35:2}uses only allowed characters and expands toshfrom the wrapper path.- The spawned
shshell accepts normal commands, confirmed byidandpwdin the escaped shell. solve/solve.pyreproduces the escape and stores the flag candidate underloot/.- Raw flag material is kept only in
loot/; remote transcripts are redacted.
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.
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.