EXERCISE 3 — Why "SameSite=Lax = CSRF-proof" is wrong ====================================================== THE CLAIM: "I set SameSite=Lax, so my app is CSRF-proof." THE GAPS that leave it still attackable: GAP 1 -- State-changing GET endpoints. - Lax DOES send the session cookie on TOP-LEVEL GET NAVIGATIONS (the user's window navigating to a URL, e.g. via window.location, a link, or a redirect the attacker triggers). - So if an endpoint changes state on GET (e.g. GET /account/delete?id=5), an attacker can cause a top-level navigation to it and the Lax cookie IS attached -> the action fires. Lax does NOT save you here. - CLOSE IT: never change state on GET. Make all state changes POST/PUT/ DELETE (which Lax won't send cross-site), and validate a CSRF token. GAP 2 -- Site vs Origin: sibling subdomains are "same-site". - SameSite keys on the registrable domain (site), not the origin. So blog.example.com -> app.example.com is SAME-SITE, and the cookie IS sent. - If an attacker controls or finds XSS on ANY subdomain (or a sibling app), requests from there are not "cross-site" and SameSite provides no protection. (This dovetails with the Chapter 6 cookie-injection issue.) - CLOSE IT: don't implicitly trust subdomains; use anti-CSRF tokens (validated server-side) so a same-site sibling still can't forge a valid request; isolate/lock down subdomains; use __Host- cookies. GAP 3 -- SameSite=None cookies. - Cookies that must work cross-site (third-party embeds, some SSO/payment flows) are set SameSite=None and are therefore sent on cross-site requests -- exactly the legacy CSRF-enabling behaviour. Lax on OTHER cookies doesn't help these. - CLOSE IT: protect any flow using None cookies with tokens / Origin checks; minimise None usage; scope those cookies narrowly. ADDITIONAL GAPS (worth noting): - The historical "Lax+POST" ~2-minute grace window for freshly-set cookies. - Older browsers that ignore SameSite entirely. WHAT TO ADD TO ACTUALLY CLOSE THEM: 1. Anti-CSRF TOKENS (synchronizer or signed double-submit) validated on every state-changing request -- covers GAP 1's POST conversions, GAP 2's same-site siblings, and GAP 3's None cookies. 2. Make GET safe (no state changes) -- removes GAP 1. 3. Optionally Origin/Referer checking (Chapter 8) as another layer. 4. Explicit SameSite=Lax (or Strict) + Secure + HttpOnly on the session cookie, and __Host- prefix where possible. CONCLUSION: SameSite=Lax is a strong BASELINE that kills the classic POST-form attack, but it is NOT complete. Combine it with tokens and safe-GET discipline -- defence in depth (Chapter 8). "Lax" reduces risk; it doesn't make you CSRF-proof.