zwesty
Write-ups / Day 30 / Script
Day 30 · Fri, Oct 30 · Script

Spill the Beans

Prey slaughtered

Cumulative static script triage (30 languages in one zip); reading an encoded analysis-plan header and checking it against the body; ATT&CK mapping by a stated precedence

STTMClaude Homebase Both

Flags are shortened to their first 8 and last 4 characters because they're unique per team.

Challenge ID
script-analysis-day-30-beanshell-jsp-graph (+ /time_trial, /mastery)
Solved
2026-10-30: all three modes CORRECT on the first submit, both timed modes ranked, all by our automated morning solver (a scheduled script on our Kali VM) with no code change. Single CORRECT 09:01:33.3 ET, Time Trial 09:01:40.9, Mastery 09:01:51.8. 21.9 s from the Single start (09:01:29.970) to the Mastery CORRECT; 1 min 51.8 s from the 9 AM release. No answer was rejected
Speed boards
Time Trial 4th of 43 (0.901 s; fastest 0.570 s). Mastery 3rd of 41 (4.007 s; fastest 1.560 s). Captured 2026-11-01
Player
Zwesty (solo), with Claude Code (Opus 5.5) tooling: the automated solver solved all three modes on its own with our existing Script solver and plug-ins. Nobody worked this challenge by hand or in a Claude chat; this write-up was put together afterwards from the records

The challenge

"BeanShell source sits inside an inert JSP host container, and it's the last piece of a thirty-language puzzle. Analyze both, then rebuild the complete execution, host, and evidence graph, and find the edge that holds everything together."

Script is cumulative: one evidence.zip holds every day's members, and every earlier question is asked again. The Single asked 1,802 questions. Day 29's Single had 1,747, all still present, so Day 30 added 55: 11 questions on each of five new BeanShell members. - polyglot-beanshell-benign-utility.bsh, polyglot-beanshell-downloader.bsh, polyglot-beanshell-launcher.bsh, polyglot-beanshell-staged-chain.bsh (standalone BeanShell) - host-jsp-beanshell-beanshell.bsh (the BeanShell in a JSP host)

The 11 questions per member (answer id = member stem + suffix):

Suffix Question Format
-role "Classify the inert source role." enum of 11 (benign_utility, downloader, … advanced_syntax)
-attack "Map the decoded ordered behavior graph to one primary MITRE ATT&CK technique using this precedence: …" (a long rule list; the parent technique is also accepted when a language sub-technique is canonical) pattern: a technique ID, or none for the benign graph
-adapter "Recover the modeled language adapter." exact text
-campaign "Recover the exact synthetic campaign identifier." exact text
-url or -file-path "Recover the exact url indicator." / "Recover the exact file_path indicator." exact text
-stage-count "Recover the modeled stage count." integer
-steps "Recover the ordered behavior graph using > separators." tokens joined by >
-host-profile "Identify the script host or source container." ("… or webshell source container" for the hosted member) exact text
-obfuscation "Recover the ordered encoding profile." exact text
-stage-profile "Recover the multi-stage relationship profile." exact text
-next-language "Recover the next script language in the inert handoff." exact text

The Time Trial asked 1,898 questions on its own zip (65 of them on BeanShell members). The Mastery asked 1,898 per set over 14 sets (26,572 answers).

Reading the prompt: what it said we'd need

Nobody read the prompt on the day; the solver answered from the questions. This table is written afterwards.

Clue What it pointed to How the solver met it
"BeanShell source" A new language: Java-style BeanShell (.bsh) Four new polyglot-beanshell-* members, read like every polyglot-* member since Day 8: from their analysis-plan header
"inside an inert JSP host container" A hosted-source member, like the earlier host-* files host-jsp-beanshell-beanshell.bsh; its accepted host-profile was jsp_beanshell (the four standalone ones: standalone)
"the last piece of a thirty-language puzzle" Cumulative: every earlier language is still asked The 1,747 older questions were unchanged; the solver and plug-ins already covered them
"rebuild the complete execution, host, and evidence graph" The ordered behaviour graph (-steps, -stage-count), the host field, and the zip as a whole The same 11 fields every polyglot-* member has had; nothing new in shape
"find the edge that holds everything together" Possibly the handoff to the next language (-next-language) No new question type matched it. The question list had only the usual 11 fields per member
"Spill the Beans" (title) Beans → BeanShell Confirmed the language

Safety

Static analysis only. Nothing was executed. The solver reads each member as text: it decodes the HUNTRESS_ANALYSIS_V1 plan header and compares it with any plan found in the body. The header reader only interprets bounded literal expressions and never evaluates challenge code. For this write-up only the solver's report, the saved Game Master replies, and the solver source were read; the evidence zips were not opened. The URL and file-path answers are synthetic (example[.]invalid hosts, /tmp/huntress-beanshell/… paths) and are left out here.

What was in the files

Described from the question list and the accepted answers.

Single Solve: evidence.zip, 162,886 bytes, 1,802 questions. The five BeanShell members' accepted values:

Member role attack adapter steps stages obfuscation next-language
benign-utility benign_utility none local_parser decode_embedded>write_file>dispatch_process 3 hex_base64 powershell
downloader downloader T1105 http_client profile_host>decode_configuration>dispatch_language 3 fragmented_hex jscript
launcher launcher T1059 process_builder decode_configuration>return_configuration 2 plain_hex batch
staged-chain staged_chain T1059 operation_graph acquire_remote>dispatch_process 2 base64 vbscript
host-jsp-beanshell staged_chain T1059 operation_graph profile_host>decode_configuration>dispatch_language 3 fragmented_hex jscript

Campaigns are BEANSHELL- plus 8 hex digits. The role and ATT&CK answers that were accepted are the header's own, even where the step list read by the stated precedence would point elsewhere (the launcher's steps are decode_configuration>return_configuration, which the precedence calls benign; T1059 was accepted).

  • Time Trial: a fresh evidence.zip, 185,953 bytes, 1,898 questions with a different family mix, 65 of them on BeanShell members.
  • Mastery: one zip of 2,572,436 bytes, 14 sets of 1,898 questions, 65 BeanShell questions per set.

Single Solve: the automated solver

The solver ran our Script solver's core first, as one batch, then one batch from the plug-ins.

What the core does. It is our Day 8 solver, extended through Day 23. Every polyglot-* and host-* member is answered from its HUNTRESS_ANALYSIS_V1 plan: the header is decoded (HEX, B64, HB64, FRAG, Day 18's split form, Day 19's literal expressions, Day 23's LYR- stacks) into v=3|key=value|…, and the 11 answer suffixes map to plan keys. If any plan found in the body disagrees with the header, it stops instead of answering. The older languages (PowerShell, Batch, VBScript, Python, Bash, Node) go through our Day 7 solver. Plug-ins run after the core and may only fill ids it left open: here the JScript files, the WSF/HTA/SCT hosts, and some Batch wording answers.

All 55 BeanShell answers came from the core batch; none needed a plug-in.

Single Solve
started_at  09:01:29.970 ET   deadline none
answers     1,672 core (accepted, 130 remaining), then 130 plug-in answers
start        625 ms
artifact     418 ms   (162,886 bytes)
solve        889 ms
submit      2574 ms   (core batch 532 ms, plug-in batch 2039 ms)
TOTAL       4510 ms
result      CORRECT   flag 9cb5bb16…f7c2   (09:01:33.3 ET)

Time Trial

CORRECT on the first attempt, ranked, on a fresh variant.

Time Trial (ranked)
started_at  09:01:40.028 ET   deadline 09:02:30.028 (50 s clock)
answers     1,898
start        727 ms
artifact     305 ms   (185,953 bytes)
solve         90 ms
submit      1026 ms
TOTAL       2151 ms   (submit +1,652 ms after started_at)
result      CORRECT   flag 1d0ebe47…5cb7   (09:01:40.9 ET; 0.901 s on the server's stamps)

Mastery

CORRECT on the first attempt, ranked.

Mastery (ranked)
started_at  09:01:47.839 ET   deadline 09:11:27.839 (9 min 40 s clock)
answers     26,572 (14 sets × 1,898)
start       2270 ms
artifact     676 ms   (2,572,436 bytes)
solve       1334 ms
submit      2752 ms
TOTAL       7082 ms   (submit +6,227 ms after started_at)
result      CORRECT   flag defe4693…9576   (09:01:51.8 ET; 4.007 s on the server's stamps)

Why no change was needed

  • The core keys on the plan header's marker, not on the language. It finds HUNTRESS_ANALYSIS_V1 in the text whatever the comment syntax, so a new language whose header uses a known encoding is read like every language before it.
  • The new members brought no new field. The 55 new ids were the usual 11 suffixes. Despite the prompt's "graph" and "edge", the diff against Day 29 was 55 new ids and 0 removed.
  • The header and body agreed on every BeanShell member, so the fail-closed check didn't stop anything.
  • The older 1,747 were already solved by the same core and plug-ins on Day 29.

Time to solve

All three modes were solved by 09:01:51.8 ET, 1 min 51.8 s after release and 21.9 s after the Single started. Each mode went through on its first submit, and both timed modes were ranked.

Takeaways

  • A reader that keys on the data, not the syntax, survives new languages. BeanShell was language 30 and cost no code.
  • Flavour text isn't a new field. "Find the edge that holds everything together" sounded new; diffing the question ids against the day before (55 new, 0 gone, all the usual suffixes) showed it wasn't.
  • The header's own role and ATT&CK were accepted, even where the step list alone, read by the precedence, suggests something else. Trusting the header, with a body cross-check, was the right rule all event.
  • The lane ran last and still finished by 09:01:52, with both timed modes ranked (4th and 3rd).