EXERCISE 3 — A JSON + custom-header fetch forgery, and why it fails =================================================================== THE ATTEMPT: fetch("https://target.com/api/transfer", { method: "POST", credentials: "include", headers: { "Content-Type": "application/json", "X-Requested-With": "XMLHttpRequest" }, body: JSON.stringify({ to: "attacker", amount: 5000 }) }); WHAT THE BROWSER DOES, STEP BY STEP: 1. This is NOT a "simple request": it has a custom header (X-Requested-With) AND a non-simple content type (application/json). Either one alone already makes it non-simple. 2. So before sending the real POST, the browser sends a CORS PREFLIGHT -- an OPTIONS request to target.com asking: may origin evil.com send a POST with Content-Type application/json and an X-Requested-With header? 3. target.com is NOT configured to allow evil.com (no Access-Control-Allow-Origin: evil.com, and it won't allow those headers/ methods for a foreign origin). 4. The preflight FAILS. The browser therefore NEVER sends the real POST. The transfer request never reaches the server. The forgery fails. WHY IT FAILS (root cause): - The attacker cannot get the browser to send a cross-origin request that uses JSON or custom headers WITHOUT passing a preflight, and they can't pass the preflight because they don't control target.com's CORS policy. - A plain HTML
also can't produce a JSON body or a custom header, so there's no non-fetch way around it either. WHAT IT WOULD (WRONGLY) TAKE TO BE "SAFE" RELYING ON THIS ALONE: - A server could try to rely on "we only accept application/json" or "we require the X-Requested-With header" as its ONLY CSRF defence, reasoning that forged simple requests can't set those. - This is FRAGILE and not recommended as a sole defence because: * It depends on the browser's preflight behaviour and on the server STRICTLY rejecting requests lacking the header / wrong content type. A lax server that also accepts urlencoded bodies reopens the hole. * Misconfigured/permissive CORS, or any endpoint that also accepts a simple content type, defeats it. * It protects nothing if combined with a separate XSS (same-origin script CAN set those headers). - Requiring a custom header IS a legitimate DEFENSE-IN-DEPTH layer (Chapter 8/9), but it must be enforced strictly and paired with real CSRF defences (tokens / SameSite), never trusted on its own. SUMMARY: The forgery fails because non-simple requests are preflighted and the attacker can't pass the preflight. But "the preflight saved me" is an accident of request shape, not a deliberate defence -- enforce tokens/ SameSite and treat custom-header requirements as one layer among several.