Cross-site request forgery (CSRF)
CWE-352OWASP A01:2021Updated August 29, 20265 min read
Cross-site request forgery (CSRF) is a vulnerability that makes a logged-in user's browser send a request without their knowledge. The site executes that request as if it came from the user, so actions such as a password change or a money transfer are carried out without the victim's consent.
Cross-site request forgery, or CSRF, is not about stealing data but about abusing trust: your own user unknowingly carries out an action the attacker composed for them. This article explains what CSRF is, how such an attack unfolds, what an attacker can achieve with it, and how to protect your application against it.
What is cross-site request forgery?
Cross-site request forgery (CSRF) is a vulnerability that lets an attacker abuse the browser of a logged-in user to perform an unwanted, state-changing action on a website where that user is signed in. The victim notices nothing: a single visit to a prepared page is enough to fire off a background request that the target site treats as entirely legitimate.
The heart of the problem is a habit of the browser. Imagine a bank that carries out any instruction as long as it arrives in an envelope bearing your personal seal. A fraudster cannot forge that seal, but he can slip you a ready-made instruction letter and trick you into placing it in your own, already-sealed envelope. The envelope here is your session cookie: the browser attaches it automatically to every request to the bank, including one set in motion by a stranger’s page. The site sees a valid seal and obeys.
How does a CSRF attack work?
CSRF lurks wherever an application performs a sensitive action based on nothing more than the session cookie: changing an email address, resetting a password, transferring money or granting permissions. Because the browser sends that cookie unprompted, the attacker does not need to know the login credentials at all.
Vulnerable:
// Changes the email address, trusts the session cookie alone
app.post('/account/email', (req, res) => {
if (!req.session.userId) return res.status(401).send('Not logged in');
updateEmail(req.session.userId, req.body.email);
res.send('Email updated');
});
This endpoint checks only that a valid session exists and then applies the change without a second thought. Nowhere does it verify that the request actually came from its own website. So the attacker only has to build a page that sends this request on the victim’s behalf, and get a logged-in user to open it.
<!-- Hosted on the attacker's site; submits itself immediately -->
<form action="https://bank.example/account/email" method="POST">
<input type="hidden" name="email" value="attacker@evil.example">
</form>
<script>document.forms[0].submit()</script>
The moment the victim opens this page (through a phishing email, an advert or a hidden frame) the browser submits the form, session cookie for the bank included. The account’s email address is changed to the attacker’s, who then requests a password reset and takes the account over. Not a single stolen password was involved.
Safe:
import { randomBytes, timingSafeEqual } from 'node:crypto';
// When rendering the form: generate a token and store it in the session
app.get('/account/email', (req, res) => {
req.session.csrfToken = randomBytes(32).toString('hex');
res.render('email-form', { csrfToken: req.session.csrfToken });
});
app.post('/account/email', (req, res) => {
if (!req.session.userId) return res.status(401).send('Not logged in');
const expected = req.session.csrfToken ?? '';
const received = req.body.csrfToken ?? '';
const ok = expected.length === received.length
&& timingSafeEqual(Buffer.from(expected), Buffer.from(received));
if (!ok) return res.status(403).send('Invalid CSRF token');
updateEmail(req.session.userId, req.body.email);
res.send('Email updated');
});
The safe version adds a secret the attacker cannot possibly guess. When the form is requested, the user receives a random token, which the server also keeps in the session. On submission that same token must come along, and the server compares the two with a timing-safe check. The attacker’s page does not know this token (after all, it cannot read the response containing the form) and its request runs into a 403. On top of that, set the session cookie to SameSite=Lax or Strict, so the browser stops sending the cookie at all on a request that starts from a foreign site.
What is the impact of CSRF?
The severity of CSRF is usually medium, for a clear reason. The same-origin policy stops the attacker from reading the response; the request is sent blind, and it needs a logged-in victim who can be lured to the attacker’s page. So CSRF does not steal data: it forces actions.
Even so, that one forced action can weigh heavily. If the email address or password is changed, a full account takeover often follows. Other classic targets are a money transfer, adjusting permissions, adding an administrator or changing settings that weaken security. Inside an admin interface, a single successful CSRF request can flip the configuration of an entire system. The damage to the business therefore ranges from a hijacked customer account to reputational and financial loss, which is exactly why a “medium” vulnerability like this one is not something to leave open.
How do you detect CSRF?
During a test, a pentester walks through every state-changing action (anything that creates, changes or deletes) and checks whether the request is protected by an unpredictable token that is unique per session. Then that token is removed, reused from another session, or slightly altered to see whether the server still carries out the request. The SameSite setting of the session cookie and any checks on the Origin and Referer headers are inspected as well. Endpoints that change state through a GET request are a classic red flag, because they are trivial to abuse. Automated scanners report a missing token, but rarely judge whether the token check is actually watertight. AssistSec covers CSRF as standard in a penetration test and specifically probes the edge cases where a token appears to be present but is not validated correctly.
How do you prevent CSRF?
- Protect every state-changing action with the synchronizer token pattern: an unpredictable, per-session token that the server verifies on each request.
- Set session cookies to
SameSite=LaxorSameSite=Strict, along withSecureandHttpOnly. - Never make a change through a
GETrequest; reserveGETfor reading data. - On sensitive actions, check the
OriginandRefererheaders as an extra layer. - For critical operations (password or email changes, payments) re-prompt for the password or a second factor.
- Rely on your framework’s built-in CSRF protection rather than rolling your own, and keep it switched on.
- Close cross-site scripting thoroughly, because XSS defeats any CSRF token you put in place.
Sources
Frequently asked questions
What is the difference between CSRF and XSS?
In XSS an attacker injects malicious scripts that run in the victim's browser and can read data. In CSRF no script runs on the target site; the attacker only makes the browser send a request and abuses the session cookie that rides along with it. XSS is also a common way to defeat CSRF protection.
Does HTTPS protect against CSRF?
No. HTTPS encrypts the traffic, but a CSRF request is itself a valid, well-formed request. The encryption does nothing to change the fact that the browser attaches the session cookie automatically. You need a CSRF token or a strict SameSite cookie.
Is a SameSite cookie enough on its own against CSRF?
SameSite=Lax or Strict blocks most classic attacks and is a strong baseline, but it does not cover every scenario: think of subdomains, older browsers, or GET requests that change state. Combine SameSite with a CSRF token for sensitive actions.
Can a CSRF attack read the response?
No. The same-origin policy stops the attacker from reading the victim's response. The request is sent blind. That is why CSRF is mainly dangerous for actions that change something, not for stealing data.
Related articles
- VulnerabilitiesCWE-287A07:2021Broken authenticationBroken authentication explained: how attackers take over accounts through brute force, leaked passwords and predictable session tokens, and how to stop them.
- VulnerabilitiesCWE-79A03:2021Cross-site scripting (XSS)Cross-site scripting (XSS) lets attackers inject malicious scripts into web pages that run in visitors' browsers. Learn how XSS works and how to prevent it.
- VulnerabilitiesCWE-16A05:2021Security misconfigurationSecurity misconfiguration explained: how default passwords, debug modes and open cloud buckets let attackers in, and how to harden your systems.
- VulnerabilitiesCWE-918A10:2021Server-side request forgery (SSRF)Server-side request forgery (SSRF) explained: how attackers abuse your server to reach internal systems and cloud services, and how to prevent it.