Chapter 5 · Attacks & Defenses· Beginner· ~15 min

Web Vulnerabilities (OWASP Top 10)

The same handful of bugs has been responsible for most web breaches for two decades. Understand them once, recognize them everywhere.

You will learn

  • The mental model behind the OWASP Top 10
  • Five classic bug families in plain terms: injection, XSS, CSRF, broken auth, SSRF
  • What each one looks like, what it costs the victim, and what stops it
  • Why "sanitize input" is the wrong frame, and what to do instead

The Top 10 is a shortlist of patterns

The OWASP Top 10 is a community-maintained list of the most common categories of web vulnerability. It is not a checklist; it is a guidebook for what to expect. The categories get reshuffled every few years, but the underlying patterns barely change.

At a higher level, every web bug in the list is some variation of:

  • Data from one trust zone is treated as code or as truth in another.
  • The application forgets to enforce something on the server because the UI seems to enforce it.
  • The application trusts the wrong thing: the user, the network, the framework defaults.

Injection (the classic: SQL injection)

Injection happens when user input is glued into a command or query without being treated as data. The textbook example is SQL:

query = "SELECT * FROM users WHERE email = '" + email + "'"

If email is alice@x.com, fine. If it's ' OR 1=1; --, the database happily returns every user.

The fix is parameterized queries: the structure of the SQL is sent separately from the values, so user input can never become part of the command. This is a one-line change in every modern library, and it solves the entire class of problem.

"Sanitize input" is a trap

The correct framing isn't "clean the dangerous characters". It's "keep user data in the data slot, not the command slot." Parameterized queries, template engines that auto-escape, and prepared statements all do this for you.

Cross-Site Scripting (XSS)

XSS is injection's twin in the browser. The site echoes user-controlled data into the page without escaping it; that data turns out to be HTML or JavaScript; the script runs in another user's session.

From there an attacker can steal cookies, hijack actions, deface the page, or redirect. Three flavors:

  • Reflected: the malicious string is in the URL.
  • Stored: the string is saved (a comment, a profile field) and runs for every visitor.
  • DOM-based: the bug lives in the client-side JavaScript, which builds DOM from URL/fragment data unsafely.

Defenses, in roughly the order they matter:

  1. Use a template system that escapes by default. Almost every modern framework does.
  2. Set a Content Security Policy that restricts where scripts may come from.
  3. Mark sensitive cookies HttpOnly so script can't read them.
  4. Avoid innerHTML with untrusted strings; prefer textContent or trusted-types APIs.

Cross-Site Request Forgery (CSRF)

CSRF exploits the fact that the browser automatically sends your cookies to the site they belong to. A malicious page that submits a form to bank.example.com rides on whatever session you already have there.

Defenses:

  • SameSite cookies: the modern default. Lax already blocks most of this; Strict blocks more at a UX cost.
  • CSRF tokens: a per-form, per-session secret the server checks on submit.
  • Origin / Referer checks on state-changing requests.

Broken access control

The most boring and most expensive bug class. The server enforces permissions in the UI (hides a button) but not in the API (the endpoint still works if you call it directly). Or, two users' URLs differ by an ID and there's no check that the caller owns that ID. This is called IDOR (Insecure Direct Object Reference).

The fix is conceptual, not technical: every server endpoint must check, on every call, "does the current identity have the right to do this to this resource?" No amount of frontend cleverness substitutes.

Server-Side Request Forgery (SSRF)

SSRF is when the server makes outbound requests to a URL the attacker controls. The attacker uses the server as a proxy into places they couldn't reach themselves: internal services, cloud metadata endpoints (169.254.169.254), or external targets to which the server's IP is allow-listed.

Defenses include strict allow-lists of destinations, blocking link-local and private ranges, and never assuming the network is the boundary.

Frameworks did most of the work

Modern frameworks (Rails, Django, Express with sensible middleware, Next.js, Laravel) default to safe behavior for most of these. Most modern XSS, SQLi, and CSRF bugs are where developers stepped off the framework's defaults.

Take the quiz