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:

  1. Alice is logged into her bank (bank.com). Her session cookie is stored in her browser.
  2. An attacker creates a malicious webpage on evil.com containing a hidden form that submits to bank.com's transfer endpoint.
  3. 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.
  4. The malicious page's JavaScript silently submits the form.
  5. Alice's browser sends the request to bank.com, automatically including her session cookie.
  6. Bank.com sees a valid authenticated request and processes the transfer.
  7. Alice never sees a confirmation β€” her browser may not even show any visible activity.
evil.com/attack.html HTML
<!-- 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.

transfer_form.html + handler.py Python + HTML
<!-- 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.

Set-Cookie Header HTTP
# 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: * with Access-Control-Allow-Credentials: true is 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_exempt decorator disables it β€” audit all uses.
  • Rails: protect_from_forgery enabled by default in ApplicationController. skip_before_action :verify_authenticity_token disables 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 csurf middleware (or newer alternatives) for form-based applications.

Testing for CSRF Vulnerabilities

CSRF testing is straightforward for state-changing endpoints:

  1. Identify a state-changing request (change email, transfer funds, delete account)
  2. Intercept the request with Burp Suite
  3. Remove the CSRF token from the request (or modify it to an invalid value)
  4. Replay the request
  5. 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