EXERCISE 2 — Auditing an app against the broken-defence catalogue ================================================================= THE APP: - Validates CSRF tokens on POST only. - Exposes GET /admin/delete?id= - Stores its CSRF value only in a cookie. - Team says "CORS protects the API." FLAW 1 -- State-changing GET endpoint (GET /admin/delete?id=). WHY IT FAILS: a GET that deletes data is forgeable by a bare on any page the victim loads. Token validation on POST is irrelevant -- the attacker uses the GET. SameSite=Lax also still sends the cookie on a top-level GET navigation. FIX: never change state on GET. Convert /admin/delete to POST (or DELETE), and validate the CSRF token on it. GET must be side-effect-free. FLAW 2 -- Token validation on POST ONLY (partial coverage). WHY IT FAILS: any state-changing route that isn't POST-with-validation is unprotected -- e.g. the GET above, or any PUT/DELETE/PATCH. One unguarded endpoint is enough for an attacker. FIX: validate the token on EVERY state-changing request (all of POST/PUT/DELETE/PATCH), ideally via global middleware that fails closed. FLAW 3 -- CSRF value stored ONLY in a cookie. WHY IT FAILS: a cookie is auto-sent by the browser on forged cross-site requests (ambient authority). If the server just reads the token from the cookie, the forged request carries it automatically -> check passes. This is NOT the synchronizer pattern; it provides no protection. (And a naive cookie==cookie/double-submit is defeatable by subdomain cookie injection, Chapter 6.) FIX: the token must travel in the request BODY or a custom HEADER and be compared to the session-stored token (synchronizer) or a SIGNED double-submit value. The non-cookie copy is the whole point. FLAW 4 -- "CORS protects us." WHY IT FAILS: CORS governs whether cross-origin JS may READ a response; it does NOT stop a forged request from being SENT and PROCESSED by the server (Chapters 2 & 4). A forged form POST executes regardless of CORS; the attacker never needs to read the reply. CORS is not a CSRF defence (and permissive CORS can make things worse). FIX: implement real CSRF defences -- SameSite + anti-CSRF tokens (+ Origin checks). Treat CORS as unrelated to CSRF. CORRECTED CONFIGURATION (summary): 1. No state changes on GET; /admin/delete becomes POST/DELETE with token validation. 2. Validate a signed CSRF token (body/header, NOT cookie-only) on EVERY state-changing route, via fail-closed middleware, constant-time compare. 3. Session cookie: HttpOnly + Secure + SameSite=Lax (Strict for admin). 4. Origin/Referer allowlist check on state-changing routes. 5. Re-auth on high-value admin actions (delete). 6. Keep XSS closed (it defeats all of the above).