contentintech
Learn/cybersecurity/OWASP Top 10
Intermediate~25 min read

OWASP Top 10

The OWASP Top 10 web application risks explained with concrete examples and defensive remediation for each category.

OWASPInjectionAccess ControlAppSec

The OWASP Top 10 (2021)

The OWASP Top 10 is a community-driven awareness document from the Open Worldwide Application Security Project that ranks the most critical web application security risks. The 2021 edition remains the current baseline in 2026 and is organized around root-cause categories rather than individual bugs. This guide summarizes each category defensively: what it is, a concrete example, and the fix. Practice everything in dedicated labs (WebGoat, Juice Shop, PortSwigger Academy) — never against systems you do not own or have written authorization to test.

How to use the Top 10

Treat it as a checklist for threat modeling and code review, not an exhaustive list. Map each category to concrete controls in your stack, then verify with automated scanning plus manual testing in a lab.

A01 — Broken Access Control

The most common category. Users act outside their intended permissions: viewing others' records (IDOR), escalating role, or accessing admin functions. The root cause is trusting client-supplied identifiers or hiding functionality instead of enforcing authorization server-side.

// VULNERABLE (IDOR): any authenticated user reads any invoice by ID
app.get('/api/invoices/:id', requireLogin, async (req, res) => {
  const inv = await db.invoice.findById(req.params.id);
  res.json(inv);
});

// SECURE: scope every query to the authenticated principal
app.get('/api/invoices/:id', requireLogin, async (req, res) => {
  const inv = await db.invoice.findOne({
    id: req.params.id, ownerId: req.user.id, // server-side authz
  });
  if (!inv) return res.status(404).end();
  res.json(inv);
});

Fix: deny by default, enforce ownership/role checks on the server for every request, and prefer opaque or per-user-scoped identifiers.

A02 — Cryptographic Failures

Sensitive data exposed through missing or weak cryptography: plaintext transport, weak/absent password hashing, hard-coded keys, or outdated algorithms (MD5, SHA1, DES).

Weak Modern replacement
MD5/SHA1 for passwordsArgon2id (or bcrypt/scrypt)
HTTP / mixed contentTLS 1.3 + HSTS
DES / ECB modeAES-256-GCM (authenticated)
Keys in source codeKMS / secrets manager

A03 — Injection

Untrusted input is interpreted as code or a command by an interpreter (SQL, NoSQL, OS, LDAP, ORM). SQL injection is the classic case.

// VULNERABLE: string concatenation into SQL
const q = "SELECT * FROM users WHERE email = '" + email + "'";
db.query(q);
// email = "' OR '1'='1" returns every row.

// SECURE: parameterized query / prepared statement
db.query('SELECT * FROM users WHERE email = ?', [email]);
// The driver sends data separately from code; input is never parsed as SQL.

Fix: always use parameterized queries or a safe ORM, validate input against allowlists, and escape only as a last resort in contexts parameterization cannot cover.

A04 — Insecure Design

Flaws baked into the architecture before a line of code is written — missing rate limits, no threat model, business logic that trusts the client. You cannot patch an insecure design with better implementation. Fix: threat model early, define security requirements and abuse cases, use secure design patterns and reference architectures.

A05 — Security Misconfiguration

Default accounts, verbose error messages, unnecessary features enabled, missing security headers, open cloud storage. Fix: harden with a repeatable baseline, disable defaults, automate configuration (IaC), minimize the attack surface, and verify with configuration scanners.

A06 — Vulnerable & Outdated Components

Using libraries, frameworks, or runtimes with known CVEs. Fix: maintain an inventory/SBOM, run dependency scanning in CI, remove unused dependencies, and patch on a defined cadence.

npm audit --production        # Node
pip-audit                     # Python
osv-scanner -r .              # multi-ecosystem (Google OSV)
# Wire these into CI and fail the build on known-high CVEs.

A07 — Identification & Authentication Failures

Weak credentials, missing brute-force protection, broken session handling, credential stuffing exposure. Fix: enforce strong password policies checked against breach lists, add MFA, apply rate limiting and account lockout/backoff, and regenerate session IDs on login.

A08 — Software & Data Integrity Failures

Trusting code or data whose integrity is not verified: unsigned updates, compromised CI/CD pipelines, untrusted plugins, and insecure deserialization that lets attacker-controlled objects execute code. Fix: verify signatures and checksums, pin and lock dependencies (integrity hashes), secure the build pipeline (SLSA), and avoid deserializing untrusted data — prefer plain data formats like JSON with schema validation.

A09 — Security Logging & Monitoring Failures

Without adequate logging and alerting, breaches go undetected. Fix: log authentication events, access-control failures, and input-validation failures with enough context (never secrets/PII in cleartext), centralize logs, set alerts, and rehearse incident response.

Log the security-relevant, not the sensitive

Record who did what and whether it was allowed; never log passwords, tokens, or full card numbers. Ensure logs are tamper-resistant and retained long enough for investigations.

A10 — Server-Side Request Forgery (SSRF)

The server is coerced into making requests to unintended destinations — often internal services or the cloud metadata endpoint (169.254.169.254) — because it fetches a user-supplied URL without validation.

// SECURE: allowlist + block internal ranges + no redirects to internal
const ALLOWED_HOSTS = new Set(['images.example.com']);
const url = new URL(userUrl);
if (url.protocol !== 'https:' || !ALLOWED_HOSTS.has(url.hostname)) {
  throw new Error('blocked');
}
// Resolve DNS and reject private/link-local IPs; disable redirects.

Fix: validate against a strict allowlist, block private/link-local/metadata ranges, disable unnecessary URL schemes and redirects, and enforce network egress controls (require IMDSv2 in AWS).

Summary: All 10 at a Glance

Category Typical cause Primary fix
A01 Broken Access ControlClient-trusted IDs, no authzDeny by default, server-side checks
A02 Cryptographic FailuresWeak/absent cryptoTLS 1.3, Argon2id, KMS
A03 InjectionInput parsed as codeParameterized queries
A04 Insecure DesignNo threat modelingDesign reviews, abuse cases
A05 Security MisconfigurationDefaults, verbose errorsHardened baseline, IaC
A06 Vulnerable ComponentsOutdated deps with CVEsSBOM, dependency scanning
A07 Auth FailuresWeak creds, no brute-force limitMFA, rate limiting
A08 Integrity FailuresUnsigned code, deserializationSignatures, locked deps, no untrusted deser.
A09 Logging FailuresNo visibility/alertingCentralized logs + alerts
A10 SSRFUnvalidated URL fetchAllowlist, block internal ranges

Practice Exercises

  1. Work through the access-control and injection lessons in WebGoat and the corresponding challenges in OWASP Juice Shop, then write the server-side fix for each.
  2. Take a deliberately vulnerable SQL query in a local test app and convert it to a parameterized query; confirm that classic ' OR '1'='1 style input no longer alters results.
  3. Add access-control tests (an owner and a non-owner user) to your test suite and verify a non-owner receives 403/404 for another user's resource (IDOR regression test).
  4. Run a dependency audit with npm audit / osv-scanner, upgrade a flagged package, and re-run to confirm it is resolved.
  5. Add security logging for login success/failure and access-control denials to a test app, then trigger events and confirm they appear with useful context (and no secrets).
  6. Complete the PortSwigger Web Security Academy SSRF labs, then implement an allowlist plus private-range blocking in a local URL-fetching endpoint and verify metadata-endpoint access is blocked.

Section navigation