What it is#
Browsers automatically attach cookies to eligible requests. CSRF abuses that behavior by causing a victim's browser to send a state-changing request from an attacker-controlled page, email, advertisement, or embedded resource.
The attacker does not need to know the victim's session cookie. The browser supplies it. The vulnerability exists when the server treats possession of the cookie as proof that the user intentionally made the request.
CSRF primarily affects cookie-authenticated applications. APIs that require an authorization header unavailable to another origin are less exposed, but must still account for browser storage, cross-origin policy, and any cookie-based fallback.
Why it matters#
- Change an email address, password, shipping destination, or notification setting.
- Add an attacker-controlled MFA method or API key.
- Initiate a purchase, transfer, workflow approval, or administrative action.
- Modify security settings while the victim is signed in.
- Abuse privileged staff sessions when an administrative endpoint lacks CSRF protection.
The impact is limited to actions the victim is authorized to perform. That makes attacks against administrators and finance users especially serious.
Usually medium to high. Severity increases when a single request can change authentication settings, move value, expose sensitive data, or perform an administrative action without reauthentication. Actual severity depends on context: who is affected, what data or actions are reachable, and existing controls.
Examples#
# Vulnerable: cookie authentication is checked, but user intent is not.
@app.post('/account/email')
@login_required
def change_email():
current_user.email = request.form['email']
db.session.commit()
return '', 204
<!-- Fixed: Django issues and validates a session-bound token. -->
<form method="post" action="/account/email">
{% csrf_token %}
<input type="email" name="email" autocomplete="email">
<button type="submit">Update email</button>
</form>
For JSON APIs, require a non-simple request containing a CSRF token or a custom header set by trusted application code. Do not assume Content-Type: application/json is permanent protection unless the server strictly rejects form and text content types.
How to fix it#
1. Use the framework's maintained CSRF protection. Enable it globally, not route by route. Use synchronizer tokens for server sessions or a properly implemented signed double-submit cookie when server-side token storage is unavailable.
2. Bind tokens to the session. Tokens must be unpredictable, validated server-side, and rejected when missing or invalid. Do not place CSRF tokens in URLs, where they leak through history and logs.
3. Set cookie defenses. Use Secure, HttpOnly, and an appropriate SameSite value. SameSite=Lax or Strict blocks many cross-site submissions, but it is defense in depth rather than a replacement for tokens where sensitive actions are present.
4. Verify request origin. For state-changing requests, compare Origin to an exact allowlist. Use Referer only as a carefully handled fallback. Reject missing or malformed origin evidence according to a documented policy.
5. Never change state with GET. Use POST, PUT, PATCH, or DELETE, and make read operations genuinely side-effect free.
6. Require deliberate confirmation for critical actions. Reauthenticate or require a fresh MFA assertion before changing credentials, enrolling authenticators, moving value, or granting privileges.
7. Keep CORS narrow. Do not combine credentialed CORS with reflected or broadly trusted origins. CORS is not itself a CSRF control, but a permissive policy can defeat header-based defenses.
8. Protect login and logout flows. Login CSRF can place a victim into an attacker-controlled account. Logout CSRF can disrupt work or facilitate social engineering.
How to test for it#
- In a test environment, identify every state-changing route and record its authentication and CSRF requirements.
- Submit each request without the token, with an empty token, with a token from another session, and with a token issued before logout. Each must fail safely.
- Verify exact
Originhandling using a controlled alternate origin, a sibling subdomain, and an untrusted origin with a similar hostname. - Confirm that sensitive actions cannot be performed with
GETor a simple cross-origin form submission. - Test cookie behavior in supported browsers, including
SameSite, redirects, embedded contexts, and top-level navigation. - Confirm that JSON endpoints reject form-encoded and
text/plainbodies when those formats are not required. - Verify that password, email, MFA, payout, and privilege changes require fresh confirmation appropriate to their risk.
- Add automated integration tests that assert CSRF rejection before every protected handler is reached.
References#
- CWE-352: Cross-Site Request Forgery ↗ (opens in a new tab)cwe.mitre.org
- OWASP Cross-Site Request Forgery Prevention Cheat Sheet ↗ (opens in a new tab)cheatsheetseries.owasp.org
- OWASP Top 10 A01:2021 Broken Access Control ↗ (opens in a new tab)owasp.org
- PortSwigger Web Security Academy: CSRF ↗ (opens in a new tab)portswigger.net