CSRF tricks authenticated users into executing unwanted actions on web applications they're logged into, exploiting browser cookie auto-attachment.
TL;DR
SameSite — 96.48% lack the primary modern defensetext/plain bypass defeats CORS preflight without custom headersCross-Site Request Forgery (CSRF, CWE-352) is a web security vulnerability that forces authenticated users into executing state-changing actions they did not intend. Unlike XSS, the attacker does not need to access the victim's session token — the browser's automatic cookie attachment mechanism does the work. When the victim visits the attacker's page, the browser silently includes all valid session cookies for the target domain on any embedded request, and the target server processes that request as if it originated from the user.
CSRF was removed from the OWASP Top 10 in the 2021 edition on the assumption that SameSite cookies, which are now the default in Chrome 80+ and most modern browsers, would eliminate most vectors. In practice, the threat has not gone away: CVE-2024-20252 (Cisco Expressway, CVSS 9.6, February 2024) and CVE-2024-23897 (Jenkins CLI, CVSS 9.8, CISA Known Exploited Vulnerabilities catalog, January 2024) both rely on CSRF as a primary or enabling vector. A 2024 audit found that only 3.52% of websites had any SameSite attribute set, meaning the vast majority of production applications still run without this defense.
Three conditions must simultaneously be true for a CSRF attack to succeed: (1) a privileged, state-changing action exists — password change, fund transfer, account creation, admin operation; (2) the application authenticates the user exclusively via cookies, with no supplementary check like a custom header or CSRF token; and (3) all request parameters needed to execute the action are predictable or known to the attacker. When these three conditions hold, the attacker can construct a crafted page that triggers the action with one visit from the victim.
The browser's Same-Origin Policy prevents JavaScript from reading cross-origin responses, but it does not prevent cross-origin requests from being sent. A page on evil.com can cause the browser to make a GET or form-POST request to bank.com, and the browser dutifully attaches bank.com's cookies. The server receives a syntactically valid, authenticated request with no indication that it originated on a hostile page.
The attack flow has five steps:
bank.com — session cookie set.evil.com (attacker-controlled page), either via phishing or an embedded ad.bank.com.bank.com receives the request, validates the session cookie, finds no CSRF token check, and executes the action.The RFC 9110 §9.2 safe method rule mandates that GET requests MUST NOT modify server state. GET-based CSRF exploits applications that violate this rule — a common anti-pattern in legacy code, admin panels with "quick-action" links, and logout endpoints.
| Variant | Mechanism | Primary Bypass | Impact |
|---|---|---|---|
| Classic GET-based | <img>, <a>, window.location trigger authenticated GET | State-changing GETs ignore SameSite=Lax on top-level nav | Account action, data deletion |
| POST-based | Auto-submitting hidden form, multipart upload | Token omission: server validates only if field present | Password change, email change, account deletion |
| JSON CSRF | enctype="text/plain" crafts JSON body, no CORS preflight | Server parses body regardless of Content-Type | API state change, GraphQL mutations |
| SameSite Bypass | 5 bypass paths: GET nav, 2-min Lax+POST, open redirect, subdomain XSS, null Origin | Chrome 120-second grace window for cookies without explicit SameSite | Full CSRF despite SameSite defense |
| Login CSRF | Pre-auth form auto-logs victim into attacker account | No session exists, so no token issued | Data harvesting, Self-XSS chain, OAuth code injection |
Understanding when cookies are sent cross-site is fundamental to understanding the attack surface:
| Cookie SameSite | Cross-site POST form | Top-level GET nav | <img> subresource | Notes |
|---|---|---|---|---|
Strict | Blocked | Blocked | Blocked | Safest. May break OAuth/SSO back-button flows |
Lax (Chrome 80+ default) | Blocked | Sent | Blocked | State-changing GETs remain exposed |
None; Secure | Sent | Sent | Sent | Required for payment widgets, cross-site embeds |
| Absent (Chrome 80+, new cookie) | Sent within 120s window | Sent | Blocked | Chrome Lax+POST grace period is exploitable |
SameSite=Lax is the Chrome default for cookies without an explicit attribute, but it does not protect state-changing GET endpoints. Sending cookies on top-level navigation is by design — it means any window.location redirect or <a href> click to a sensitive GET endpoint succeeds with victim credentials.
CVE-2024-20252 / CVE-2024-20254 — Cisco Expressway Series (CVSS 9.6, Critical): Insufficient CSRF protections on the Cisco Expressway web API allowed an unauthenticated remote attacker to trick an authenticated administrator into following a crafted link. The attack triggered arbitrary configuration changes, account creation with elevated privileges, and DoS conditions — all at the admin's privilege level. CVE-2024-20254 was exploitable in the default configuration. Published February 2024, patch required, no workaround.
CVE-2024-23897 — Jenkins CLI (CVSS 9.8, CISA KEV, Critical): Jenkins' args4j CLI parser replaced @<filepath> arguments with file contents. Attackers read /var/jenkins_home/secrets/master.key unauthenticated, extracted the CSRF crumb secret, forged valid CSRF tokens, and executed privileged CLI actions without any user interaction beyond the initial exploit. Added to CISA's Known Exploited Vulnerabilities catalog August 2024 with a mandatory patch deadline of September 9, 2024.
CVE-2024-4994 — GitLab CE/EE GraphQL (CVSS 8.1, High): CSRF on the /api/graphql endpoint allowed unauthenticated attackers to execute arbitrary GraphQL mutations on behalf of authenticated victims. GraphQL accepted mutations via content types that did not trigger CORS preflight, defeating the assumed protection. HackerOne #1122408 ($3,370 bounty) documented the GET-request mutation vector on the same endpoint.
HackerOne #1497169 — GitHub Enterprise Management Console ($10,000): The highest-known CSRF bounty on HackerOne. The attack bypassed CSRF protection in GitHub Enterprise's server management console, enabling arbitrary administrative actions on enterprise infrastructure.
delete, update, transfer, create in paths).csrf_token, authenticity_token, _token, __RequestVerificationToken, or XSRF-TOKEN field.Content-Type: text/plain and monitor whether the server still parses and executes the body.Set-Cookie headers. Note the SameSite value (or absence). Test state-changing GET endpoints directly via top-level navigation.?_method=POST or add X-HTTP-Method-Override: POST header to GET requests targeting POST endpoints. Frameworks like Symfony, Rails, and Laravel accept these.Burp Suite Pro's CSRF PoC generator identifies the attack surface; OWASP ZAP rule 20012 flags missing tokens. XSRFProbe performs deep token analysis including omission, blank, random, and cross-user reuse. Automated scanners miss token-not-bound-to-session vulnerabilities (cross-user token reuse) and SameSite bypass chains requiring multi-step exploitation.
BreachVex detects CSRF through multiple complementary techniques: form-token analysis with a JSON API probe, a cross-origin POST probe with baseline comparison and segment-aware skip logic, and a differential GET probe that detects authenticated-vs-unauthenticated response divergence.
Generate a cryptographically random, session-bound token per user session. Embed it in every state-changing form. Validate it server-side on every state-changing request, rejecting requests that omit or present an invalid token.
# FastAPI — CSRF token generation and validation
import secrets
from fastapi import Request, HTTPException, Form
def generate_csrf_token() -> str:
return secrets.token_urlsafe(32) # 256-bit entropy
def validate_csrf_token(request: Request, csrf_token: str = Form(...)):
session_token = request.session.get("csrf_token")
if not session_token or not secrets.compare_digest(session_token, csrf_token):
raise HTTPException(status_code=403, detail="CSRF token invalid or missing")Use secrets.compare_digest (constant-time comparison) to prevent timing oracle attacks. Tokens must be unique per session, not per request — per-request tokens break multi-tab applications.
Set SameSite=Strict on session cookies to block all cross-site requests, including top-level navigation. Use the __Host- cookie prefix to prevent subdomain injection attacks.
Set-Cookie: __Host-SID=<token>; Path=/; Secure; HttpOnly; SameSite=Strict__Host- requires Secure, Path=/, and no Domain attribute — preventing subdomains from injecting a matching cookie that could defeat double-submit patterns.
Implement a Resource Isolation Policy using Sec-Fetch-Site headers, which modern browsers attach to all requests (98%+ global coverage as of 2025):
# FastAPI resource isolation policy
SAFE_METHODS = {"GET", "HEAD", "OPTIONS"}
SAFE_SITES = {"same-origin", "same-site", "none"}
def resource_isolation_policy(request: Request):
site = request.headers.get("Sec-Fetch-Site", "")
mode = request.headers.get("Sec-Fetch-Mode", "")
dest = request.headers.get("Sec-Fetch-Dest", "")
if request.method in SAFE_METHODS or site in SAFE_SITES:
return
if mode == "navigate" and dest not in ("object", "embed"):
return # Allow top-level browser navigation
raise HTTPException(status_code=403, detail="Cross-site request rejected")Never rely on a single CSRF defense. SameSite alone fails for GET endpoints and the 2-minute window. CSRF tokens alone fail if the token is not session-bound (cross-user reuse). Implement SameSite=Strict + CSRF tokens + Fetch Metadata for defense in depth.
CSRF tokens can be leaked via BREACH (Browser Reconnaissance and Exfiltration via Adaptive Compression of Hypertext) when the server compresses responses containing both the token and attacker-controlled input in the same body. The Huffman compression oracle reveals the token character-by-character through ciphertext length measurements. Mitigation: use per-request token masking (XOR nonce, as implemented by the csrf-csrf library), or disable HTTP compression on secret-carrying responses.
XMLHttpRequest; login CSRF chains with stored XSS for account takeover.Cross-Site Request Forgery (CWE-352) forces authenticated users to unknowingly submit state-changing requests to a trusted application. The browser automatically attaches session cookies to every request to a domain, so an attacker's page can trigger authenticated actions without ever accessing the victim's session.
Three conditions must hold simultaneously: (1) an exploitable state-changing action exists on the target, (2) session authentication relies solely on cookies with no supplementary mechanism like custom headers, and (3) all request parameters are predictable by the attacker.
Yes. CVE-2024-20252 (Cisco Expressway, CVSS 9.6) and CVE-2024-23897 (Jenkins, CVSS 9.8, CISA KEV) both exploit CSRF as a primary or enabling vector. Only 3.52% of websites implement the SameSite cookie attribute, meaning the vast majority of applications still lack the primary modern CSRF defense.
No. SameSite=Lax blocks cross-site POST forms but sends cookies on top-level GET navigations. State-changing GET endpoints remain fully exploitable. The method override trick (?_method=POST) exploits Lax-protected POST endpoints via GET. Chrome's 2-minute Lax+POST grace window allows cross-site POST for newly-issued cookies lacking an explicit SameSite attribute.
XSS injects and executes attacker JavaScript in the victim's browser context, enabling cookie theft and arbitrary DOM manipulation. CSRF abuses the browser's cookie auto-attachment to forge authenticated requests—no code execution required. XSS can escalate CSRF by stealing CSRF tokens; CSRF can escalate XSS via login CSRF that makes victims execute stored payloads in the attacker's account.
No. HTTPS encrypts the transport layer but does not prevent CSRF. The attacker's page on evil.com causes the victim's browser to send an authenticated HTTPS request to target.com. TLS protects the channel, not the request origin.
The server generates a cryptographically random, session-bound token (minimum 128 bits, CSPRNG) and embeds it in every state-changing form. The server validates the token on each state-changing request, rejecting requests that omit or forge it. This is the primary CSRF defense for server-rendered applications.
No. JSON APIs are vulnerable via the text/plain Content-Type bypass: an HTML form with enctype='text/plain' sends a POST with a JSON-shaped body without triggering CORS preflight. CVE-2024-4994 (GitLab GraphQL, CVSS 8.1) and CVE-2022-41919 (Fastify) demonstrate this vector against real production APIs.
Login CSRF forces the victim to authenticate as the attacker's account. Since the login endpoint has no pre-existing session to protect, most applications skip CSRF protection there. The attacker then harvests any data the victim submits—credit card details, personal info—which lands in the attacker's account context.
Burp Suite Pro includes a CSRF PoC generator (right-click request → Engagement tools → Generate CSRF PoC). OWASP ZAP's active scan rule 20012 detects missing anti-CSRF tokens. XSRFProbe CLI tests token omission, blank tokens, random tokens, and cross-user token reuse. BreachVex automates CSRF detection across all five variant classes including JSON and SameSite bypass.