HackproofHacks
Web App Security 14 min read

Cross-Site Request Forgery (CSRF): How It Works and How to Prevent It

A clear, practical guide to CSRF — how attackers make a victim's browser send unwanted authenticated requests, the conditions that make it possible, and the modern defences (SameSite cookies, anti-CSRF tokens) that stop it.

Hassan Ansari

Hassan Ansari

A code card showing a forged CSRF POST request carrying the victim session cookie

Cross-Site Request Forgery (CSRF): The Complete Explainer

Cross-site request forgery, catalogued by OWASP as CSRF, is one of the most elegant attacks in web security, because it exploits a feature the web can’t function without: browsers automatically attach your cookies to requests. CSRF turns that convenience into a weapon, making your own browser act against you.

It’s less common than it used to be thanks to modern browser defaults, but it’s far from dead: misconfigured cookies and cookie-authenticated APIs still leave the door open. This guide explains exactly how CSRF works, the conditions it needs, and the layered defences that stop it.


What is CSRF?

To understand CSRF, start with how browsers handle authentication. When you log in to a site, the server sets a session cookie in your browser. From then on, your browser automatically attaches that cookie to every request it sends to that site, whether you clicked a link on the site itself or something on a completely different site triggered the request.

That automatic attachment is the vulnerability. CSRF is an attack where a malicious page causes your browser to send a request to a site where you’re logged in, and the browser dutifully includes your session cookie, so the target site sees an authenticated, legitimate-looking request.

The classic scenario:

  1. You log in to your bank. Your browser now holds a valid bank session cookie.
  2. Without logging out, you visit a malicious website in another tab.
  3. That website contains hidden code that submits a “transfer money” request to your bank.
  4. Your browser sends the request and, as always, attaches your bank session cookie.
  5. The bank sees a valid, authenticated request and processes the transfer.

The attacker never saw your password, your session cookie, or the bank’s response. They didn’t need to. They just needed your browser to send one request on your behalf.


How a CSRF attack actually works

The mechanics are simpler than most vulnerabilities. Suppose a site changes your email address with this request:

POST /account/email HTTP/1.1
Host: app.example
Cookie: session=abc123...
Content-Type: application/x-www-form-urlencoded

email=user@personal.example

If the only thing authenticating this request is the session cookie, an attacker can forge it. They build a malicious page with a hidden, auto-submitting form:

<form action="https://app.example/account/email" method="POST">
  <input type="hidden" name="email" value="attacker@evil.example">
</form>
<script>document.forms[0].submit();</script>

When a logged-in victim loads the attacker’s page, the form submits automatically. The browser sends the POST to app.example and attaches the victim’s session cookie. The application sees a valid request and changes the victim’s email to one the attacker controls: the first step toward a full account takeover via password reset.

Even simpler: if the sensitive action accepts a GET request, the attacker doesn’t even need a form. A single hidden image does it:

<img src="https://app.example/account/delete?confirm=true">

The victim’s browser loads the “image,” sending an authenticated GET request. This is exactly why state-changing actions must never be reachable via GET.


The three conditions CSRF needs

CSRF only works when all three of these are true. Break any one and the attack fails:

  1. A relevant action. Something worth doing: changing credentials, moving money, altering permissions, deleting data.
  2. Cookie-based session handling. The request is authenticated solely by cookies the browser sends automatically. If it requires a token the browser doesn’t attach on its own, CSRF can’t supply it.
  3. No unpredictable request parameter. The attacker must be able to construct the entire request in advance. If the request requires a value the attacker can’t know or guess (an anti-CSRF token), they can’t forge a valid request.

Every CSRF defence works by removing one of these conditions, usually the third.


What an attacker can do with CSRF

The impact depends entirely on what actions the victim can perform. Realistic outcomes include:

  • Account takeover by changing the victim’s email or password.
  • Fund transfers or purchases on financial and e-commerce sites.
  • Privilege changes: adding an attacker as an admin, changing roles.
  • Data deletion or modification.
  • Silent settings changes: disabling security features, adding a recovery address.

The attacker acts entirely through the victim’s own authenticated session, so the actions look completely legitimate in the logs. That stealth is part of what makes CSRF dangerous.


How to prevent CSRF: layered defences

Modern CSRF prevention combines a browser-level control with an application-level one.

1. SameSite cookies (the browser-level defence)

The SameSite cookie attribute tells the browser whether to send a cookie on cross-site requests:

Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax
  • SameSite=Strict: the cookie is never sent on any cross-site request. Strongest, but can affect legitimate cross-site navigation (e.g., following a link to your site while logged in).
  • SameSite=Lax: the cookie is withheld from cross-site subrequests (like the forged form POST) but still sent on top-level GET navigations. This is now the browser default and stops the classic CSRF form attack.

SameSite has dramatically reduced CSRF, and every session cookie should set it. But it isn’t a complete answer on its own, which is why it pairs with tokens. You can audit your cookies’ SameSite, HttpOnly, and Secure flags in seconds with our HTTP Header Analyzer, and there’s a full walkthrough in the HTTP security headers guide.

2. Anti-CSRF tokens (the application-level defence)

The classic, robust defence is the synchronizer token pattern. The server generates a unique, unpredictable token tied to the user’s session, embeds it in each form or page, and requires it back on every state-changing request:

POST /account/email HTTP/1.1
Cookie: session=abc123...

email=user@personal.example&csrf_token=9f3c2a7e1b...

Because the attacker’s cross-site page can’t read the token (the same-origin policy prevents it) and can’t guess it, they can’t construct a valid request. This removes CSRF’s third condition directly. Most modern web frameworks include CSRF-token protection built in. The key is to keep it enabled and apply it to every state-changing endpoint.

3. Verify the Origin and Referer headers

As an additional check, the server can inspect the Origin (and, as a fallback, Referer) header on state-changing requests and reject any that don’t come from its own origin. Browsers set these automatically and pages can’t forge them, so this is a useful supplementary control, though it’s best used alongside tokens, not instead of them.

4. Never use GET for state changes, and re-authenticate for critical actions

Ensure that any action which changes state uses POST, PUT, PATCH, or DELETE (never GET) so it can’t be triggered by a simple resource load. For the most sensitive actions (changing a password, deleting an account), require re-entry of the password or a second factor, so even a forged request can’t complete without something the attacker doesn’t have.


A note on XSS and APIs

Two important caveats.

First, XSS defeats CSRF protection. If an attacker has a cross-site scripting foothold, they can read the anti-CSRF token straight from the page and forge perfectly valid requests. This is one reason XSS is rated so highly: it undermines other defences. Fixing XSS is part of defending against request forgery.

Second, API authentication matters. APIs and single-page apps that authenticate with a bearer token sent in a custom header are largely immune to classic CSRF, because the browser never attaches that token automatically to cross-site requests. But APIs that authenticate with session cookies are just as vulnerable as traditional apps and need the same protections. When testing APIs, this distinction is central: see the API security testing guide for the full methodology.


Key takeaways

  • CSRF abuses the browser’s automatic attachment of cookies to make a victim send unwanted authenticated requests.
  • It needs three conditions: a relevant action, cookie-based auth, and a predictable request. Break one and it fails.
  • The impact runs from account takeover to fund transfers, all through the victim’s own session.
  • Defend in depth: SameSite cookies at the browser level, anti-CSRF tokens at the application level, Origin checks, and no GET for state changes.
  • XSS bypasses CSRF defences, and cookie-authenticated APIs are just as vulnerable as traditional apps.

CSRF shows that security isn’t only about your own code. It’s about how the browser behaves on behalf of your users. Control what the browser is allowed to send, and the attack disappears.

The reference implementations for every defence above are in the OWASP CSRF Prevention Cheat Sheet, and PortSwigger’s free CSRF labs let you build and defeat each one legally.

Keep learning: read the XSS explainer, the HTTP security headers guide, and the OWASP Top 10 breakdown. Ready to practise? See our free tools and hands-on training.

#CSRF #cross-site request forgery #web security #SameSite #cookies #OWASP
Free newsletter

Liked this? I write one like it every week.

One practical security lesson in your inbox each week, explained the same simple way. Join 10,000+ readers. Unsubscribe anytime.

From the article

Need a security assessment?

HackproofHacks provides web application and API penetration testing — using the same techniques covered in this article, with your explicit authorisation.

Book a free scoping call

More on Web App Security.

All articles →
FAQ

Questions about this topic.

What is cross-site request forgery (CSRF) in simple terms?

CSRF is an attack that tricks a victim's browser into sending an unwanted, authenticated request to a site where the victim is already logged in. Imagine you've signed in to your bank in one tab. In another tab you visit a malicious page, and that page secretly instructs your browser to submit a 'transfer money' request to the bank. Because your browser automatically attaches your bank session cookie to every request to the bank, the bank sees a valid, logged-in request and processes it. The attacker never sees your session or the response — they just abuse the fact that your browser will act on your behalf.

What is the difference between CSRF and XSS?

They're often confused but are different. XSS (cross-site scripting) runs the attacker's own JavaScript inside the victim's browser on a trusted site, giving broad control over the session. CSRF doesn't run any script on the target site — it simply causes the victim's browser to send a legitimate-looking request to a site where they're authenticated, without the attacker seeing the response. XSS is generally more powerful, and importantly, an XSS vulnerability can be used to bypass CSRF protections entirely, which is one reason XSS is treated as more severe.

Do SameSite cookies stop CSRF?

SameSite cookies are a strong, modern defence and browsers now default cookies to SameSite=Lax, which blocks cookies from being sent on most cross-site requests and stops the classic CSRF attack in many cases. However, SameSite alone is not a complete solution: Lax still allows top-level GET navigations, some flows need SameSite=None for legitimate reasons, and browser support and edge cases vary. Best practice is defence in depth — use SameSite cookies and a proper anti-CSRF token for state-changing requests.

What kinds of requests are vulnerable to CSRF?

State-changing requests that rely solely on cookies for authentication and are otherwise predictable — changing an email or password, transferring funds, updating settings, deleting data, or performing admin actions. If an attacker can predict exactly what the request looks like and the server accepts it based only on the automatically-sent session cookie, it's a CSRF candidate. Requests that require an unpredictable token, or that authenticate with a bearer token the browser doesn't send automatically, are generally not vulnerable.

Why should state-changing actions never use GET requests?

Because GET requests are trivially triggered cross-site — an attacker can fire one just by embedding an image or a link that the victim's browser loads automatically. If a sensitive action like deleting an account or changing a setting can be performed via a GET request, an attacker can trigger it with a single hidden image tag on any page the victim visits. State-changing actions should always use POST, PUT, PATCH, or DELETE, and be protected by an anti-CSRF token, so they can't be triggered by simple resource loads.

Are APIs and single-page apps vulnerable to CSRF?

It depends on how they authenticate. If an API authenticates with a bearer token stored in memory or local storage and sent via a custom header, it's largely immune to classic CSRF, because the browser doesn't attach that token automatically to cross-site requests. But if an API or single-page app authenticates using session cookies, it is just as vulnerable to CSRF as a traditional app and needs the same protections. The deciding factor is whether authentication relies on something the browser sends automatically.