Foundry Comics
Brooklyn indie micro-press with an open submissions portal. Would-be contributors paste a pitch into the submit-preview tool and see how the editor will read it — folded into the house style and rendered server-side. The portal kept the previous owner's stack alongside the rebuild, so different sessions hit different code paths.
The Scenario
Foundry Comics is a three-person riso-print outfit in Gowanus that
publishes single-issue runs by emerging cartoonists. In 2022 they
picked up a defunct submission portal off another small press that
was winding down — quicker than rebuilding from scratch. The portal
works, but the migration was never finished: returning creators get
routed through whichever bucket the cookie says they belong to,
and the two buckets were never fully merged.
Challenge Intel
Synopsis
The /submit/preview tool renders the submitter's pitch through one of two templating engines — Jinja2 (Python/Flask) or Twig (PHP) — chosen by a cookie bucket assigned on first visit. Fingerprint the engine, then fire the engine-specific payload.
What It Is
Foundry Comics' submission preview folds the contributor's pitch body into a template before rendering. The submission portal is polyglot under the hood — an Apache/PHP/Twig backend serves some sessions, a Python/Flask/Jinja2 backend serves others. The choice is locked at first visit via the `fc_portal_v` cookie (set server-side, two buckets), and an nginx reverse proxy routes /submit/preview to whichever backend the bucket points at. Both backends concatenate the pitch body into a template body before rendering. Both are unsandboxed. Solve path: probe with `{{7*7}}` to confirm template echo (both engines render to `49`), then fingerprint by trying a Python-style string subscript like `{{ "abc"[0] }}` — Jinja2 returns `a`, Twig returns empty, because Twig doesn't index strings by integer key. Once the engine is identified, fire: Twig: {{ "cat /flag.txt"|passthru }} (passthru is a legacy filter the acquired SubsDirect codebase shipped — exposes shell_exec to template authors) Jinja2: {{ self.__init__.__globals__.__builtins__.__import__("os") .popen("cat /flag.txt").read() }} Clearing cookies re-rolls the bucket; both surfaces must be exercised across sessions to solve in both buckets.
Who It's For
Players who have done one unsandboxed Jinja2 SSTI and want to learn engine fingerprinting and the Twig RCE primitive. Useful prep for polyglot-stack assessments where the engine isn't advertised in the response.
Skills You'll Practice
- Reading response differential to detect a server-side templating layer
- Distinguishing Twig from Jinja2 by string-subscript semantics
- Recognising and chaining a legacy custom Twig filter (`passthru`) into RCE
- Jinja2 `__init__.__globals__` → `os.popen` RCE primitive
- Recognising cookie-driven engine routing and re-rolling buckets
What You'll Gain
- Engine-fingerprint cheat sheet you can re-use against real polyglot stacks
- Both Twig and Jinja2 SSTI RCE payloads memorised
- Mental model for why a feature-flag cookie can hide a whole separate runtime
Ready to hack Foundry Comics?
Upgrade to Pro to unlock this challenge and the full library.