WEBVERSE

Loading...

mediumSSTIPro

Folio Coffee Co

A specialty-coffee roaster lets subscribers personalize the printed card that ships in every monthly bag. The shortcode system runs through the storefront's template engine, server-side, with sandbox off by design — customers wanted conditionals.

The Scenario

Folio Coffee Co is a small direct-trade roaster in Asheville. Their

subscription is built around the printed card that ships in each bag —

a personalized note with the lot name, ship date, and brew suggestion.

The dev who wired up the customer-facing card editor wanted real

Twig syntax (`{% if partner_gift %}…{% endif %}`) so subscribers could

conditionally tailor their card. The sandbox extension would have

blocked the conditionals. He turned it off and shipped.

Challenge Intel

Synopsis

Subscribers can edit the Twig template that prints in their monthly shipment card. The preview endpoint renders that template server-side with the sandbox disabled — so a built-in Twig filter that accepts an arbitrary callback name (`map`) becomes an arbitrary-command primitive.

What It Is

Folio's /account/subscription/card page lets a logged-in subscriber edit the personalization template for their next shipment. The POST /account/subscription/card/preview endpoint takes the form body, feeds it through Twig (via createTemplate + render), and returns the rendered HTML inline. The template body is full Twig — not a sanitized shortcode subset — because the team wanted customers to use `{% if partner_gift %}…` conditionals. The Twig sandbox extension was explicitly disabled to let those conditionals through. With sandbox off, several built-in Twig filters become RCE primitives because they accept a callback name as a string argument and dispatch it as a PHP callable. The canonical Twig 3 payload is: {{ ['cat /flag.txt']|map('system')|join(' ') }} `|map('system')` invokes `system()` on each element of the array. PHP's `system` echoes the command's output to stdout (which Twig captures into the rendered template) and also returns the last line of output (which `|join(' ')` then appends). Either way, the flag lands in the rendered card preview body. Players who reach for the Twig 1.x/2.x `_self.env.registerUndefined FilterCallback` payload will find `_self` is now just the template name string — the env handle moved in Twig 3. The fix is to pivot to a built-in filter that takes a callback name (`map`, `filter`, `reduce`, `sort` — anything that delegates to a PHP callable).

Who It's For

Players comfortable with the Jinja2 SSTI primitive who want to see the same shape on Twig 3 — and learn the modern sandbox-off callback-filter primitive. Prereq: comfort with web forms, a working mental model of "template renders server-side", and cookie-based session auth.

Skills You'll Practice

  • Recognizing user-controlled Twig template bodies as an SSTI surface
  • Confirming the engine with the Twig dialect (`{{7*7}}` → `49`)
  • Reaching arbitrary command execution via a callback-accepting Twig filter (`|map('system')`)
  • Reading the flag back out of the rendered preview body
  • Knowing why the Twig 1.x/2.x `_self.env` payload no longer works on Twig 3

What You'll Gain

  • Mental model for Twig SSTI when the sandbox extension is disabled
  • Recognition that 'sandbox off' is a Twig-specific configuration choice
  • The Twig 3 callback-filter payload skeleton, memorized for next time

Ready to hack Folio Coffee Co?

Upgrade to Pro to unlock this challenge and the full library.