Skip to main content

CSRF

High

Cross-Site Request Forgery

CSRF tricks an authenticated user's browser into making unauthorized requests to a server. The server cannot distinguish a legitimate request from a forged one because both carry the same session cookie. SameSite=Strict cookies and CSRF tokens are the primary defenses.

Overview

CSRF attacks exploit the browser's automatic cookie attachment behavior. When a user visits a malicious site, that site can make requests to a different origin (like a bank). The browser automatically attaches the user's cookies for that origin, making the forged request appear legitimate to the server.

CSRF does not steal the response – the attacker cannot read it due to the Same-Origin Policy. The attack only works for state-changing requests (POST, PUT, DELETE) where the server takes action based on the user's authenticated session.

Modern defenses have made CSRF much harder: SameSite=Strict cookies prevent cookie attachment on cross-site requests entirely, and the Fetch Metadata headers (Sec-Fetch-Site, Sec-Fetch-Mode) let servers distinguish same-origin from cross-site requests.

The Attack: Hidden Form CSRF Attack

The attacker hosts a page with a hidden form that auto-submits to the target site. When the victim visits the page, their browser submits the form with their cookies attached.

CSRF attack page (attacker-controlled)
http
<!-- Attacker's page at evil.com -->
<form 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.forms[0].submit();</script>

Defenses

1SameSite=Strict or Lax cookies

Set SameSite=Strict on session cookies to prevent them from being sent on any cross-site request. SameSite=Lax (browser default since 2021) allows cookies on top-level navigation GET requests but blocks POST. Strict is stronger but may break OAuth flows.

Secure session cookie
http
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict; Path=/

2CSRF tokens

Generate a cryptographically random token per session, embed it in every state-changing form as a hidden field or custom header, and validate it server-side. The attacker cannot know the token, so they cannot forge a valid request.

CSRF token in AJAX request header
http
POST /api/transfer HTTP/1.1
Host: bank.com
X-CSRF-Token: 8f14e45fceea167a5a36dedd4bea2543
Content-Type: application/json

{"to": "friend", "amount": 50}

3Verify Sec-Fetch-Site header

Modern browsers send Sec-Fetch-Site: cross-site on cross-origin requests. Reject POST/PUT/DELETE requests where Sec-Fetch-Site is not same-origin or same-site. This is a defence-in-depth measure on top of SameSite cookies.

Server-side check (pseudo-code)
http
# Allow only same-origin state-changing requests
Sec-Fetch-Site: same-origin   ← allow
Sec-Fetch-Site: cross-site     ← reject for POST/PUT/DELETE

Checklist

  • Set SameSite=Strict or SameSite=Lax on all session cookies
  • Use CSRF tokens for state-changing requests in server-rendered forms
  • For SPAs using APIs: use Authorization headers instead of cookies – CSRF does not apply
  • Check Sec-Fetch-Site header as a defense-in-depth measure
  • Validate Content-Type on POST requests (multipart/form-data is exploitable; application/json is not from plain HTML forms)
  • Do not use GET requests for state changes (no amount of CSRF protection helps a destructive GET)

Related Headers

Related Status Codes

FAQ

Do APIs using JSON need CSRF protection?

Not if they require an Authorization header (Bearer tokens). CSRF only applies when authentication is provided automatically by the browser via cookies. If your API uses Bearer tokens in headers, CSRF is not a concern because a forged form cannot set custom headers.

Does SameSite=Lax protect against CSRF?

Partially. SameSite=Lax blocks CSRF on POST requests (most attacks) but allows cookies on top-level navigation GET requests. This protects against most CSRF attacks. For bank-level security, use SameSite=Strict and CSRF tokens.