The bugs that actually ship

Injection, broken authz, SSRF, XSS, CSRF, deserialisation. For each: how it happens, how to spot it in a review, and the fix that removes the class, not the instance.

● Must know for interviewsFREELesson·32 min read·Beginner → Advanced
OWASPinjectionSSRFXSSauthz

01Think in classes, not instances

✓What you will learn
  • Recognise the six bug classes that cause most incidents
  • Give the class-level fix for each, not a patch
  • Spot them in a code review in under a minute each

A fix that escapes one parameter is a patch. A fix that makes the unsafe API unavailable is a class fix. Interviewers and good reviewers look for the second kind.

ClassRoot causeClass-level fix
Injection (SQL, command, LDAP)Data concatenated into a parser's inputParameterised APIs only; lint-ban string-built queries
Broken object / function authzAuthz checked at the route, not the objectScope every query by tenant/owner; central policy engine
SSRFServer fetches a URL the user controlsEgress allow-list, metadata endpoint blocked, resolve-then-connect with IP checks
XSSUntrusted data rendered as HTMLAuto-escaping templates, CSP with nonces, no innerHTML
CSRFBrowser sends cookies cross-site automaticallySameSite cookies + anti-CSRF token for state changes
Insecure deserialisationDeserialising attacker-controlled objectsData-only formats (JSON) with schemas; never native object deserialisation of untrusted input

02SSRF: the cloud-era injection

Server-side request forgery is the bug behind several of the largest cloud breaches. A feature like "import from URL" or a webhook tester lets an attacker make your server call http://169.254.169.254/ and read cloud credentials, or scan the internal network.

Defending a URL fetcherts
// Vulnerable
const res = await fetch(req.body.url);

// Safer: resolve, check, then connect to the checked IP
const target = new URL(req.body.url);
if (!["https:"].includes(target.protocol)) throw new Error("scheme");
const ips = await dns.lookup(target.hostname, { all: true });
if (ips.some(({ address }) => isPrivateOrLinkLocal(address))) throw new Error("blocked range");
// Prefer: route all outbound fetches through an egress proxy with an allow-list,
// and enforce IMDSv2 (session tokens) so metadata cannot be read with a plain GET.
!DNS rebinding

Checking the IP then letting the HTTP client resolve again lets an attacker swap the answer between checks. Connect to the IP you validated, or use the egress proxy.

03A reviewer's checklist

  1. Where does untrusted input enter? Query, body, headers, file names, webhook payloads, third-party API responses.
  2. Where does it leave? Into SQL, a shell, HTML, a URL fetch, a log line, a file path.
  3. Is each exit through a safe API (parameterised, escaped, allow-listed)?
  4. Is every data read scoped to the caller?
  5. Are secrets absent from code, config files and logs?
  6. Do errors leak stack traces, SQL or internal hostnames?
“How to say it in the room

"I'd fix this instance, then I'd ask why it was possible: we have a raw query helper exposed. I'd deprecate it, add a lint rule, and grep for the other call sites. That removes the class."

Test yourself

Which control removes the XSS class rather than one instance?

Finished this one?
Tick it and it shows as done on the roadmap and the course.