Picture this. Your customer just logged into your application. Two minutes later — literally two minutes — they click a link in a Telegram group. The page loads. Nothing visible happens. They go back to whatever they were doing.
Meanwhile, in those two minutes, an attacker just changed their email address. The "forgot password" email is on its way — to the attacker's inbox. By the time your customer notices, the account is gone. So is whatever was in it.
That's not a hypothetical. That's the LAX+POST 2-minute window — a documented exception in every modern browser that almost no engineering team has heard of. And it's just one of seven ways the "dead" bug class called CSRF has crawled back into production in 2024 and 2025.
Around 2020, the web security community quietly declared victory over CSRF. Browsers started defaulting cookies to SameSite=Lax. The classic attack — an attacker hosting a form that POSTs to your bank — stopped working. Everyone moved on. CSRF tokens fell off code review checklists. "We don't need that anymore" became a sentence we now hear on almost every engagement.
And then 2024 happened. A researcher took over Zoom sessions through a forgotten subdomain. A WordPress plugin called Post SMTP shipped a CSRF bug that exposed 400,000 sites. Another plugin hit CVSS 9.8 for letting an unauthenticated attacker manipulate a single cookie and become admin. Cisco shipped CSRF advisory after CSRF advisory against its enterprise call routing platform. The bug class everyone declared dead was very much alive — it just got sneakier.
This article is for two readers. If you run a company, the next section explains what's actually at stake and the questions to put to your team. If you build the product, the technical sections walk through the seven modern bypass paths — with the CVEs, the proof points, and the one-line defenses that actually close them.
The 60-second version, if you sign the checks
CSRF is the bug where an attacker doesn't need to steal your customer's password. They just need to make your customer's browser do something while they're logged in. Click a link. Open a tab. Visit an ad. Your application sees a request that looks like the legitimate user — because, technically, it is — and dutifully changes their email, deletes their data, or wires their money.
The browser was supposed to protect against this. Mostly, it does. But here's what keeps biting companies:
- It targets the people you can least afford to lose. CSRF only works against users who are logged in. That is, by definition, your paying customers and your administrators. Random visitors are immune. The attack is precisely tuned to hit the accounts you spent the most to acquire.
- Account takeover is the only outcome we see. Every CSRF bug we have validated on a real engagement in the last 18 months chained the same way: forge a request → change the victim's email → trigger a password reset → reset email goes to the attacker → they own the account. The HackerOne disclosure list (US DoD, Bumble, Logitech, IRCCloud, Khan Academy, Concrete CMS) reads like a litany of exactly this chain.
- Your "old" subdomains are the weapon. The marketing site nobody's touched in three years. The status page on the abandoned Heroku app. The conference landing page from 2022. Each of these can become a tool for taking over your main application — through a class of attack called cookie tossing. Zoom got hit with exactly this in 2024.
- The CVE list is growing, not shrinking. 400,000 sites exposed in one plugin CVE in 2025. CVSS 9.8 in another. Cisco enterprise products. The bug class has not been declining since 2020. The attacker community simply moved up the stack to where the defenses haven't followed.
- The fix is cheap. The audit is the entire job. Adding a CSRF token is a one-day project. Finding every state-changing endpoint, every subdomain that shares a cookie, every GET that should have been a POST, every method-override middleware silently rewriting requests — that's the work. And almost no team has done it.
If your team's response to "do we test for CSRF?" is "browsers handle that now" — that exact sentence appears in the post-mortems of most companies on the disclosure list above. It is not a controversial statement to make in 2026. It is just the wrong one.
What SameSite actually does (and the part nobody talks about)
Here's the marketing version of SameSite, which is also where most teams' understanding stops:
SameSite=Lax (the modern default) lets your cookies travel on top-level cross-site navigation — like clicking a link from another site. It blocks cookies on cross-site POST forms, AJAX, fetch calls. Classic CSRF, dead in the water. Everyone celebrated. The bug class moved on.
Now here's the part nobody talks about. SameSite has exceptions. Several. Each one is a documented carve-out the browser vendors added so that legitimate, normal web traffic wouldn't break. Each one is also a CSRF attack surface. They include:
- A two-minute grace window where Lax cookies do attach to cross-site POSTs (the LAX+POST exception).
- Anything happening on a subdomain of yours — because SameSite is between sites, not between subdomains.
- Anything happening through a browser extension — because the extension is first-party from the browser's perspective.
- Anything routed through a payment widget, embedded iframe, federated SSO, or other cookie that has to be
SameSite=Noneto work at all. - Any GET request that mutates state — because Lax was always supposed to permit GETs.
The class of attacks that survives in those gaps is what we keep finding. Here they are.
Attack 1 — The "harmless" GET that isn't
An attacker emails your customer a link. The customer clicks. Their browser opens the URL — a top-level navigation, exactly the case SameSite=Lax allows cookies on. The endpoint at the other end does something. Unsubscribes them from notifications. Confirms a setting. Removes a 2FA device. Sometimes deletes a resource.
"Click here to unsubscribe" is the most common form of this. So is the OAuth callback that completes a connection. So are the half-dozen "convenience" endpoints in every web app that accept GET because it was easier than writing the form.
The classic example is Concrete CMS's HackerOne disclosure #152052: their email-change endpoint accepted GET. A single attacker-crafted link rewrote the victim's email; the standard password reset flow then handed the attacker the entire account. The full chain fit in one tweet.
The defense: state changes are POST/PUT/PATCH/DELETE, full stop. If you accept GET on anything that mutates server state, you are exposed.
Attack 2 — The framework that rewrote your method behind your back
This one is so sneaky it embarrasses the team that finds it on themselves. Here's the pattern.
Your developer writes a defensive endpoint. It only accepts POST. They put a comment above it: // POST only, so no CSRF risk. Code review approves it. CI passes. Production ships.
Except — somewhere in the framework, there's middleware that supports "method override." Rack does this in Rails. Express has a popular middleware for it. Symfony, several PHP frameworks, older Spring configurations. The idea: a form (which can only do POST in HTML) can pretend to be a DELETE by including a magic parameter, _method=DELETE. The middleware silently converts.
Now the attacker doesn't need to send a POST. They send a GET. With ?_method=POST&email=attacker@evil.com. The browser, happily following a link, sends the GET (which Lax allows). The middleware, happily helping, converts it to POST. Your defensive endpoint, never told what happened, executes.
This is a real bug class that PortSwigger maintains a public lab for. Their academy keeps it in the curriculum because it keeps appearing in real applications. It will appear in yours too if method-override middleware is enabled and you haven't audited for it.
Attack 3 — The 2-minute window inside every browser
This is the one almost no one knows about, so it bears repeating.
When a browser sets a cookie with SameSite=Lax using the browser's default behavior (i.e., you didn't explicitly set SameSite=Lax in your Set-Cookie header), the cookie gets a hidden grace period. For exactly two minutes after the cookie is set, the browser will attach it to a top-level cross-site POST.
Two minutes. Every time your user logs in.
The attacker's playbook: identify a high-traffic application. Note that "freshly logged-in user clicks a link" is a fully reproducible state — you can engineer it by sending a link in a chat tool that the victim will read shortly after login. Slack, Discord, Telegram, Teams. Plenty of plausible delivery channels. The attack works in the time it takes the page to load.
What kills this? A single line of code. Explicitly set SameSite=Lax in your cookie header. The two-minute exception only applies to the default. Explicit configuration disables it.
Almost no team explicitly sets it. They get the default. They get the window. The attacker gets two minutes.
Attack 4 — Cookie tossing: the subdomain you forgot about
This is the modern CSRF attack class, and it is the one we expect to dominate the next three years of disclosures. Here's how it works.
SameSite protects between sites, not between subdomains. Cookies on example.com can be set by, and read by, blog.example.com, status.example.com, conference-2022.example.com — anything ending in example.com.
The attacker does not need to find a bug on your main application. They need to find a bug on any subdomain. Often, this is the easy part. Marketing sites built by interns. Status pages on third-party services that quietly went out of business. CNAME records pointing at GitHub Pages or Heroku apps that were deleted six years ago and could be claimed by anyone who tries.
Once the attacker controls a subdomain — even one nobody at your company remembers — they can write cookies. They can overwrite the CSRF token your main app issued. They can overwrite a session ID. They can plant a fresh authentication cookie that your application reads as valid.
This is what happened to Zoom in 2024. A researcher chained cookie tossing through a subdomain, abused the OAuth flow, bypassed the WAF, and ended up with full session takeover of any logged-in Zoom user. The technique now has a name (cookie tossing), a body of public research, and a growing list of victims.
If your team has not audited the cookie scope on your session cookie — and run a recent subdomain takeover scan — you have not yet been tested against the version of CSRF that is actually winning.
Attack 5 — The cookies you had to leave open
Some cookies legitimately need to work across origins. Payment widgets. Federated SSO. Customer portals embedded in iframes. Social embeds. For these, SameSite=None is not negligence — it is a requirement for the feature to function.
What goes wrong: the developer who set SameSite=None often did not also add CSRF tokens to the endpoints those cookies travel on, because "we have SameSite." Now SameSite is doing nothing for those particular cookies. And those particular cookies are typically attached to the highest-value endpoints — payment confirmation, account linking, settings changes.
The bug we keep finding: explicit SameSite=None + implicit CSRF reliance = no defense at all on the parts of the app that matter most.
Attack 6 — One-click via window.open
The victim doesn't even need to click your link. They visit the attacker's page — an ad, an embedded share preview, a malicious search result. The page runs window.open("https://yourapp.com/...?action=evil"). That's a top-level navigation. Lax permits it. The action executes.
This is the same attack as #1, with the "victim must click" requirement removed. Anywhere a victim browses the open web, the attack can fire. For applications with high session lifetime — anything that keeps you logged in for days — the population at risk is essentially "anyone with an account."
Attack 7 — The malicious browser extension
Here's the one with no browser-level fix.
A browser extension runs in the user's browser. It has access to the user's cookies. When it makes a request to your application, the browser treats it as first-party. SameSite is irrelevant — the request is coming from the user's own browser, which is exactly the context SameSite is built around.
Applications with concentrated high-value users — developer tools, financial dashboards, enterprise admin panels, crypto wallets — are repeatedly targeted by extensions explicitly built to abuse them. Recent campaigns have hit MetaMask, banking dashboards, and several SaaS admin consoles. There is no header you can set that changes this. The defenses are at the application layer — CSRF tokens, re-authentication on sensitive actions — not the cookie layer.
The CVEs that should be on your radar
- CVE-2025-5947 — WordPress Service Finder Bookings plugin. CVSS 9.8. The
service_finder_switch_back()function didn't validate a cookie before trusting it as authentication. Manipulate the cookie, become admin. Unauthenticated. The bug is cookie tossing in its purest possible form — the application volunteers to trust whatever cookie shows up. - CVE-2025-11833 — WordPress Post SMTP plugin. CVSS 9.8. Account takeover affecting 400,000 installations. Among the most severe WordPress disclosures of 2024–2025. Single CSRF chain, full admin.
- CVE-2025-15001 — WordPress FS Registration Password plugin. The plugin updated passwords without validating identity first. Account takeover via a forged request.
- Cisco Expressway CSRF advisories. Multiple disclosures in 2024 and 2025 against Cisco's enterprise call routing platform. Same-day patches in some cases. The bug class is alive in well-resourced enterprise products, not just plugins.
- Patchstack tracking. The Patchstack WordPress vulnerability database catalogs dozens of CSRF CVEs per quarter — New User Approve, Userpro, Appointify, and a long tail of others. WordPress is a leading indicator. What appears here this quarter appears in custom SaaS code next quarter.
The pattern across all of these: the bugs migrated. Core frameworks (Rails, Django, Spring) ship aggressive defaults that handle the basic cases. The bugs moved to plugins, integrations, custom middleware, and the "long tail" code teams give less attention to. Yours is in there somewhere.
What "actually secure" looks like
Nine guards. Each closes one of the attacks above. Each is a single configuration choice or a small code change. The work isn't writing them — it's finding every place they need to go.
- Explicitly set
SameSite=Lax(minimum) on every session cookie. This single change closes the 2-minute LAX+POST window that bites teams relying on browser defaults. - CSRF tokens on every state-changing endpoint, regardless of HTTP method. Synchronizer Token Pattern or Double-Submit Cookie. Frameworks ship these; turn them on and audit every endpoint.
- Validate the
Originheader on sensitive endpoints. Browsers sendOriginon cross-site requests. An allowlist check is a cheap second layer. - Re-authenticate for high-risk actions. Email change. Password change. MFA enrollment. Withdrawals. Re-prompt for password. CSRF cannot forge a re-authentication — the attacker doesn't know the password.
- Stop using GET for state changes. RESTful design helps here. This is half discipline, half security — but it pays.
- Scope cookies tightly.
Domain=app.example.com, notDomain=.example.com. Every subdomain that shares a cookie is a CSRF pivot waiting to happen. - Audit and monitor your subdomain attack surface. Every dangling CNAME, every old marketing site, every status page is a cookie-tossing primitive. This is now a security audit, not a brand-protection one.
- Disable method-override middleware unless you specifically need it. If you need it, exclude sensitive routes explicitly.
- For
SameSite=Nonecookies, treat SameSite as if it doesn't exist. Because for those cookies, it doesn't. Mandatory CSRF tokens on every endpoint they reach.
The boardroom translation table
| What your team says | What it actually means | What it could cost you |
|---|---|---|
| "We don't need CSRF tokens — we have SameSite" | Protected against the simplest case. Wide open to method override, cookie tossing, GET state changes, and the LAX+POST window. | Account takeover via any of the seven modern bypasses. HackerOne pricing: $250 to $5,000 per finding. |
"Our session cookie has Domain=.ourcompany.com" |
Every subdomain shares the auth cookie. The marketing site is now an auth surface. | Zoom 2024 scenario — full session takeover via a subdomain you forgot existed. |
| "We use method override middleware for cleaner routing" | POST-only endpoints can be hit with GET. The framework converts behind your handler's back. | Standard finding in any modern CSRF assessment. |
| "We have some old subdomains we don't actively maintain" | Each one is a cookie-tossing primitive waiting to be claimed. | Account takeover via a brand asset nobody has thought about in years. |
| "We accept GET on a few convenience endpoints" (unsubscribe, share) | Every one is a single-click attack vector deliverable by email. | Support tickets, churn, user-visible damage you'll see before the security team does. |
Five questions to put to your engineering team this week
- "Name every state-changing endpoint that accepts GET requests." If they can't, that audit is your highest-leverage CSRF defense for the quarter. Each one is a one-click attack.
- "Is our session cookie scoped to one host, or to the parent domain?" The wider the scope, the bigger the attack surface. Worth a meeting.
- "Do we explicitly set
SameSite=Lax, or rely on the browser default?" Explicit closes the 2-minute window. Defaults leave it wide open. - "List every subdomain we own. When was the last subdomain takeover audit?" Cookie tossing starts here. This is no longer optional.
- "Do we re-authenticate before email change, password change, MFA enrollment, or payment?" Re-auth is the single defense CSRF cannot forge. If the answer is no, that is your easiest high-impact fix.
What we test, every engagement
- Every state-changing endpoint, with a PoC from an external origin.
- Method override: GET with
?_method=POST, POST with_method=DELETE. - LAX+POST timing: confirm whether default SameSite leaves a 2-minute post-login window.
- Cookie scope enumeration:
Domainattribute audit, subdomain attack surface mapping. - Cookie tossing: from a controlled subdomain, write cookies the main app trusts.
- Subdomain takeover scan against every CNAME and dangling DNS record.
- SameSite=None enumeration: identify which cookies opted out, verify CSRF tokens are present.
- GET-based state changes: enumerate every GET endpoint, check for mutations.
Origin/Referervalidation on sensitive endpoints.- Re-authentication checks on the actions that matter.
If your last assessment didn't include the 2024–2026 catalog above, your application has not been pressure-tested against the version of CSRF that is currently winning.
The bottom line
"SameSite handles CSRF" was true for about five minutes. The browsers handle the easy case. They do not handle the case where your subdomain got taken over, where your framework rewrites methods behind your back, where your session cookie is two minutes old and the victim is browsing Telegram, where your SameSite=None payment cookie has no token defending it, where a malicious extension is sitting in the user's browser making requests as them.
The defenses are short. The audit is the work. The cost of doing it this quarter is small. The cost of being the next entry in the disclosure thread — with your company's name, your customers' accounts, and your incident timeline — is not.
CSRF testing is still on our checklist for every engagement. Two thousand twenty-six is not the year to take it off yours.