WEBVERSE

Loading...

mediumCSRFPro

Billed

Billed is a small personal expense tracker. Anyone can sign up, but the account worth having belongs to the operator who reads the contact inbox. Get into the operations console.

The Scenario

Billed is a two-person fintech that helps people see where their money goes. Support is hands-on. Every message from the contact page lands in an internal queue, and an operator opens each one to triage it before replying. The account area got a security pass recently, but not every corner of it got the same attention.

Challenge Intel

Synopsis

The profile-update handler enforces a CSRF token on POST but applies updates from the query string on GET with no token. A stored-HTML contact message, opened by the operator bot, fires a same-origin GET that changes the operator's password, and the player signs in to the isolated /admin/ console for the flag.

What It Is

The chain is recon, a stored-HTML sink, and a method-scoped CSRF bug. Recon: /contact publishes the operator's email ([email protected]). That is the account to take over, and the only place the email appears. The CSRF bug: /account/settings renders a profile form that POSTs with a CSRF token, and the POST path validates it. The same update logic is also reachable by GET, where it reads the fields from the query string and never checks a token. So GET /account/settings?password=X changes the signed-in user's password with no token. The player finds this on their own account. Delivery: the contact form stores message bodies verbatim, and the operator queue at /admin/messages renders them with no escaping. An in-process headless Chromium operator bot, signed in as the operator, opens /admin/messages on a loop. A stored payload like <img src="/account/settings?password=hacked123"> makes the operator's browser issue a same-origin GET that carries the operator's session cookie (a same-origin request sends the cookie no matter the SameSite value, and HttpOnly does not stop a request the browser itself makes), so the operator's password becomes hacked123. Takeover: the player signs in at the isolated /admin/login with the operator email and hacked123. The flag is rendered only on /admin/dashboard, which the bot never opens. The session cookie is HttpOnly, so injected script cannot steal it, and a strict Content-Security-Policy (script-src 'none', connect-src 'none') stops any injected script from reading the dashboard or shipping the flag out, even though an interact sidecar is available as an egress channel. img-src 'self' still allows the same-origin GET-CSRF, so the intended exploit is unaffected. The only path to the flag is to take the operator's password and sign in. The fix is to validate the CSRF token on the action regardless of method, or to not accept state changes over GET at all.

Who It's For

Players who know stored XSS and want a realistic CSRF chain: recon, a token check that only covers one HTTP method, a same-origin forced request in a victim's session, and account takeover.

Skills You'll Practice

  • Recon: pulling the operator's email from a public page
  • Spotting a state-changing handler whose CSRF check covers only one HTTP method
  • Using a stored-HTML sink to fire a same-origin forged request in a victim's session
  • Understanding why SameSite and HttpOnly do not stop a same-origin CSRF

What You'll Gain

  • A concrete reason to bind CSRF validation to the action, not the HTTP method
  • Practice walking recon to stored sink to same-origin CSRF to account takeover

Ready to hack Billed?

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