What is CSRF? Cross-Site Request Forgery Explained
Understand Cross-Site Request Forgery (CSRF) vulnerabilities: how unauthorized cross-site requests exploit ambient credentials and how to defend against them.
CSRF stands for Cross-Site Request Forgery. While it sounds academic and complex, the core vulnerability exploits a simple human behavior: tricking your web browser into executing an unintended action on a trusted website without your awareness.
1. Explain Like I’m 10 (ELI5)#
Imagine you just entered an exclusive VIP members club (such as your online bank or social media profile):
- When you check in at the reception desk, they stamp a VIP pass on your hand (this is your browser’s Session Cookie).
- As long as the stamp is visible, the club staff knows you are a valid member and honors all your requests without asking for your ID again.
- Later, you walk outside and visit a shady street market stall (
evil.com). - The merchant writes an order on paper: “Transfer $1,000 from this member’s account to me”, grabs your hand with the VIP stamp, presses it onto the paper, and delivers it to the VIP club.
- The club sees a legitimate VIP stamp assumes you requested the transfer and debits your money immediately!
You never intended to transfer money, but your browser was tricked into providing its credentials. That is CSRF.
flowchart LR
subgraph AttackerSite["Malicious Website (evil.com)"]
MaliciousScript["Hidden Script / Auto-submit Form<br/>POST https://bank.com/transfer"]
end
subgraph UserBrowser["User Browser (Client)"]
Victim["Logged-in User<br/>(Holds Valid Session Cookie)"]
end
subgraph BankServer["Target Bank Server (bank.com)"]
Backend["Validates Cookie<br/>-> Executes Transfer!"]
end
Victim -->|1. Visits malicious site| MaliciousScript
MaliciousScript -->|2. Triggers silent cross-site request| Victim
Victim -->|3. Automatically attaches Cookie + Sends request| Backend
2. The Two Pillars of a CSRF Vulnerability#
CSRF does not require an attacker to steal your password or decrypt session secrets. Instead, it exploits ambient trust between web browsers and servers:
- Automatic Cookie Transmission (Ambient Credentials): Whenever your browser sends a request to any destination domain (e.g.,
bank.com), it automatically attaches all stored cookies matching that domain, regardless of which website triggered the request. - Naive Server Authentication: The server only checks “Is this user authenticated with a valid session cookie?” without verifying “Did this request genuinely originate from our own application interface?”.
3. Key Differences: CSRF vs. XSS#
Developers often confuse CSRF with XSS (Cross-Site Scripting). Here is how they compare:
| Dimension | XSS (Cross-Site Scripting) | CSRF (Cross-Site Request Forgery) |
|---|---|---|
| Mechanism | Injects and executes malicious JavaScript within the victim’s page context | Tricks the browser into sending unauthorized requests from an external origin |
| Steals Cookies? | Can read and exfiltrate cookies (unless protected with HttpOnly) | Cannot read or steal cookies (simply rides on existing sessions) |
| Root Cause | Unsanitized / unescaped user inputs rendered in the DOM | The browser’s automatic cross-site credential attachment behavior |
[!NOTE] XSS steals your identity; CSRF borrows your authority without knowing your secret tokens or passwords.
4. Common Attack Vectors#
Scenario 1: Unprotected GET Requests via <img> Tags#
If a web application mutates state using HTTP GET (violating REST conventions):
<!-- The victim's browser silently fetches the URL and attaches session cookies -->
<img src="https://bank.com/api/transfer?to=attacker&amount=5000" width="0" height="0" />htmlScenario 2: Hidden Auto-Submitting POST Forms#
Even with HTTP POST, attackers can embed hidden forms and submit them programmatically using JavaScript:
<form id="csrfForm" action="https://social.com/api/delete-account" method="POST">
<input type="hidden" name="confirm" value="true" />
</form>
<script>
// Submits automatically upon page load
document.getElementById('csrfForm').submit();
</script>html5. Comprehensive Defense Strategies#
1. CSRF Tokens (Synchronizer Token Pattern)#
The gold standard for form-based web applications:
- The server generates a unique, cryptographically random token tied to the user’s current session.
- When rendering a form, the token is embedded as a hidden field or header.
- Because malicious third-party websites are restricted by the Same-Origin Policy (SOP), they cannot read the CSRF token from your application, causing forged requests to be rejected.
<form action="/transfer" method="POST">
<!-- Secret CSRF Token generated by server -->
<input type="hidden" name="_csrf" value="d9a8f7b2c3e4..." />
<input type="number" name="amount" />
<button type="submit">Transfer Funds</button>
</form>html2. Cookie SameSite Attribute (Modern Native Defense)#
The SameSite cookie attribute instructs browsers when to send cookies with cross-site requests:
SameSite=Strict: Cookies are strictly omitted in all cross-site requests (maximum security, but users clicking external links must log in again).SameSite=Lax(Default in modern browsers): Cookies are blocked on cross-site state-modifying requests (like POST or embedded images), but allowed during safe top-level navigations (like regular links).SameSite=None: Cookies are sent in all cross-site contexts (requires theSecureflag over HTTPS).
Set-Cookie: sessionId=xyz123; Path=/; Secure; HttpOnly; SameSite=Laxhttp[!TIP] Setting
SameSite=LaxorSameSite=StrictalongsideSecureandHttpOnlyflags neutralizes the vast majority of traditional CSRF attack vectors.
3. Double Submit Cookie (For SPAs & Stateless APIs)#
In architectures where servers do not maintain server-side session stores:
- The server issues a random token into a readable cookie (e.g.,
XSRF-TOKEN). - The Single Page Application (React, Vue, Svelte) reads the cookie via JavaScript and attaches it to a custom HTTP header (e.g.,
X-XSRF-TOKEN). - The server compares the header token with the cookie token. If both match, the request is deemed authentic.
4. Validating Origin, Referer, and Sec-Fetch-Site Headers#
On backend API gateways, verify that incoming mutating requests match expected hostnames:
export function verifyOrigin(req, res, next) {
const origin = req.headers['origin'] || req.headers['referer'];
const allowedHost = 'https://mybank.com';
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
if (!origin || !origin.startsWith(allowedHost)) {
return res.status(403).json({ error: 'CSRF Protection: Invalid Request Origin' });
}
}
next();
}javascript6. CSRF in Modern SPA & JWT Architectures#
In modern frontend architectures:
- If JWT tokens are stored in JavaScript memory and passed via the
Authorization: Bearer <token>header, CSRF is eliminated, because browsers never automatically inject customAuthorizationheaders on cross-site requests. - However, if you store JWTs inside Cookies for convenience, your application remains fully vulnerable to CSRF unless proper CSRF defenses (
SameSite, Anti-CSRF headers) are enforced.
7. Summary#
CSRF is a reminder that blind trust in ambient credentials creates severe security liabilities.
To safeguard your web applications:
- Use
POST,PUT, orDELETEfor all state-changing actions. - Configure
SameSite=LaxorSameSite=Stricton all authentication cookies. - Require Anti-CSRF Tokens or custom headers for sensitive mutations.
- Validate
OriginandSec-Fetch-Siteheaders at the server middleware layer.