Anatomy of an Attack
With the mechanism understood (Chapters 1–2), this chapter is the attacker's toolkit — assembled here so you can recognize and test for these patterns defensively, which is the whole point of studying them. We'll build each forgery type from simplest to most capable, note what each requires of the target endpoint, and look at how attacks are delivered.
Level 1: GET Forgery with an Image Tag
The simplest possible attack needs no form and no script — just a state-changing endpoint exposed over GET. A single tag on any page the victim loads fires a credentialed request:
Because <img> issues a credentialed GET (Chapter 2), the browser attaches the victim's cookies and the unsubscribe happens. display:none hides the broken-image icon so there's no visual tell. This works only because the endpoint violated the rule that GET must be side-effect-free — which is exactly why that rule exists.
Level 2: POST Forgery with an Auto-Submitting Form
Most state changes are (correctly) POST, which an <img> can't produce. The standard attack is a hidden form that submits itself on load — the canonical CSRF payload:
The form's action points cross-site to the target; hidden inputs supply the attacker-chosen parameters; onload auto-submits so no click is needed. Changing the victim's account email is a favourite because it often enables a follow-up password reset — a foothold for full account takeover.
Level 3: Forgery via fetch (and its limits)
An attacker can also use fetch, but Chapter 2's rules bite here. To send cookies cross-site they must opt in, and they're confined to "simple" requests or they'll trigger a blocking preflight:
Note what the attacker cannot do: set a custom header, or send application/json — either triggers a preflight their origin can't pass. So fetch gives them no more power than a form for classic targets; the form is usually simpler and works even with scripting disabled. This limitation is the seed of several defences.
GET vs POST Forgery, Side by Side
| GET forgery | POST forgery | |
|---|---|---|
| Payload | <img>, <script>, link, or even a bare URL | auto-submitting hidden <form> |
| Needs JS? | no — a tag alone fires it | no — onload/no-JS submit works too |
| Requires | the endpoint to change state on GET (a bug) | any cookie-authorized POST endpoint |
| Stealth | trivial — hidden image, no navigation | page briefly navigates unless framed/hidden |
Delivery: Getting the Victim to the Payload
The forgery only fires if the logged-in victim loads the attacker's page. Common delivery vectors:
- A link in an email, message, or forum post leading to the attacker's page.
- A malicious or compromised ad (malvertising) on an otherwise-legitimate site.
- An embedded resource — for GET attacks, the payload can be an
<img>posted into any site that allows user images (a forum signature, a comment). - The victim simply being logged in elsewhere — they don't need to "do" anything beyond having an active session and opening the page.
What the Attacker Still Can't Do
Keeping the limits sharp (from Chapter 2) helps you scope risk correctly:
- Can't read responses — so they can't exfiltrate data this way; CSRF is about causing actions.
- Can't forge requests needing unpredictable values — a secret token they can't guess blocks the request entirely (Chapter 5).
- Can't set custom headers or JSON bodies cross-site without a preflight — limiting attacks to simple requests.
- Can't act if the cookie isn't sent — SameSite (Chapter 7) can strip the credential from the forged request.
Each "can't" is a defence the rest of the course makes concrete. Next we clear up the most-confused boundary in web security: CSRF versus XSS versus SSRF.
Hands-On Exercises
On a deliberately-vulnerable training app (Juice Shop / DVWA / PortSwigger labs), build a Level-1 GET-forgery payload and a Level-2 auto-submitting POST payload against a state-changing endpoint. For each, state exactly which endpoint property made it possible.
📄 View solutionExplain how a single forged "change email address" request can escalate into full account takeover. Then explain why an attacker can perform the change but cannot directly read the confirmation response, and why that doesn't save the victim.
📄 View solutionAn attacker tries a cross-site fetch to POST a JSON body with a custom X-Requested-With header to the target. Walk through what the browser does and why this forgery fails — then explain what it would (wrongly) take for the target to be safe relying on that alone.
Chapter 3 Quick Reference
- GET forgery — a hidden
<img>/tag fires a credentialed GET; works only against a state-changing GET (a bug) - POST forgery — an auto-submitting hidden
<form>(onload submit); the canonical CSRF payload, no click needed - fetch forgery — needs
credentials:'include'and is confined to simple requests (no custom headers / JSON without a preflight) - Change-email / change-password endpoints are prime targets — one forged change can chain to account takeover
- Delivery — links, malvertising, embedded
<img>in user content; victim just needs an active session + to load the page - Closing a tab doesn't help (persistent cookie); logging out does (invalidates the session)
- Attacker still can't: read responses · forge unpredictable tokens · set custom headers/JSON cross-site · act if the cookie isn't sent
- Practise only on owned/training targets — Juice Shop, DVWA, PortSwigger Academy
- Next chapter: CSRF vs XSS vs SSRF — untangling the three most-confused web-security acronyms