With authentication, authorization, and injection covered, we reach the attacks that are specific to the web and the browser. Three of them account for an enormous share of real-world web vulnerabilities, and they’re easy to confuse with each other, so this lesson treats them as a set: XSS, CSRF, and SSRF. For each, the structure is the same — what it is, why it’s dangerous, and the concrete headers and flags that stop it. The encouraging theme is that a small, well-known set of defenses (output encoding, a few HTTP headers, some cookie flags) handles most of this.
XSS: Your Attacker’s JavaScript, Running as Your User
Cross-Site Scripting (XSS) is injection (Lesson 4) aimed at the browser: attacker-controlled data ends up being interpreted as JavaScript in another user’s browser. Once that script runs, it runs as that user, inside your site’s origin — so it can read anything the page can, steal session tokens (unless they’re in HttpOnly cookies), make requests as the user, capture keystrokes, or rewrite the page to phish. It’s dangerous precisely because it turns your trusted site into a delivery mechanism for the attacker’s code.
It comes in three flavours worth recognising:
- Stored XSS — the payload is saved in your database (a comment, a profile bio, a product review) and runs for every user who views it. The most damaging kind, because it’s persistent and can hit many victims, including admins.
- Reflected XSS — the payload is in a URL or parameter that your page echoes back into the HTML, running when a victim clicks a crafted link. One victim per link, delivered by phishing.
- DOM-based XSS — the payload never reaches the server; client-side JavaScript takes untrusted input (say, from the URL fragment) and writes it unsafely into the page.
The defenses layer together, and the first one does most of the work:
- Output-encode by context. The core fix: when you render untrusted data into a page, encode it for where it’s going, so that
<script>becomes the harmless text<script>rather than a live tag. Crucially, the encoding depends on context — HTML body, HTML attribute, JavaScript, URL, and CSS each need different encoding. The great news is that modern frameworks (React, Angular, Vue, and server templating engines) auto-encode by default, which is why XSS is less rampant than it once was. Your job is mostly to not defeat that protection. - Content-Security-Policy (CSP). This HTTP header tells the browser which sources of scripts are allowed to run, so even if an injection slips through, the browser refuses to execute unauthorised inline or external scripts. A strong CSP is a powerful second net — it turns many would-be XSS bugs into nothing.
- HttpOnly cookies. Keep session tokens in HttpOnly cookies (Lesson 2) so that even if a script runs, it can’t read the token — removing the most valuable prize.
- Avoid dangerous sinks. Don’t build HTML by string concatenation; avoid
innerHTML,document.write, and React’sdangerouslySetInnerHTML(the name is a warning). When you must render user-supplied HTML (a rich-text comment), run it through a vetted sanitizer library — never your own regex.
CSRF: Riding Your User’s Login
Cross-Site Request Forgery (CSRF) is sneakier. The attacker doesn’t steal anything directly; instead they get the victim’s own browser to send a request to your app, which rides along on the victim’s existing logged-in cookie. The victim visits a malicious page (or an ad, or an image in an email); that page quietly submits a form to yourbank.com/transfer; the browser dutifully attaches the victim’s auth cookie; and your server, seeing a valid session, processes the transfer the victim never intended.
The defenses are well-established:
- SameSite cookies. Setting
SameSite=Lax(a sensible default, now the browser default) orStricttells the browser not to send your cookie on cross-site requests, which neutralises the classic CSRF vector almost for free. This single flag is the biggest win. - Anti-CSRF tokens. For state-changing requests, require an unpredictable token that your server issued and the attacker’s page can’t know. The legitimate form includes it; the forged request can’t. Most web frameworks have this built in — turn it on.
- Check
Origin/Refereron sensitive actions as an additional signal. - Make sure state-changing actions use POST/PUT/DELETE, not GET — a side effect behind a GET can be triggered by merely loading an image URL.
SSRF: Turning Your Server Into the Attacker
Server-Side Request Forgery (SSRF) flips the victim. Here the attacker tricks your server into making an HTTP request to a destination the attacker chooses. If any feature takes a URL from the user and fetches it — “import from URL,” webhooks, link previews, an image proxy, a PDF renderer — an attacker can point it inward: at your internal admin services, your databases, or, most infamously, the cloud metadata endpoint at 169.254.169.254, which on a misconfigured cloud instance hands out temporary credentials. SSRF has been behind major cloud breaches for exactly this reason: your server sits inside the trusted network, so making it send the request bypasses your perimeter.
Defenses:
- Don’t let users supply raw fetch URLs where you can avoid it. If you must, allowlist the permitted destinations (specific domains/paths) rather than trying to block bad ones.
- Block requests to private and link-local IP ranges (
10.x,192.168.x,127.x,169.254.x) — and do it after DNS resolution, because a hostname can resolve to an internal IP (and beware DNS-rebinding, which changes the answer between your check and your fetch). - Require IMDSv2 on AWS (which blocks the simplest metadata grab) and generally isolate egress so your app servers can’t freely call internal services.
The Memory Hook: Who Gets Tricked?
Because CSRF and SSRF sound alike and both involve forged requests, people mix them up and apply the wrong fix. Anchor on who is tricked into sending the request:
- CSRF abuses the user’s browser — the victim is your application, made to act on the user’s behalf without their intent. Fix at the cookie/token level (SameSite, CSRF tokens).
- SSRF abuses your server — the victim is your internal network. Fix at the egress/allowlist level (block internal IPs, allowlist destinations).
Both share the deeper theme from Lesson 1: never let an attacker decide who sends a request, on whose behalf, to where.
A Few More Headers Worth Setting
While we’re on HTTP headers, set these across your app (a helmet-style middleware does it in one line):
Strict-Transport-Security(HSTS) — forces HTTPS, preventing downgrade and cookie-stealing over plain HTTP (Lesson 6).X-Content-Type-Options: nosniff— stops the browser from “guessing” a response is a script.X-Frame-Options/ CSPframe-ancestors— prevents clickjacking (your site being framed invisibly over a malicious page).- A sensible CSP (above) ties much of this together.
These are cheap, set-once wins that raise your baseline considerably.
Pitfalls to Avoid
- Defeating your framework’s auto-encoding. Reaching for
innerHTML/dangerouslySetInnerHTML/ raw template output re-opens XSS. Stay on the safe rendering path; sanitize rich HTML with a library. - No CSP. Without it, one injection runs freely. A strong Content-Security-Policy is a powerful second net.
- Tokens in localStorage. XSS then steals them. Use HttpOnly cookies so scripts can’t read them.
- State-changing GET requests, no SameSite. Invites CSRF. Use SameSite cookies, CSRF tokens, and non-GET methods for side effects.
- Fetching user-supplied URLs without restriction. Classic SSRF. Allowlist destinations and block internal/link-local IPs after DNS resolution.
- Forgetting the cloud metadata endpoint. SSRF’s favourite target. Require IMDSv2 and isolate egress.
- Skipping the cheap security headers. HSTS, nosniff, frame protections, and CSP are one-line baseline wins.
Quick Recap
- XSS runs the attacker’s JavaScript as your user. Defend with context-aware output encoding (frameworks do this), a strong CSP, HttpOnly cookies, and avoiding dangerous HTML sinks.
- CSRF rides the user’s login from a malicious site. Defend with SameSite cookies, anti-CSRF tokens, Origin checks, and non-GET state changes.
- SSRF tricks your server into attacking your internal network or cloud metadata endpoint. Defend by allowlisting destinations, blocking internal IPs, and requiring IMDSv2.
- Memory hook: CSRF abuses the user’s browser; SSRF abuses your server.
- Set the cheap baseline headers (HSTS, nosniff, frame protections, CSP) everywhere.
Next lesson: the subject everyone fears and most get wrong by trying to be clever — cryptography. We’ll sort out hashing vs encryption vs signing, understand TLS, and learn the one rule that keeps you safe: don’t roll your own.