CSRF Explained: How Your Cookies Can Land You in Peril
A technical walkthrough of Cross-Site Request Forgery — how the attack is structured, why browsers make it possible, and what actually stops it.
Every time you log into a website, your browser receives a session cookie. That cookie is your identity — it proves to the server that you are who you say you are. What most people do not realise is that browsers send that cookie automatically on every request to that domain, regardless of where the request originates. That single behaviour is the foundation of one of the most misunderstood attacks in web security: Cross-Site Request Forgery (CSRF).
This post walks through the anatomy of a CSRF attack using a two-application demo — a vulnerable notes app and a disguised attacker page. By the end, you will understand exactly how the attack is constructed, what makes it succeed, and what actually stops it.
What Is CSRF?
CSRF is an attack that tricks your browser into making a request to a web application you are already authenticated with — without your knowledge or consent.
The attacker does not steal your credentials. They do not break into your session. They simply craft a request that your browser sends on their behalf — with your cookie attached.
Three conditions must be true for a CSRF attack to succeed:
- The victim has an active session — they are logged in and their session cookie is live
- The browser sends cookies automatically on cross-site requests — the server’s cookie is not scoped to same-site requests only
- The server cannot distinguish a legitimate request from a forged one — there is no token, no origin check, nothing that proves the request came from the real application
Remove any one of these three conditions and the attack fails. The vulnerability in most cases comes down to condition two — and it is controlled by a single cookie attribute.
The Setup
The demo involves two applications running side by side:
| App | Role |
|---|---|
| The Notes App | The victim application — a simple HTTPS notes app where users can log in, create notes, and delete notes |
| The Attacker’s Page | A disguised malicious website dressed up as a news publication called “The Daily Chronicle” |
The victim logs into the Notes App. Their browser receives a session cookie. They then visit the Attacker’s Page — believing it to be a legitimate news site. That is where the attack begins.
The Vulnerability That Makes It Possible
The Notes App sets its session cookie with the following configuration:
cookie: {
sameSite: "none",
secure: true,
httpOnly: true,
}
sameSite: "none" is the critical setting. It instructs the browser to attach this cookie to every request sent to the Notes App’s domain — including requests that originate from a completely different website.
This single line disables the browser’s built-in CSRF protection. Modern browsers default to sameSite: "lax" when the attribute is not set, which blocks most cross-site POST requests. By explicitly setting "none", the app opts out of that protection entirely.
Anatomy of the Attack
Attack 1: Silent Note Creation
The Attacker’s Page contains a hidden form and a hidden iframe, both invisible to the visitor:
<div style="display:none">
<iframe name="csrf-sink"></iframe>
<form id="csrf-create"
action="https://notes-app.example.com/notes"
method="POST"
target="csrf-sink">
<input name="title" value="You've been pwned">
<input name="text" value="This note was silently created by a third-party site.">
</form>
</div>
The form targets the Notes App’s note creation endpoint directly. The iframe named csrf-sink is the response sink — any server response lands there, out of sight.
The moment the page finishes loading, JavaScript fires:
window.onload = function() {
document.getElementById('csrf-create').submit();
}
What happens next:
- The browser submits a
POSTrequest to the Notes App - Because
sameSite: "none"is set, the browser attaches the victim’s session cookie automatically - The Notes App receives what looks like a fully authenticated request
- A note is created on the victim’s account
- The response is captured by the hidden iframe — the victim sees nothing unusual
The entire attack completes in the time it takes the page to load. No clicks required.
Attack 2: Targeted Note Deletion
This attack requires the attacker to know the ID of a specific note they want to delete. The attacker crafts a URL containing that ID and sends it to the victim:
https://attacker-site.example.com/?noteId=<target-note-id>
When the Attacker’s Page loads with this parameter, it renders a second hidden form targeting the delete endpoint:
<form id="csrf-delete"
action="https://notes-app.example.com/notes/<target-note-id>/delete"
method="POST"
target="csrf-sink">
</form>
But this time, the attack does not fire automatically. Instead, the page displays a convincing newsletter subscription widget:
<form onsubmit="document.getElementById('csrf-delete').submit(); return false;">
<input type="email" placeholder="Enter your email address" required>
<button type="submit">Subscribe</button>
</form>
The victim sees a newsletter form. They type their email address — the required attribute on the email field makes it feel like a legitimate interaction. They click Subscribe.
The onsubmit handler intercepts the click and submits the hidden delete form instead. The victim’s note is deleted. The email address they typed is never sent anywhere. The visible form is pure social engineering — it exists only to get the victim to click.
The Full Attack Flow
1. Victim logs into the Notes App
└── Browser receives session cookie: sameSite=none
2. Attacker sends the victim a link to "The Daily Chronicle"
3. Victim visits the Attacker's Page
└── window.onload fires immediately
└── Browser submits POST to notes-app.example.com/notes
└── Session cookie attached automatically
└── Note created on victim's account
└── Response captured by hidden iframe — victim sees nothing
4. (Attack 2) Attacker sends crafted URL with ?noteId=<id>
└── Victim visits the URL, sees newsletter widget
└── Victim fills in email, clicks Subscribe
└── Browser submits POST to notes-app.example.com/notes/<id>/delete
└── Session cookie attached automatically
└── Note deleted silently
At no point does the attacker see the session cookie. They never need to. The victim’s own browser does all the work.
Vulnerabilities at Play
The following vulnerabilities in the Notes App contribute to the attack succeeding:
| # | Vulnerability | Impact |
|---|---|---|
| 1 | sameSite: "none" on the session cookie | Browser sends the session cookie on all cross-site requests — the root cause of the attack |
| 2 | No CSRF tokens on any state-changing form | The server has no way to verify a request originated from its own UI |
| 3 | No Origin or Referer header validation | The server performs no server-side check on where the request came from |
| 4 | Hardcoded credentials | Authentication can be trivially bypassed without needing to exploit CSRF at all |
| 5 | Weak session secret | Session cookies can be forged independently of the CSRF attack |
Vulnerabilities 1–3 are directly responsible for the CSRF attack succeeding. Vulnerabilities 4 and 5 are compounding issues that make the application more broadly insecure. A future post will walk through patching each of these in the demo app.
The Fix
The primary fix is a single line. Change the session cookie configuration from:
cookie: {
sameSite: "none", // sends cookie on all cross-site requests
secure: true,
httpOnly: true,
}
To:
cookie: {
sameSite: "strict", // cookie never sent on cross-site requests
secure: true,
httpOnly: true,
}
With sameSite: "strict", the browser will not attach the session cookie to any request that originates from a different domain. The forged POST from the Attacker’s Page arrives at the Notes App without a cookie. The server sees an unauthenticated request and rejects it. The attack fails entirely — before any other defence even comes into play.
For defence-in-depth, a hardened application would also implement:
- CSRF synchronizer tokens — a per-session secret embedded in every form and validated server-side
Originheader validation — reject any state-changing request whose origin does not match the expected domain- Double-submit cookie pattern — a secondary JS-readable token that cross-site forgers cannot access due to the Same-Origin Policy
The Core Lesson
CSRF is not about stealing credentials or breaking encryption. It exploits something the browser does by design — sending cookies automatically. The attacker never touches your session. They just need your browser to make one request on their behalf.
The victim’s browser becomes an unwitting proxy. The only things standing between a logged-in user and a silently forged request are the browser’s SameSite cookie policy, CSRF tokens the server validates, and origin checks. In the Notes App, all three are absent — and the results speak for themselves.