EXERCISE 3 — How "require application/json" CSRF protection still fails ======================================================================= THE INTENDED DEFENCE: The API rejects state-changing requests unless Content-Type is application/json. Reasoning: an HTML
can't send a JSON body, and a cross-origin fetch with application/json triggers a CORS preflight the attacker can't pass (Chapter 2). So forged classic requests can't comply. TWO WAYS IT STILL FAILS: FAILURE 1 -- a permissive body parser that ALSO accepts form encoding. - If the server (or a global middleware) also parses application/x-www-form-urlencoded or text/plain and routes it to the same handler, then an attacker can submit a normal HTML with one of those SIMPLE content types -- which sends cross-site with NO preflight and WITH the cookie. The "must be JSON" intent is bypassed because the endpoint quietly accepts non-JSON too. - Even sneakier: a form can set enctype="text/plain" and craft a body that looks like JSON (e.g. {"amount":5000, "ignore":"=x"}) -- if the server leniently JSON-parses text/plain, it slips through. - FIX: STRICTLY reject anything that isn't exactly application/json for state-changing methods (return 415), and don't register parsers for other content types on those routes. FAILURE 2 -- cookie auth with lax enforcement / not the sole need. - If the endpoint authenticates via a COOKIE, the cookie is still auto-sent. The JSON requirement is the ONLY thing standing between a forged request and execution -- so any gap in enforcement (a route that skips the check, a misconfigured permissive CORS that allows the origin, an older browser quirk) means a forged state change succeeds. - Relying on request shape alone, under cookie auth, is brittle. It is a LAYER, not a complete defence. - FIX: under cookie auth, never rely on JSON-only by itself. CORRECT LAYERED CONFIG FOR A COOKIE-AUTHENTICATED SPA: 1. Session cookie: HttpOnly + Secure + SameSite=Lax (Strict for high-value). 2. Anti-CSRF token via DOUBLE-SUBMIT: server sets XSRF-TOKEN cookie; the SPA (Angular/Axios) echoes it as X-XSRF-TOKEN header; server validates the signed token on every state-changing request. (Use a maintained library.) 3. Enforce application/json strictly (415 otherwise) as an extra layer -- and because a custom header (X-XSRF-TOKEN) is required, a classic form forgery already can't comply. 4. Origin/Referer allowlist check on state-changing routes. 5. Never change state on GET. 6. Re-authentication on sensitive actions (change email/password, payments). WHY THIS IS SOUND: Each layer fails for a DIFFERENT reason (Chapter 8): SameSite (browser rules), token (page-read barrier + preflight), Origin (browser-set header), re-auth (user secret). The JSON requirement is a helpful bonus, not the load-bearing wall. And keep XSS closed -- it defeats all of the above.