Description
Cross-Site Request Forgery (CSRF) abuses the way browsers automatically attach credentials—cookies, HTTP Basic Auth headers—to every request sent to a given origin, regardless of where the request originates. An attacker who can get an authenticated user to visit a malicious page can trigger requests to the target application that carry the user's full session credentials, causing the application to execute those requests as if the user had intentionally initiated them.
CWE-352 describes CSRF as a failure to verify that the user's intent is behind a state-changing request. The vulnerability exists because HTTP is stateless; sessions are maintained via cookies, and browsers do not distinguish between a request initiated by the user and one initiated by a third-party page loaded in another tab. OWASP classifies this under A01:2021 Broken Access Control because the attacker effectively gains the ability to perform access-controlled actions on behalf of the victim.
CSRF remains relevant despite SameSite cookie adoption because many applications still serve cookies without the SameSite=Strict or SameSite=Lax attribute, and because SameSite=Lax only protects against cross-site form POSTs—not all attack vectors. Legacy applications, mobile API backends that issue cookie-based sessions, and third-party integrations are particularly susceptible.
How It Works
A classic CSRF attack uses an HTML form on an attacker-controlled page that auto-submits to the target application when the victim loads it:
<!-- Attacker's page: https://evil.example.com/csrf.html -->
<html>
<body onload="document.forms[0].submit()">
<form method="POST" action="https://bank.example.com/transfer">
<input type="hidden" name="to_account" value="attacker-account-id">
<input type="hidden" name="amount" value="5000">
</form>
</body>
</html>
If the victim is authenticated to bank.example.com and visits this page, the browser sends the POST request with the victim's session cookie. The bank processes it as a legitimate transfer request.
For JSON APIs that use cookies, the same attack works if the endpoint accepts Content-Type: text/plain (which can be sent cross-origin without a preflight) alongside JSON-shaped bodies:
<form method="POST" action="https://api.example.com/v1/settings"
enctype="text/plain">
<input name='{"email":"attacker@evil.com","ignore":"' value='"}'>
</form>
Testing with Burp Suite: intercept a state-changing request, right-click, and select "Engagement tools > Generate CSRF PoC" to automatically produce a ready-to-use exploit page.
Impact
- Account takeover — Attackers change the victim's email address or password, locking them out and claiming the account.
- Unauthorized financial transactions — Fund transfers, purchases, or subscription changes are performed without the user's knowledge.
- Privilege escalation — Admin users are forced to create attacker-controlled administrator accounts.
- Data destruction — Mass deletion of records, configurations, or user data is triggered through administrative endpoints.
- Security control bypass — MFA settings, audit log configurations, and access controls are modified to facilitate follow-on attacks.
Detection
- Identify all state-changing HTTP requests (POST, PUT, PATCH, DELETE) by proxying the application through Burp Suite and reviewing the HTTP history.
- For each state-changing request, remove the CSRF token parameter and resubmit—a successful response indicates the token is not validated.
- Attempt to replay a captured request from a different browser session or with a modified
Origin/Refererheader to test server-side origin validation. - Use Burp's "Generate CSRF PoC" function to construct an exploit page, then serve it from a separate domain and confirm whether the action executes.
- Test JSON endpoints with
Content-Type: text/plainand a body that parses as valid JSON to identify endpoints that accept loose content types cross-origin. - Verify that session cookies carry the
SameSite=StrictorSameSite=Laxattribute using the browser's Application tab orSet-Cookieheader inspection.
Remediation
Implement synchronizer token pattern. Generate a cryptographically random, per-session (or per-request) CSRF token, embed it in forms and custom request headers, and validate it server-side on all state-changing requests.
# Django example — CSRF middleware is enabled by default
# Custom API endpoints must use @csrf_protect or use DRF's SessionAuthentication
Set SameSite=Strict on session cookies. This prevents the browser from sending session cookies on any cross-site request:
Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly
Validate Origin and Referer headers. As a defense-in-depth measure, reject requests where the Origin header does not match your application's expected origin.
Prefer token-based authentication for APIs. APIs that use Authorization: Bearer <token> headers instead of cookies are inherently immune to CSRF because headers are not automatically attached by the browser.
