Skip to main content

Cross-Site Request Forgery CSRF

Cross-Site Request Forgery, also called CSRF, is an attack where a user is tricked into performing an unwanted action on a website where they are already logged in.

A simple example is a banking website.

User is logged in to bank website
↓
User clicks a malicious link from email/website
↓
That link silently triggers bank transfer API
↓
Bank server receives request with user's valid session
↓
Unwanted transaction may happen

In real banking apps, extra protection like OTP helps prevent this. But if such safeguards are missing, CSRF can become dangerous.

One-line idea: CSRF tricks a logged-in user's browser into sending an unwanted authenticated request to a trusted website.

Core Concepts​

What It Is​

CSRF forces an authenticated user to execute unwanted actions on a web application.

Simple meaning:

User is already logged in
↓
Attacker tricks user into clicking/opening something
↓
Browser sends request to trusted website
↓
Cookies/session are automatically included
↓
Server thinks request came from real user

The attacker does not need the user's password. The attacker abuses the user's already-authenticated browser session.

Why It Matters​

CSRF is dangerous because the server may trust the request only because the valid session or cookie is present.

A successful CSRF attack can perform actions like:

  • transferring funds
  • changing email address
  • changing password-related settings
  • sending messages
  • updating profile data
  • performing admin actions

If the victim is an admin user, CSRF can compromise the entire web application.

Core Flow​

A typical CSRF attack flow looks like this:

Attacker creates malicious link/form/image
↓
Victim is already logged in to target website
↓
Victim opens malicious email or website
↓
Browser sends request to target website
↓
Authentication cookie/token is automatically sent
↓
Server processes the unwanted action

Example mental model:

Attacker cannot access your bank directly,
but attacker can trick your logged-in browser to send a bank request.

Main Reasons CSRF Happens​

The PDF explains that CSRF happens because of basic web and HTTP behavior.

ReasonMeaning
Statelessness of HTTPEvery HTTP request is treated as a fresh request
Automatic authenticationCookies/tokens may be automatically carried with the request
Weak action APIsSensitive actions may be triggered too easily
Social engineeringUser may be tricked into clicking malicious links/forms

Statelessness of HTTP​

HTTP is stateless.

That means:

Every request is independent.
Server checks the request and authentication data each time.

If the browser sends a valid session cookie with a request, the server may treat that request as authenticated.

User Authentication Problem​

Cookies and tokens can be automatically sent by the browser.

Example:

User logged in to bank.com
↓
Browser has bank session cookie
↓
Malicious site triggers request to bank.com
↓
Browser may attach bank cookie
↓
Bank server sees authenticated request

This is why CSRF is possible.

Practical Examples​

Vulnerability: Using GET for State-Changing Actions​

One major mistake is using a GET API call to update data or perform an action.

Bad example:

http://bank.com/fundtransfer?accId=21312&amount=10000

If this URL performs a fund transfer, it is dangerous.

Why?

Because attackers can embed this URL in:

  • anchor tags
  • image tags
  • forms
  • emails
  • external websites

Bad anchor example:

<a href="http://bank.com/fundtransfer?accId=21312&amount=10000"> Offer </a>

Bad image example:

<img
src="http://bank.com/fundtransfer?accId=21312&amount=10000"
width="0"
height="0"
/>

If the image loads automatically, the browser may send the request without the user clearly noticing.

Important rule:

Never use GET for actions that update data.

Vulnerability: Hidden Image Request​

A malicious email or webpage can hide an API call inside an image tag.

Example:

<img
src="http://bank.com/fundtransfer?acct=224224&amount=500000"
width="0"
height="0"
border="0"
/>

On page load:

Browser loads image
↓
Image URL calls fund transfer endpoint
↓
Request goes to bank.com
↓
If user is logged in, session may be included

This is dangerous because the user may not even click anything.

A malicious email can show a normal-looking button.

Example:

<a href="http://bank.com/fundtransfer?acct=224224&amount=50000">
Sign Up For Offer
</a>

The user thinks they are clicking an offer, but the link actually triggers a sensitive action.

Flow:

Email says: Click to get offer
↓
User clicks
↓
Redirects to bank transfer URL
↓
Bank action may execute

Vulnerability: Form-Based CSRF​

CSRF is not limited to GET requests. A malicious form can also send a POST request.

Example:

<form action="http://bank.com/fundtransfer" method="POST">
<input type="hidden" name="acct" value="224224" />
<input type="hidden" name="amount" value="50000" />
<input type="submit" value="Click to get your free gift!" />
</form>

When the user clicks the button:

Form submits POST request
↓
Hidden account and amount values are sent
↓
Bank endpoint receives request
↓
Unwanted transfer may happen

The user sees a harmless button, but hidden inputs contain the actual sensitive action data.

Main Mitigations​

The PDF lists these main CSRF mitigations:

MitigationPurpose
Anti-CSRF tokenServer verifies that the request came from the real form/session
SameSite cookiesControls when cookies are sent in cross-site requests
Referer validationChecks whether request came from expected domain
CAPTCHAAdds human verification
CSP headersAdds browser-level protection
Avoid GET updatesPrevents state change through simple links/images

Anti-CSRF Token​

An Anti-CSRF token is a secret token generated by the server and attached to forms or requests.

When the form is submitted, the server checks whether the submitted token matches the token stored on the server.

Flow:

Client opens page
↓
Server generates CSRF token
↓
Server stores token in session
↓
Server sends token inside form
↓
Client submits form with token
↓
Server validates token
↓
Only valid token request is processed

Simple rule:

Every sensitive form/request should include a CSRF token.

How Client Gets CSRF Token​

The client gets the CSRF token when it first loads the page or form from the server.

Client sends first request
↓
Server creates CSRF token
↓
Server saves token in session
↓
Server sends token to client form

Example hidden input:

<input type="hidden" name="csrf_token" value="SERVER_GENERATED_TOKEN" />

When form is submitted, this token goes back to the server.

Anti-CSRF Token Example​

A simplified Express example:

const express = require("express");
const bodyParser = require("body-parser");
const session = require("express-session");
const crypto = require("crypto");

const app = express();
const port = 3000;

app.use(
session({
secret: "your_secret_key",
resave: false,
saveUninitialized: true,
}),
);

app.use(bodyParser.urlencoded({ extended: false }));

app.get("/", (req, res) => {
if (!req.session.csrfToken) {
req.session.csrfToken = crypto.randomBytes(32).toString("hex");
}

res.send(`
<form action="/fundtransfer" method="POST">
<input type="hidden" name="acct" value="224224" />
<input type="hidden" name="amount" value="50000" />

<input
type="hidden"
name="csrf_token"
value="${req.session.csrfToken}"
/>

<input type="submit" value="Transfer" />
</form>
`);
});

app.post("/fundtransfer", (req, res) => {
const submittedToken = req.body.csrf_token;

if (!submittedToken || submittedToken !== req.session.csrfToken) {
return res.status(403).send("CSRF Token Validation Failed!");
}

delete req.session.csrfToken;

res.send("Form submitted successfully!");
});

app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`);
});

What this does:

Creates token
↓
Stores token in server session
↓
Sends token in form
↓
Checks token on POST request
↓
Rejects request if token is missing or wrong

SameSite Cookies​

SameSite controls when cookies are sent with cross-site requests.

Example:

app.use((req, res, next) => {
res.setHeader(
"Set-Cookie",
"sessionId=abc123; SameSite=Strict; Secure; HttpOnly",
);
next();
});

SameSite values:

ValueMeaning
StrictBrowser sends cookie only for same-site requests
LaxCookie is not sent on most cross-site requests
NoneCookie is sent with both cross-site and same-site requests

Simple mental model:

Strict -> strongest same-site restriction
Lax -> balanced restriction
None -> allows cross-site cookies

Modern browsers require SameSite=None cookies to also use Secure.

For CSRF protection, stricter cookie behavior helps reduce the chance that authentication cookies are sent during malicious cross-site requests.

Referer-Based Validation​

Referer validation checks where the request came from.

Server reads the Referer header and verifies whether it starts with the expected domain.

Example:

const express = require("express");
const bodyParser = require("body-parser");

const app = express();
const port = 3000;

app.use(bodyParser.urlencoded({ extended: false }));

app.use((req, res, next) => {
const referer = req.get("Referer");

if (referer && referer.startsWith("https://yourwebsite.com")) {
next();
} else {
res.status(403).send("Forbidden");
}
});

app.post("/process", (req, res) => {
res.send("Request processed successfully");
});

app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`);
});

Flow:

Request reaches server
↓
Server checks Referer header
↓
Referer is expected domain -> allow
Referer is missing/wrong -> reject

In production, parse the header and compare the origin exactly. Many systems also validate the Origin header for state-changing requests when it is present.

CAPTCHA​

CAPTCHA can help protect sensitive actions because it adds human verification.

Example use cases:

  • fund transfer
  • password reset
  • account setting changes
  • suspicious actions

Mental model:

CSRF tries to automate unwanted action.
CAPTCHA adds a human verification step.

CSP Headers​

The PDF also mentions using CSP headers.

CSP headers can reduce the risk of unsafe content execution and help control what resources can load.

For CSRF, CSP is not the only protection, but it adds another browser-level security layer.

Simple rule:

Use CSRF token and SameSite cookies as primary protection.
Use CSP as additional defense.

Why GET Should Not Update Data​

GET requests should be used for reading data, not changing data.

Bad:

GET /fundtransfer?acct=224224&amount=50000

Better idea:

POST /fundtransfer

But remember:

POST alone does not prevent CSRF.
POST must also use CSRF protection.

Use:

POST + CSRF token + SameSite cookies + validation

Some Good Practices​

The PDF mentions these user and application practices:

  • Always log out of bank apps when done.
  • Create complex passwords.
  • Do not save passwords in the browser.
  • Do not allow the same user to log in from multiple places in sensitive apps like banking apps.
  • Do not use GET method for update operations.

For developers, the most important practice is:

Do not depend only on login session.
Verify that sensitive requests are intentional.

CSRF Testing​

The PDF mentions:

putsmail.com/tests/new

This can be used for testing email-based scenarios.

CSRF testing should include:

  • malicious link test
  • hidden image request test
  • form POST test
  • missing CSRF token test
  • wrong CSRF token test
  • SameSite cookie behavior test
  • Referer validation test

CSRF vs XSS​

TopicMeaning
CSRFTricks authenticated user/browser into sending unwanted request
XSSInjects malicious script into a page
Main abuseCSRF abuses trust in authenticated requests
Main protectionCSRF tokens, SameSite cookies, Referer checks
ExampleLogged-in user clicks malicious transfer link

Simple comparison:

XSS runs malicious script in the site.
CSRF sends malicious request using user's logged-in session.

Summary​

CSRF tricks a logged-in user's browser into sending an unwanted authenticated request to a trusted website.

Logged-in user + malicious link/form + auto-sent cookie = CSRF risk

Remember it like this:

Authentication proves who the user is.
CSRF token proves the action came from the real form.
SameSite controls when cookies travel.
Referer checks where the request came from.
GET should never change data.

Or:

Do not process sensitive actions just because a valid cookie came with the request.