Anatomy of an Attack

Chapter 3
Anatomy of an Attack
Crafting the malicious page — GET forgery, POST forgery, and delivery

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.

Defensive framing — and a real place to practise legally
Everything here is for understanding and authorized testing of your own applications. Don't fire these at sites you don't own. To practise safely, use deliberately-vulnerable training apps built for it — OWASP Juice Shop, DVWA, or PortSwigger's Web Security Academy CSRF labs — which give you a legal target and guided exercises. The exercises in this chapter assume that kind of sandbox.

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:

<!-- fires the moment the page renders; victim sees nothing --> <img src="https://target.com/newsletter/unsubscribe?id=42" style="display:none">

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:

<body onload="document.forms[0].submit()"> <form action="https://target.com/email/change" method="POST"> <input type="hidden" name="email" value="attacker@evil.com"> </form> </body>

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.

Why attackers target email/password change specifically
A single forged "change email" request can be the whole ballgame: change the account's email to one the attacker controls, then use the normal "forgot password" flow to receive a reset link at that address. This chains a limited CSRF (one state change) into full account takeover. It's why account-security endpoints (change email, change password, add a recovery method) deserve the strongest CSRF protection and re-authentication (Chapter 8) — they're the highest-value targets.

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:

fetch("https://target.com/transfer", { method: "POST", credentials: "include", // needed to send the victim's cookies headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: "to=attacker&amount=5000" // simple content type = no 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 forgeryPOST forgery
Payload<img>, <script>, link, or even a bare URLauto-submitting hidden <form>
Needs JS?no — a tag alone fires itno — onload/no-JS submit works too
Requiresthe endpoint to change state on GET (a bug)any cookie-authorized POST endpoint
Stealthtrivial — hidden image, no navigationpage 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.
The victim doesn't need to be on the target site at the time
A persistent cookie means the victim is "logged in" to the target even when no tab for it is open. Loading the attacker's page in any tab is enough — the forged request still carries the stored session cookie. This is why "I logged out" matters (it invalidates the session) but "I closed the tab" generally doesn't. It also means CSRF doesn't require the victim to be actively using the target; mere ambient session existence suffices.

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

Exercise 1

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 solution
Exercise 2

Explain 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 solution
Exercise 3

An 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.

📄 View solution

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