WEBVERSE

Loading...

hardSSTIPro

Pocketmic

A buy-me-a-coffee-style tip jar for indie podcasters. Listeners leave a few bucks and an optional thank-you note that gets emailed to the host. No response, no preview, but the thank-you redirect carries a tiny performance metric in its headers.

The Scenario

Pocketmic is a side project from two ex-audio-engineers who got tired of

watching their favorite indie shows burn out from no income. The pitch is

simple: a single page per show, a few preset tip amounts, an optional

note, and the host gets a thank-you summary email at the end of every

day. The team shipped the daily-digest fast in beta, then a regular

asked if hosts could see notes in real time. So they switched to

per-tip emails, synchronous, blocking the response, rendered through

the same Jinja2 layout the team uses for the daily summary.

Challenge Intel

Synopsis

The tip form's "note" field is rendered through Jinja2 into a thank-you email for the podcast host. The HTTP response never shows the rendered output, but the redirect carries a Server-Timing header with the real render duration, a side channel that turns blind SSTI into a binary timing oracle.

What It Is

POST /tip/<slug> takes a name, amount, and an optional note. The handler builds an HTML email body by rendering a Jinja2 template string that splices the note in raw, hands the result to a local aiosmtpd stub on 127.0.0.1:1025 synchronously, and only then issues a 302 to /tip/<slug>/thanks. The listener sees no rendered output. The render duration is real (gunicorn blocks on render → SMTP send → redirect) and the team surfaces it back to the client via the standard Server-Timing response header on the redirect: `Server-Timing: render;dur=<ms>, send;dur=<ms>, total;dur=<ms>`. They use this header for their own performance dashboards. That header turns the blind SSTI into an oracle. Payloads like `{% if open('/flag.txt').read()[0]=='W' %}{{ range(8000000)|last }}{% endif %}` make the render branch expensive only when the guess is right. The player walks the flag byte-by-byte by submitting one tip per byte and reading the dur= value from Server-Timing. No __subclasses__ chain needed, Jinja2's default globals don't include `open`, so the app intentionally exposes `open` to the template context (the team uses it elsewhere to attach signed receipt PDFs). The vulnerability is the template-string splice + that globals exposure.

Who It's For

Players comfortable with the basic Jinja2 SSTI primitive who want to learn the blind-with-timing-oracle variant. Familiarity with binary or per-character extraction over a side channel helps.

Skills You'll Practice

  • Spotting that user input is being rendered server-side even when no output is reflected
  • Using Server-Timing as a binary oracle when the response body is opaque
  • Writing a Jinja2 conditional whose branch cost depends on the guessed byte
  • Per-byte flag extraction across many small HTTP requests

What You'll Gain

  • Confidence that 'I can't see the output' isn't enough to call an SSTI safe
  • Pattern for converting any blind server-side bug into an oracle via a measurable side effect
  • A reusable timing-conditional payload skeleton

Ready to hack Pocketmic?

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

Pocketmic — WebVerse Pro SSTI