Chapter 44 — Request forgery: CSRF, SSRF, clickjacking
In one sentence
These attacks abuse trust in a request's origin: CSRF rides the user's credentials from another site, SSRF makes your server fetch attacker-chosen URLs, and clickjacking tricks the user into clicking your UI through an invisible overlay.
CSRF — Cross-Site Request Forgery
The user is logged in to bank.example. They visit evil.example, which
contains:
<form action="https://bank.example/transfer" method="POST" id="f">
<input name="to" value="attacker"><input name="amount" value="5000">
</form>
<script>f.submit()</script>
The browser sends the POST to bank.example WITH the user's session
cookie (SOP allows sending, just not reading the reply). The transfer
goes through. The attacker never saw the response -- they didn't need to.
Defences (use layered):
1. SameSite cookies: SameSite=Lax (default in modern browsers) stops
cross-site POST/AJAX from carrying the cookie. SameSite=Strict for
high-value actions. NOT sufficient alone (top-level GET, some flows,
older clients, same-site-but-different-origin).
2. ANTI-CSRF TOKENS: a per-session (or per-request) random token in a
hidden field / custom header that the server verifies. The attacker's
cross-site page can't read it (SOP). Synchronizer-token or
signed double-submit pattern.
3. Custom header requirement for JSON APIs: require e.g.
X-Requested-With or a custom content type that a cross-site HTML form
cannot set -> forces a CORS preflight the attacker can't satisfy.
4. Re-authentication / step-up for sensitive actions.
5. Check Origin/Referer as defence in depth (not sole defence).
GET must never change state. A GET /transfer?... is CSRF-able with an
<img> tag and immune to most token schemes.
SSRF — Server-Side Request Forgery
Your server has a feature: "fetch the image at this URL", "import from
this webhook", "render this PDF from a URL". The attacker supplies:
http://169.254.169.254/latest/meta-data/iam/security-credentials/...
-> cloud instance metadata service -> STEALS cloud credentials
(this was the Capital One breach, 2019, ~100M records)
http://localhost:6379/ -> internal Redis, admin panels, k8s API
file:///etc/passwd -> local file read
http://[internal-only-service]/ -> pivot into the private network
gopher://... / dict:// -> smuggle arbitrary protocol payloads
Defences:
- Default DENY. Allowlist destination hosts/domains AND schemes
(https only). Deny by default; never blocklist.
- Resolve the hostname, check the RESULTING IP against RFC1918 /
loopback / link-local / IPv6 ULA / metadata IPs -- BEFORE connecting,
AND re-check after redirects (DNS rebinding: resolve twice, TOCTOU).
- Disable redirects, or re-validate every hop.
- Block the metadata endpoint at the network layer; use IMDSv2
(session-token, hop-limit 1) on AWS; equivalent hardening on GCP/Azure.
- Isolate the fetcher: a locked-down egress-filtered subnet / separate
service / no cloud role attached.
- No raw error/response passthrough (blind SSRF is still bad, but don't
hand the attacker the loot).
Clickjacking
evil.example loads bank.example in a transparent <iframe>, positions a
"You won! Click here" button exactly over bank.example's "Delete
account" / "Confirm payment" button. The user clicks the real button
without knowing.
FIX (both):
Content-Security-Policy: frame-ancestors 'none' (or 'self' / allowlist)
X-Frame-Options: DENY (legacy, still set it)
For UIs that MUST be embeddable, use frame-ancestors with a tight
allowlist and require user gestures / confirmations for destructive acts.
Related: open redirects and tabnabbing
OPEN REDIRECT: /login?next=https://evil.example -> after login the app
redirects off-site. Feeds phishing and can chain into OAuth token
theft / SSRF filter bypass. Fix: allowlist redirect targets, or only
allow relative paths.
REVERSE TABNABBING: target="_blank" links let the opened page rewrite
window.opener.location. Fix: rel="noopener noreferrer" (now default
in modern browsers, but set it).
Practice (35 min) — lab only
1. CSRF (Juice Shop / PortSwigger):
- Find a state-changing endpoint without a token. Host a tiny
auto-submitting form on a different origin (python -m http.server
on another port counts as cross-site if the app checks Origin).
- Add SameSite=Lax to the cookie; retry. Add a synchronizer token;
retry. Document what each layer blocked.
2. SSRF (PortSwigger has a dedicated track):
- Use a URL-fetch feature to hit 169.254.169.254 (their lab
simulates it) or an internal admin endpoint.
- Implement the allowlist + post-resolution IP check fix in a local
mini-app. Try to bypass with a redirect and with a DNS name that
resolves to 127.0.0.1 (use `lvh.me` / `127.0.0.1.nip.io`).
3. Clickjacking:
- Frame a local app in an <iframe> on another page. Overlay a button.
- Add frame-ancestors 'none'; watch the frame refuse to render.
Common confusions
- "SameSite=Lax killed CSRF, tokens are obsolete." Lax has gaps (top-level GET, method overrides, same-site subdomain attacker, non-cookie auth is unaffected). Keep tokens for state-changing endpoints.
- "CSRF matters for token/Authorization-header APIs." If auth is a non-cookie bearer token that the browser doesn't attach automatically, classic CSRF doesn't apply. It's cookies/Basic/NTLM/client-certs — ambient credentials — that make CSRF work.
- "SSRF is just an internal port scanner." It's credential theft (metadata), RCE (internal admin panels), and network pivot. Capital One was SSRF.
- "Blocklisting
localhostand127.0.0.1stops SSRF."0.0.0.0,[::1],2130706433(decimal),127.1, DNS rebinding, redirects, and IPv6 all bypass naive blocklists. Allowlist + post-resolution check. - "X-Frame-Options is enough." It's legacy and lacks allowlist nuance. Use
CSP
frame-ancestors; keep XFO for old browsers.
Check yourself
- Why can a cross-site form submit to your app with the user's cookie but the attacker still can't read the response — and why does CSRF work anyway?
- Name three CSRF defences and one gap in
SameSite=Lax. - What is the cloud metadata endpoint, and which breach was SSRF to it?
- Why is a blocklist the wrong approach to SSRF, and what's the right one (including the redirect/rebinding subtlety)?
- Which header (modern) stops clickjacking, and what's its value for "never frame me"?
Further reading
- Cheat sheets: OWASP "CSRF Prevention", "SSRF Prevention", "Clickjacking Defense".
- Course: PortSwigger Academy — CSRF and SSRF tracks (both excellent, free labs).
- Write-up: the Capital One breach (2019) technical analyses — SSRF → IMDSv1 → S3 exfiltration. Then read AWS's IMDSv2 announcement.
- Article: "SSRF bible" / Orange Tsai's SSRF talks (
blackhat/hitcon) — protocol smuggling and parser confusion.