CSRF — Forging Authenticated Requests
Cross-Site Request Forgery (CSRF) exploits the trust a server places in an authenticated user’s browser. The attacker crafts a malicious page that, when visited, fires a request to the target using the victim’s cookies already stored in the browser.
How It Works
1. Victim logs in to bank.example.com — session cookie stored in browser.
2. Victim visits evil.example (link sent by email or chat).
3. evil.example contains a hidden form that POSTs to bank.example.com/transfer.
4. Browser automatically includes the session cookie.
5. Bank processes the transfer as if the victim initiated it.
Payload Example (Fictional Environment)
<!-- evil.example/trap.html -->
<form id="f" action="https://bank.example.com/transfer" method="POST">
<input name="to" value="attacker-account">
<input name="amount" value="5000">
</form>
<script>document.getElementById('f').submit();</script>
A GET-based attack can be even simpler with an <img> tag:
<img src="https://bank.example.com/delete-account?confirm=yes">
Why Cookies Alone Are Not Enough
The browser sends cookies automatically with every cross-origin request to the correct domain. The victim’s session is valid — the server cannot distinguish a legitimate request from a forged one without extra controls.
Prevention — CSRF Token
Generate an unpredictable per-session token. Include it in every form and validate it server-side.
// Generation (PHP)
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
// In the HTML form
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
// Server-side validation
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
http_response_code(403);
exit('CSRF detected');
}
Prevention — SameSite Cookie
Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly
SameSite=Strict: cookie not sent on any cross-site request.SameSite=Lax: sent only on top-level navigations (links), not on cross-site POSTs.
SameSite is the simplest and most effective defense for modern browsers.
Prevention — Verify Origin/Referer
# Django / any framework
origin = request.headers.get('Origin', '')
if origin not in ALLOWED_ORIGINS:
return HttpResponseForbidden()
Complementary Defenses
- Re-authenticate for sensitive actions (transfers, password changes).
- JSON APIs with
Content-Type: application/json— HTML forms cannot set that header cross-origin. - Double Submit Cookie: token in both cookie and body, compared server-side.
- Modern frameworks (Laravel, Django, Rails) enable CSRF protection by default — do not disable it.
What CSRF Cannot Do
CSRF cannot read server responses (same-origin policy blocks that). The attack acts, it does not eavesdrop — to steal data use XSS, not CSRF.