What CSRF Actually Is
Cross-Site Request Forgery (CSRF) exploits a browser's default behaviour: when you make a request to a website, the browser automatically includes any cookies set by that website β including session cookies. This is what makes "staying logged in" work.
CSRF attacks use this behaviour against you. If an attacker can get your browser to make a request to bank.com while you're logged in, the browser will include your session cookie β and bank.com will think you made the request voluntarily.
OWASP historical context: CSRF was in the OWASP Top 10 from 2010 through 2017. It dropped off the 2021 list largely because modern browser defaults (SameSite cookies) have significantly reduced its prevalence. But it's still present in codebases that haven't adopted modern cookie settings or rely on legacy frameworks.
A CSRF Attack Walkthrough
The classic bank transfer scenario:
- Alice is logged into her bank (bank.com). Her session cookie is stored in her browser.
- An attacker creates a malicious webpage on evil.com containing a hidden form that submits to bank.com's transfer endpoint.
- The attacker tricks Alice into visiting evil.com β perhaps via a phishing link in an email, a malicious ad, or a link on a forum.
- The malicious page's JavaScript silently submits the form.
- Alice's browser sends the request to bank.com, automatically including her session cookie.
- Bank.com sees a valid authenticated request and processes the transfer.
- Alice never sees a confirmation β her browser may not even show any visible activity.
<!-- CSRF attack β auto-submits when page loads --> <form id="csrf" action="https://bank.com/transfer" method="POST"> <input type="hidden" name="to" value="attacker-account"> <input type="hidden" name="amount" value="10000"> </form> <script>document.getElementById('csrf').submit();</script>
Alice's session cookie is sent automatically. Bank.com processes the transfer. Alice didn't type a URL, fill in a form, or click a button β her browser did it all in the background.
Why Authentication Doesn't Protect Against CSRF
The most common developer misconception: "my users are authenticated, so CSRF doesn't apply." Authentication is exactly what CSRF exploits. The attack works because the user is authenticated β their session cookie proves that to the server.
Authentication tells the server "this request is from Alice." CSRF protection tells the server "Alice intended to make this request, from this page, at this time." Those are different questions. Authentication answers the first. CSRF protection answers the second.
Logging in doesn't help either: Requiring users to log in before making sensitive actions doesn't prevent CSRF. The attacker waits until the user is already logged in. The attack is triggered while they have a valid session.
CSRF Tokens: How They Work and Where They Break
A CSRF token is a random, unpredictable value generated server-side, associated with the user's session, and embedded in every form or state-changing request. The server validates that the submitted token matches the expected value before processing the request.
<!-- Form includes CSRF token --> <form method="POST" action="/transfer"> <input type="hidden" name="csrf_token" value="{{ csrf_token }}"> <input name="amount"> <button type="submit">Transfer</button> </form> # Server validates token @app.post("/transfer") def transfer(request: Request, amount: int, csrf_token: str): if csrf_token != session.get('csrf_token'): raise HTTPException(403, "Invalid CSRF token") # process transfer
The attacker on evil.com can't know the CSRF token β it's tied to Alice's session and embedded in the form that was generated on bank.com. The cross-origin same-origin policy prevents evil.com from reading responses from bank.com, so it can't steal the token.
Where CSRF tokens break
- XSS vulnerability on the same origin β if an attacker can execute JavaScript on bank.com, they can read the CSRF token from the page
- Token in URLs β CSRF tokens in GET parameters appear in browser history, Referer headers, and server logs
- Tokens not validated server-side β accepting any non-empty token value
- Tokens not tied to session β static tokens that work for any session
SameSite Cookies: The Modern Defence
The SameSite cookie attribute is a browser-level CSRF mitigation that controls when cookies are sent with cross-site requests. It's now the primary CSRF defence for modern applications.
# Strict β cookie only sent on same-site requests # Prevents CSRF completely but breaks OAuth flows and some email links Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly # Lax β cookie sent on top-level navigations (clicking a link) # Prevents most CSRF, allows normal navigation flows # Default in Chrome/Firefox since 2020 Set-Cookie: session=abc123; SameSite=Lax; Secure; HttpOnly # None β cookie sent in all requests (old behaviour) # Required for third-party embeds, but fully vulnerable to CSRF Set-Cookie: session=abc123; SameSite=None; Secure
Since Chrome and Firefox now default to SameSite=Lax, many CSRF attacks are blocked automatically for modern browsers even without explicit CSRF token protection. But explicit protection is still important for compatibility with older browsers and more complex attack scenarios.
Double Submit Cookie Pattern
The double submit cookie pattern is an alternative to server-side token storage. The server sets a random CSRF token as a cookie and requires the same value to be submitted as a form field or header. The server validates they match.
This works because cross-origin requests from evil.com can't read cookies set by bank.com (due to the Same-Origin Policy). The attacker can't submit the correct cookie value in the request body if they can't read it.
Limitation: If an attacker controls a subdomain of your domain (subdomain.bank.com), they can set cookies scoped to bank.com. This can break the double submit cookie pattern. Use a CSRF framework that prevents subdomain cookie injection attacks.
CSRF in APIs: Why JSON APIs Are Different
Traditional CSRF attacks work by forging form submissions. JSON APIs have a different risk profile:
- Cross-origin JSON requests: By default, browsers don't send cookies with cross-origin requests that have custom headers (like
Content-Type: application/json). The preflight CORS check prevents the attack. - But simple requests still send cookies: A POST with
Content-Type: application/x-www-form-urlencoded(the HTML form default) is a "simple request" and bypasses the CORS preflight β cookies are sent automatically. - CORS misconfiguration creates CSRF: An API that sets
Access-Control-Allow-Origin: *withAccess-Control-Allow-Credentials: trueis broadly exploitable.
For REST APIs: require the Content-Type: application/json header for all state-changing endpoints. This blocks forged form submissions. Combine with a strict CORS policy scoped to your legitimate frontend origins.
Framework Protections
Major frameworks provide CSRF protection by default β but only if you use it correctly:
- Django:
{% csrf_token %}template tag in every form. CSRF middleware enabled by default. The@csrf_exemptdecorator disables it β audit all uses. - Rails:
protect_from_forgeryenabled by default in ApplicationController.skip_before_action :verify_authenticity_tokendisables it β audit all uses. - Spring Security: CSRF protection enabled by default for all non-GET methods. Disabled with
.csrf().disable()β common in API projects but dangerous for browser-facing apps. - Express.js: No CSRF protection by default. Use
csurfmiddleware (or newer alternatives) for form-based applications.
Testing for CSRF Vulnerabilities
CSRF testing is straightforward for state-changing endpoints:
- Identify a state-changing request (change email, transfer funds, delete account)
- Intercept the request with Burp Suite
- Remove the CSRF token from the request (or modify it to an invalid value)
- Replay the request
- If the server processes the request without the valid token, it's vulnerable to CSRF
Burp Suite's built-in CSRF PoC generator creates a proof-of-concept HTML page for any captured request β one click to generate the attack page.
Test SameSite settings too: Check that your session cookie has SameSite=Lax or Strict set. Inspect cookies in browser DevTools (Application β Cookies) or in Burp's HTTP history.
Find CSRF and Web Security Issues
AquilaX SAST detects missing CSRF protections, unsafe cookie configurations, and CORS misconfigurations across your web application codebase.
Start Free Scan