Safeguard
Vulnerabilities

A CSRF Example: Full Attack Walkthrough

A concrete csrf example — a bank transfer form with no anti-forgery token — showing exactly how a forged request rides in on a victim's session and what stops it.

Safeguard Research Team
Research
Updated 5 min read

The clearest way to understand cross-site request forgery is through a concrete CSRF attack example: a bank's "transfer funds" form that accepts a POST request with an account number and amount, relying only on the user's session cookie to confirm who's asking. If that's the entire authentication check, any page the victim visits while logged in can submit that same request on their behalf — the browser attaches the session cookie automatically, and the server has no way to tell a request the user meant to make from one a malicious page tricked their browser into sending.

What does a working CSRF attack example look like?

Picture a bank's transfer form at bank.example/transfer that accepts amount and to_account as POST parameters and checks only that a valid session cookie is present. An attacker hosts a page containing a hidden auto-submitting form pointed at that same URL, with amount and to_account fields pre-filled to the attacker's own account. When a logged-in victim visits the attacker's page — through a phishing link, a malicious ad, or a comment link on a forum — their browser submits the form, and because browsers attach cookies to requests based on the target domain (not the page that triggered the request), the bank's server sees a normal, authenticated-looking transfer request. The victim never sees a form, never clicks a transfer button, and often never realizes anything happened until the balance is wrong.

Why does the victim's browser cooperate with this?

Because cookie-based session authentication was never designed to distinguish "the user intended this request" from "the user's browser happens to be logged in when this request fired." Cookies get attached automatically to any request aimed at their domain, regardless of which page initiated it — that's the entire mechanism CSRF abuses. This is why GET-based state changes are especially dangerous: a request as simple as an img tag pointing at bank.example/transfer?amount=500&to=attacker can trigger the same attack the moment the image tries to load, no form or JavaScript required.

What actually stops a CSRF attack from working?

A CSRF (anti-forgery) token: a random, unpredictable value the server embeds in the legitimate form and requires back on submission, tied to the user's session. Because the attacker's forged page has no way to read that token — it isn't stored in a cookie, so the automatic-cookie-attachment behavior that makes CSRF possible in the first place doesn't help the attacker retrieve it — the forged request arrives without a valid token and the server rejects it. Modern frameworks (Django, Rails, ASP.NET, Spring) generate and validate this token automatically for state-changing requests, which is why CSRF has become rarer in mainstream frameworks and shows up more often in custom-built APIs or legacy applications that opted out of the framework's default protections.

Are SameSite cookies enough on their own?

They substantially reduce the risk but aren't a complete substitute for tokens. SameSite=Strict or SameSite=Lax cookie attributes tell the browser not to attach the cookie on cross-site requests in most cases, which blocks the classic forged-form scenario described above. But SameSite=Lax still allows cookies on top-level GET navigations in many browsers, and older browsers or misconfigured proxies may not enforce the attribute consistently — so relying on SameSite alone, without a token, leaves a gap for state-changing GET requests and for any environment where the cookie policy isn't uniformly enforced. Defense in depth here means both: SameSite cookies as a baseline, anti-forgery tokens on every state-changing request as the actual guarantee.

Why do state-changing GET requests make this example worse?

Because a GET request needs no form submission, no JavaScript, and no user interaction beyond a resource loading — which is precisely why treating GET as "safe" for anything that changes state (transferring funds, changing a password, deleting a record) is a design mistake independent of CSRF tokens entirely. In the bank example, if the transfer endpoint also accepted GET /transfer?amount=500&to_account=attacker, an attacker wouldn't even need the victim to visit a malicious page with a form — a single img tag with that URL as its src, embedded in an email, a forum post, or an ad, would fire the request the instant the victim's browser tried to render the image, with no visible page load or click required. This is part of why REST and HTTP conventions treat GET as required to be side-effect-free: it isn't just a style preference, it's a security boundary that CSRF defenses partly depend on.

FAQ

Does CSRF affect APIs that use bearer tokens instead of cookies?

Generally no, if the token is sent in a custom header (like Authorization: Bearer ...) rather than a cookie — CSRF specifically abuses automatic, browser-managed credential attachment, and a header the client code has to set explicitly isn't attached automatically by a forged cross-site request the way a cookie is.

Is CSRF the same thing as XSS?

No, though they're often confused. XSS lets an attacker run arbitrary script in the victim's browser context; CSRF doesn't require running any script at all — it just relies on the browser's normal, automatic behavior of attaching session credentials to requests, regardless of which page triggered them.

Can a web application firewall catch CSRF on its own?

It can catch some obvious patterns, but CSRF requests are functionally identical to legitimate ones from the server's point of view — the fix belongs in the application logic (token validation), not in traffic filtering, which is why frameworks bake CSRF protection in rather than leaving it to network-layer tools.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.