Overview
Cross-Site Request Forgery (CSRF) is a client-side vulnerability in which an attacker induces a victim's browser to issue an HTTP request — carrying the victim's cookies or HTTP authentication credentials — to a target application without the victim's knowledge or intent. Because the browser automatically attaches stored credentials, the target application cannot distinguish the forged request from a legitimate one based on credentials alone.
CSRF was listed in the OWASP Top 10 as a standalone category for many years and is now codified under CWE-352. It is particularly dangerous for operations that modify state: changing a password or email address, initiating a funds transfer, altering account privileges, or deleting records.
How it works
The attack relies on two properties of web browsers: cookies are automatically sent with every request to the cookie's origin, and the Same-Origin Policy governs JavaScript read access to responses but does not block the browser from sending cross-origin requests.
- Authentication: The victim logs into a target application (e.g., a banking site) and receives a session cookie stored in the browser.
- Lure: While the session is still active, the victim visits or is tricked into loading a malicious page (crafted email, third-party website, or injected ad).
- Forged request: The malicious page contains markup or script — an
<img>tag with a craftedsrc, a hidden HTML form with JavaScript auto-submit, or afetch()call with appropriate CORS pre-flight bypass — that causes the browser to issue a request to the target application. - Credential attachment: The browser automatically appends the session cookie (and any HTTP Basic/Digest credentials) to the outbound request.
- Action execution: The target application, seeing valid credentials, processes the request as if it were intentional.
Key technical factors that affect exploitability:
- HTTP method: GET-based state changes are trivially exploitable via
<img src>or<link href>. POST-based actions require a form or fetch, but are still exploitable with simple HTML. - Content-Type restrictions:
application/jsonPOST requests normally trigger a CORS preflight, which the browser will block for cross-origin requests to non-permissive endpoints — buttext/plainormultipart/form-datado not trigger preflights and can still carry JSON-like payloads. - Cookie attributes: Absence of the
SameSiteattribute (or usingSameSite=None) means cookies are sent on all cross-site requests, widening attack surface. - Subdomain trust: If subdomains share cookies (broad
Domainattribute), a compromised subdomain can serve a CSRF payload against the main application.
Business impact
A successful CSRF attack executes arbitrary state-changing operations with the victim's full privileges. Concrete consequences include:
- Account takeover: An attacker changes the victim's email address or password, locking them out and gaining full control.
- Financial fraud: Unauthorized fund transfers, purchases, or subscription changes in payment-enabled applications.
- Data destruction or exfiltration setup: Deleting records, changing data-sharing settings, or adding an attacker-controlled administrator account.
- Privilege escalation: Granting the attacker elevated roles within the application.
- Regulatory and legal exposure: Unauthorized processing of personal or financial data may trigger obligations under GDPR, PCI DSS, or other frameworks.
- Reputational damage: Users who experience unexplained account changes lose trust in the platform.
How to fix it
- Synchronizer Token Pattern (primary defense): Generate a cryptographically random, per-session (or per-request) token and embed it as a hidden field in every state-changing HTML form or as a custom HTTP header (
X-CSRF-Token) for AJAX. Validate the token server-side on every mutating request. Tokens must be unguessable (at least 128 bits of entropy) and tied to the session. - Double Submit Cookie: Set a random value in both a cookie and a request parameter (or header). Validate that they match server-side. This is weaker than the synchronizer pattern if subdomain cookie injection is possible but is useful for stateless architectures.
- SameSite Cookie Attribute: Set
SameSite=StrictorSameSite=Laxon session cookies.Strictblocks the cookie on all cross-site requests.Lax(the browser default since 2020 in most browsers) blocks it on cross-site sub-resource and form POST requests while allowing top-level navigations. Neither attribute alone is a complete defense — use it as a defence-in-depth layer. - Verify the Origin or Referer header: On the server, check that the
Originheader (preferred) orRefererheader matches the expected origin. Reject requests that originate from unexpected sources. Be careful with header stripping by proxies and missing headers on direct navigation. - Use custom request headers for AJAX: Require a custom header (e.g.,
X-Requested-With: XMLHttpRequest) for all AJAX-based state changes. Cross-origin requests cannot set arbitrary headers without a CORS preflight, which the server can reject. - Eliminate GET-based state changes: Ensure that HTTP GET requests are idempotent and never modify server state. Use POST, PUT, PATCH, or DELETE for all mutations, following REST semantics.
- Require re-authentication for sensitive operations: For high-impact actions (password change, payment, privilege grant), prompt users to re-enter their password or complete a second factor, independent of CSRF tokens.
- Apply CORS policy correctly: A restrictive CORS policy prevents cross-origin JavaScript from reading responses, but it does not prevent simple cross-origin requests from being sent. Do not rely on CORS as a CSRF defense.
- Framework defaults: Use the built-in CSRF protection provided by your web framework (Django's
CsrfViewMiddleware, Laravel'sVerifyCsrfToken, Spring Security'sCsrfFilter, Rails'protect_from_forgery, etc.) rather than implementing custom token logic.