Skip to content
CWE-352A01:2021 – Broken Access Control

CSRF in 2026

What still works and what died with Lax-by-default — the honest, browser-by-browser exploitability verdict, plus the techniques that are now purely historical.

The landscape changed, and most CSRF advice did not

Almost every CSRF tutorial still online was written before 2020. It teaches the auto-submitting cross-site POST form as the CSRF attack. In Chromium — the majority of the web — that exact attack has not worked by default since Chrome 80 shipped SameSite=Lax as the default for cookies with no SameSite attribute.

This has two consequences, and testers get burned by both:

  1. False negatives. You test the textbook POST form in Chrome, it fails, and you conclude the endpoint is protected. It is not — it may well be fully exploitable in Firefox and Safari, neither of which ever shipped a Lax default.
  2. False positives. You test in Firefox, it works, and you report "CSRF" without qualification. The triager tests in Chrome, sees nothing, and closes it.

The fix for both is the same: know which shape you are sending, know which cookie policy the target uses, and name the browsers in the finding. That is what this page is for.

The important structural point: CSRF did not die, it moved. The productive vectors in 2026 are top-level GET navigation, the endpoints that set SameSite=None because they needed third-party support, subdomain-adjacent attacks that SameSite never covered, and every non-cookie ambient credential.

Where the three engines actually stand

EngineDefault for a cookie with no SameSiteThird-party cookies
Chromium (Chrome, Edge, Brave, Opera)Lax, since Chrome 80 — plus the Lax+POST intervention belowStill allowed by default. The planned deprecation was reversed in April 2025; third-party cookies remain enabled in a default profile.
FirefoxNone — the lax-by-default rollout was tested in Nightly and backed out of releasePartitioned per top-level site under Total Cookie Protection, which is on by default. A third-party context gets its own empty jar.
SafariNone — never shipped a Lax defaultBlocked outright since Safari 13.1 (full third-party cookie blocking), on top of ITP.

The live/dead ledger

Technique2026 statusDetail
Cross-site top-level GET navigation to a state-changing endpointLive everywhereThe single most productive vector left. Lax sends the cookie on a top-level navigation with a safe method, and Lax is the Chromium default. Only SameSite=Strict blocks it. If the app performs writes on GET, SameSite has bought it nothing.
Cross-site top-level POST form, cookie has no SameSite attributeDead in Chromium, live in Firefox and SafariThe textbook attack. Report it with the browsers named, or it will be closed as unreproducible.
Same, but the cookie was set less than 120 seconds ago (Chromium)Live, brieflyThe Lax+POST intervention. It applies only to cookies with no SameSite attribute — never to an explicitly Lax cookie. In practice this means the window right after a login or an SSO bounce. Documented as temporary.
Cross-site top-level POST form, cookie is SameSite=None; SecureLive in all threeA top-level navigation makes the target the first party, so no third-party cookie policy applies. Any app that set None for embedding or third-party API support is fully exposed here.
Subresource CSRF (img, script, iframe, fetch) against a cookie with no SameSiteDead in all threeThree different mechanisms, same outcome: Chromium's Lax default, Firefox's Total Cookie Protection, Safari's third-party blocking.
Subresource CSRF against SameSite=None; SecureLive in Chromium onlyFirefox partitions the cookie, Safari blocks it. A Chromium-only finding is still a finding — say so.
JSON smuggled through enctype="text/plain"LiveUnaffected by any of the above — it is a body-encoding trick, not a cookie trick. Works wherever the server parses the body regardless of the declared content type. The cookie still has to arrive, so combine with a live row.
Subdomain cookie injection (cookie tossing)Live everywhereevil.example.com is same-site with example.com. SameSite offers zero protection, which is the single most under-appreciated fact on this page.
HTTP Basic, Digest, NTLM/Negotiate, TLS client certificatesLive everywhere, SameSite-immuneThese are ambient credentials but they are not cookies, so no cookie attribute governs them. Intranet applications behind Windows Integrated Auth are fully CSRF-able.
Routers, printers, IoT, and localhost dev serversLiveLocal Network Access gates subresource requests to private addresses; it does not gate top-level navigations. An auto-submitting form still reaches them.
Clickjacking-assisted CSRFConditionally liveA cross-site iframe receives no Lax or Strict cookie, so the framed page renders logged out and there is nothing to click. Needs SameSite=None, or a popup (which is top-level) instead of a frame.
Cross-site WebSocket hijackingLive only with SameSite=NoneThe handshake is a subresource GET, so it sits on the dead row above unless the cookie is None.
CORS-assisted CSRF (reflected ACAO with credentials)Live, subject to the cookie rowsThe CORS misconfiguration lets you read the response, but the cookie still has to arrive — Firefox and Safari will not send it in a third-party context regardless of the CORS headers.
Flash crossdomain.xml, Silverlight, Java applets, XDomainRequestDeadFlash reached end of life in December 2020; IE in June 2022. Historical interest only.
meta http-equiv="refresh" to force a POSTNever workedA refresh navigation is always a GET. This appears in old write-ups and has always been wrong.
JSON hijacking via the Array constructor or __defineSetter__DeadFixed in ES5. A JSON response loaded through script src is not executable JavaScript.
JSONP callback abuseMostly deadAnd it was always a read primitive, not a state-changing one — so it was never really CSRF.

The Lax+POST window, precisely

This is the most frequently misstated detail in modern CSRF write-ups, so it is worth being exact.

Chromium ships a temporary intervention: a cookie that has no SameSite attribute at all and was set less than 120 seconds ago is still sent on a cross-site top-level POST. The stated purpose is to avoid breaking POST-based SSO flows, where an identity provider sets a cookie and immediately POSTs back to the relying party.

What it does not do:

  • It does not apply to a cookie explicitly marked SameSite=Lax. An explicit Lax blocks a cross-site POST at any age.
  • It does not apply to subresource requests, only to top-level navigations.
  • It is not guaranteed to stay. It has been documented as temporary since it shipped.

Practically: if you can get the victim to authenticate and then land on your page inside two minutes, an unset-SameSite cookie behaves the old way in Chromium too. Chaining a forced re-login makes this less theoretical than it sounds — see Bypassing SameSite.

What this means for testing

Work in this order. It is the reverse of the usual instinct, and it saves the most time.

  1. Read the Set-Cookie first. Everything downstream depends on the session cookie's SameSite value. None means the old rules apply almost everywhere. An absent attribute means you have a browser-dependent finding. Strict means you need a same-site gadget before anything else is worth trying.
  2. Determine the auth mechanism. If it is a bearer token in a header, stop — there is no CSRF here. If it is Basic or a client certificate, SameSite is irrelevant and everything is live.
  3. Try GET first. If the endpoint accepts its state change over GET, you are done in every browser except Strict. Check for method-override parameters too — see Method and Content-Type Switching.
  4. Then the top-level POST form. Test it in Firefox as well as Chrome; they will disagree.
  5. Only then the exotic shapestext/plain JSON, subdomain cookie tossing, CORS assistance.
  6. Write the finding with the browser named, the cookie's SameSite value quoted from the response, and a PoC that a triager can open directly.

The PoC Generator applies all of this automatically: pick the delivery and the cookie policy and it tells you which engines will actually send the cookie.