Lesson 1 of 6
Web app vulns & the OWASP Top 10
Shut down the web's most-exploited bugs — SQLi, XSS, broken access control, and SSRF — with the durable fixes attackers can't sidestep.
① Watch
Speed
📖 Read this walkthrough — every command, and why
OWASP Top 10 (2021) — a defender's checklist
Mental model: this list catalogs the web's most common flaws, and almost every one
traces back to a single mistake — trusting input, or a client, that you never actually
verified. The durable fix for the whole class is the same: push trust back to the server,
validate what comes in, and default to secure. What follows is each risk in one line plus
the DEFENSE. Defensive only — no exploit recipes.
A01 · Broken Access Control
Risk: the server lets a user reach data or actions they were never authorized for.
Defense: enforce authorization SERVER-SIDE on every request, and DENY BY DEFAULT.
- Re-check authz for this exact user against this exact object, every time.
- A signed token is not proof of authorization. A JWT is signed, not encrypted —
anyone can base64url-decode its claims and read them:
payload: {"sub":"alice","role":"user","iss":"sprkd","exp":1893456000}
The "role":"user" claim is readable by ANYONE and can be forged in transit, so
never trust it on its word — the signature only proves it wasn't tampered with,
it does not decide what this user may touch. The server still makes the call.
- Object-level checks stop IDOR: don't let /api/invoices/1041 -> 1042 return
another tenant's record just because the id was edited. Ownership is checked
server-side; obscuring or encrypting the id is NOT a substitute for the check.
A02 · Cryptographic Failures
Risk: sensitive data left in the clear, or protected with weak or home-grown crypto.
Defense: TLS everywhere, strong standard algorithms, and NEVER roll your own crypto.
- Encrypt data in transit (TLS) and at rest; use vetted, current algorithms.
- No DIY schemes, no weak/legacy ciphers, no hard-coded keys.
- Never put secrets in a JWT payload — it is readable by anyone who holds the token.
A03 · Injection
Risk: untrusted input is pasted into a query or command, so the engine can't tell
your data from its instructions.
Defense: PARAMETERIZE — bind values as parameters; never concatenate untrusted input.
Bad (string concatenation — input can alter the query):
db.query("SELECT * FROM users WHERE email = '" + input + "'")
Safe (parameterized / prepared — input is bound as a value, never as SQL):
db.query("SELECT * FROM users WHERE email = ?", [input])
Why it works: prepared statements send the SQL structure and the data separately,
so the query's shape is fixed and input can never become executable SQL. Escaping
quotes and blacklisting keywords like DROP/UNION are brittle and bypassable;
parameterization removes the injection surface entirely. Same rule for shell
commands, ORMs, and any other interpreter.
Related: XSS is the output side of the same idea — it's an output-encoding problem,
fixed by context-aware encoding of untrusted data where it's rendered (with a strict
Content-Security-Policy as defense in depth).
A04 · Insecure Design
Risk: whole classes of threats the design never planned for — no defense was ever built.
Defense: threat-model EARLY; bake security into the design, not on as an afterthought.
- Ask "what could go wrong here?" before you build, per feature and trust boundary.
- Least privilege and secure patterns by default; don't rely on later patching.
A05 · Security Misconfiguration
Risk: default passwords, open buckets, unneeded features, verbose error messages.
Defense: ship SECURE DEFAULTS — expose nothing you didn't intend.
- Change/remove defaults, close unused ports and features, lock down storage.
- Least privilege; suppress stack traces and internal detail in error responses.
A06 · Vulnerable and Outdated Components
Risk: outdated libraries and images carrying known, published CVEs.
Defense: SCAN and PATCH — know what you ship and keep it current.
- Inventory dependencies (SBOM), scan images/deps for known CVEs, patch on a cadence.
- Pin images by sha256 digest so what you scanned is what you run.
A07 · Identification and Authentication Failures
Risk: weak or missing MFA, guessable credentials, sloppy session handling.
Defense: harden login with MFA and manage sessions correctly.
- Require multi-factor auth; block weak/breached passwords; rate-limit attempts.
- Rotate and expire session tokens; invalidate on logout; use HttpOnly, Secure,
SameSite cookies. (Note: CSRF is a separate risk — a forged authenticated request
the browser sends automatically — defended by anti-CSRF tokens and SameSite,
not by output encoding.)
A08 · Software and Data Integrity Failures
Risk: pipelines and updates that trust unsigned code or data from unverified sources.
Defense: VERIFY sources and SIGNATURES before you run anything.
- Check signatures on code, dependencies, and updates (e.g. Sigstore/SLSA).
- Trust only known registries; verify integrity of anything pulled into the build.
A09 · Security Logging and Monitoring Failures
Risk: you can't respond to an attack you never saw.
Defense: LOG security events, monitor them, and ALERT on anomalies.
- Log authn, authz, and high-value actions with enough context to investigate.
- Ship logs somewhere tamper-resistant; alert on suspicious patterns; never log secrets.
A10 · Server-Side Request Forgery (SSRF)
Risk: the server is tricked into calling internal or unintended addresses on an
attacker's behalf.
Defense: VALIDATE and ALLOWLIST outbound destinations.
- Allowlist the hosts the server may call; deny by default.
- A string denylist (blocking 127.0.0.1, 169.254.169.254) is insufficient — alternate
encodings, IPv6/decimal forms, DNS rebinding, and redirects slip past it. Resolve
and pin the address, block private/link-local ranges, disallow redirects, and
isolate egress.
The recurring root cause: trusting untrusted input.
The recurring defenses: validate input, parameterize, enforce least privilege and
server-side authz, and default to secure. Get those right and you close whole classes
of these bugs at once.
Notes:
- A JWT is signed, not encrypted — claims are public; the signature only proves the
token wasn't tampered with. It does not authorize the action; the server must.
- Parameterize instead of escape/blacklist — the latter are brittle and bypassable.
- IDOR is broken access control, not a crypto or injection bug — fix it with a
server-side ownership check, not by hiding or encrypting the identifier.
- SSRF and injection share a lesson: never trust the input's own claims about itself.
Verify:
- Decode a JWT's payload and confirm the claims are plainly readable (base64url) —
proof they must never carry secrets and can't be trusted for authz.
- Try id-swapping a resource URL against an account that shouldn't own it; the server
must return 403/404, not the record.
- Run a dependency/image CVE scan (e.g. trivy) and confirm it flags known-vulnerable
versions before you ship.