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.