Frontend Labs
⋯

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.
   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 localhost and 127.0.0.1 stops 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

  1. 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?
  2. Name three CSRF defences and one gap in SameSite=Lax.
  3. What is the cloud metadata endpoint, and which breach was SSRF to it?
  4. Why is a blocklist the wrong approach to SSRF, and what's the right one (including the redirect/rebinding subtlety)?
  5. 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.

Generated from real-life-guide.md · 8 Oct 2026 16:36