The OWASP Top 10 is a prioritization list of the most exploited web application risks — not a compliance checkbox, but a starting point for code review and threat modeling. The 2021 edition (still the current reference widely used in 2026) renamed and merged categories to reflect how applications are actually breached: broken access control and cryptographic failures dominate real incidents more than exotic zero-days. This guide maps each category to concrete vulnerabilities, shows vulnerable vs fixed code, and gives you a remediation checklist you can apply this sprint.
A01: Broken Access Control
Users act outside their intended permissions — viewing another account's invoice, calling admin APIs as a regular user, or changing userId in a JSON body.
Vulnerable:
app.get('/api/invoices/:id', async (req, res) => {
const invoice = await db.invoice.findById(req.params.id);
res.json(invoice); // Any authenticated user can read any invoice
});Fixed — enforce ownership on every object access:
app.get('/api/invoices/:id', requireAuth, async (req, res) => {
const invoice = await db.invoice.findOne({
where: { id: req.params.id, tenant_id: req.user.tenantId }
});
if (!invoice) return res.status(404).end();
res.json(invoice);
});Rules:
- Deny by default; allow explicitly per role and resource
- Use server-side checks — never rely on hiding UI buttons
- Invalidate sessions on logout; short-lived access tokens
- Log access control failures and alert on spikes
Horizontal privilege escalation (user A → user B's data) is the most common finding in pen tests.
A02: Cryptographic Failures
Sensitive data exposed because encryption, hashing, or TLS was missing or misapplied. This replaces the old "Sensitive Data Exposure" category.
Password storage — never MD5 or SHA-1:
# BAD
hashlib.md5(password.encode()).hexdigest()
# GOOD — bcrypt, Argon2, or scrypt via library defaults
from argon2 import PasswordHasher
ph = PasswordHasher()
stored = ph.hash(password)Generate test passwords for staging accounts with /tools/password-generator; never reuse production password policies in seed scripts with weak defaults.
Data in transit: enforce TLS 1.2+ everywhere, including service-to-service calls. HSTS header on public sites:
Strict-Transport-Security: max-age=31536000; includeSubDomainsData at rest: encrypt database volumes and backups; application-level encryption for especially sensitive fields (SSN, bank tokens).
Inspect JWTs in dev with /tools/jwt-decoder — confirm algorithms are RS256/ES256, not none, and that PII is not stuffed into payload claims logged by proxies.
A03: Injection
Untrusted input interpreted as code — SQL, NoSQL, OS commands, LDAP, template engines.
SQL injection — vulnerable:
const q = `SELECT * FROM users WHERE email = '${req.body.email}'`;Parameterized query:
const user = await db.query(
'SELECT id, role FROM users WHERE email = $1',
[req.body.email]
);NoSQL injection (MongoDB):
// Attacker sends { "email": { "$gt": "" }, "password": { "$gt": "" } }
const user = await User.findOne(req.body); // BAD
const user = await User.findOne({
email: String(req.body.email),
password: hashedInput
});Use ORM/query builders with bound parameters. Escape output only at the presentation layer — escaping on input is not a substitute for parameterization.
A04: Insecure Design
Flaws in architecture and business logic, not implementation typos. Examples: password reset tokens that never expire, coupon codes stackable without limit, race conditions on account balance withdrawals.
Mitigate at design time:
- Threat modeling (STRIDE) before coding payment or auth flows
- Rate limits on sensitive actions (login, reset, OTP verify)
- Idempotency keys on financial operations
- Separation of duties for admin approvals
No single code snippet fixes insecure design — document abuse cases in user stories ("attacker tries to redeem gift card twice concurrently").
A05: Security Misconfiguration
Default credentials, verbose error pages, open cloud storage buckets, unnecessary features enabled (directory listing, admin consoles on public URLs).
Hardening checklist:
// Express — disable x-powered-by, set security headers
app.disable('x-powered-by');
app.use(helmet());
// Never expose stack traces to clients
app.use((err, req, res, next) => {
logger.error({ err, requestId: req.id });
res.status(500).json({ error: 'internal_error', request_id: req.id });
});Review cloud IAM monthly. Remove unused S3/GCS public ACLs. Change default admin passwords before deploy — automated scanners find admin:admin in minutes.
A06: Vulnerable and Outdated Components
Known CVEs in frameworks, libraries, and container images. Supply chain attacks increased sharply — pinning dependencies is not enough; you need visibility.
Actions:
- Dependabot, Renovate, or Snyk in CI
npm audit,pip audit, OS package updates on base images- Remove unused dependencies (smaller attack surface)
- Verify package integrity (lockfiles, signed commits on tags)
Block merges when critical CVEs affect production code paths.
A07: Identification and Authentication Failures
Broken login, session management, and credential recovery — partially merged from the old "Broken Authentication."
Failures include:
- Weak password policy with no breach list check
- Session tokens in URLs
- Missing MFA on admin accounts
- Credential stuffing succeeds because login lacks rate limiting
Session cookie flags:
Set-Cookie: sid=...; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=3600Implement multi-factor authentication for privileged roles. After password change, revoke all refresh tokens. For OAuth-based login, see OAuth 2.0 explained for developers — misconfigured redirects and token storage belong in this category.
A08: Software and Data Integrity Failures
Assuming untrusted data and code are safe — unsigned CI artifacts, auto-updates without signature verification, insecure deserialization.
Insecure deserialization example:
# NEVER unpickle untrusted bytes
import pickle
obj = pickle.loads(request.body) # Remote code executionPrefer JSON with schema validation. For webhooks, verify HMAC signatures:
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))) {
return res.status(401).end();
}Use /tools/hash-generator to test HMAC test vectors against your implementation.
A09: Security Logging and Monitoring Failures
Breaches go undetected for months because login failures, access control denials, and admin actions were not logged or alerted.
Log (structured JSON):
- Authentication success/failure with source IP (hash or truncate for GDPR)
- Authorization denials
- Input validation failures on sensitive endpoints
- Admin configuration changes
Do not log passwords, full credit card numbers, or raw session tokens.
Ship logs to a SIEM with retention and alerting rules. Tabletop exercises: "What query finds an account takeover in progress?"
A10: Server-Side Request Forgery (SSRF)
Attacker tricks server into requesting internal URLs (http://169.254.169.254/latest/meta-data/, internal admin panels).
Vulnerable — fetch URL from user input:
app.post('/preview', async (req, res) => {
const html = await fetch(req.body.url).then(r => r.text());
res.send(html);
});Mitigations:
- Block private IP ranges and link-local addresses after DNS resolution (TOCTOU-aware)
- Allowlist outbound domains if previews are required
- Disable unnecessary URL handlers in image/PDF libraries
- Run fetch workers in isolated network segments without cloud metadata access
XSS: still everywhere despite frameworks
Cross-site scripting is not a separate 2021 Top 10 entry but appears under injection and misconfiguration. Stored XSS persists in comments; reflected XSS hits search parameters.
Vulnerable:
res.send(`<p>Results for: ${req.query.q}</p>`);Fixed — contextual encoding:
// React escapes by default; dangerouslySetInnerHTML bypasses protection
<p>{req.query.q}</p>For rich text, sanitize with an allowlist library (DOMPurify). CSP blocks inline script execution even when HTML escapes fail:
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'Treat every byte from users as hostile in HTML, JavaScript, URL, and CSS contexts — escaping rules differ per context.
CSRF on cookie-authenticated apps
If your API uses session cookies (not bearer tokens), mutating requests need CSRF protection:
<input type="hidden" name="_csrf" value="{{csrfToken}}">Double-submit cookie pattern or synchronizer tokens validate that the form submission originated from your site. APIs using Authorization: Bearer headers are less CSRF-vulnerable because browsers do not attach custom headers cross-origin without CORS — but cookie-plus-header hybrids need careful review.
Cross-cutting defenses
These controls reduce risk across multiple Top 10 categories:
| Control | Addresses |
|---|---|
| Content Security Policy | XSS, some injection |
| CSRF tokens on cookie-auth forms | A01, A07 |
| Input validation with JSON Schema | A03, A04 |
| Principle of least privilege (DB roles) | A01, A03 |
| Regular pen tests + bug bounty | All |
OWASP remediation sprint checklist
Pick one item per day for two weeks:
- Object-level authorization test on every
:idroute - Password hashing migrated to Argon2/bcrypt with per-user salt
- All SQL/NoSQL queries use parameter binding
- Security headers (CSP, HSTS, X-Frame-Options) verified in staging
- Dependency scan gates CI on critical CVEs
- Login rate limit + account lockout policy documented
- Webhook HMAC verification on inbound integrations
- Centralized logging with alerts on auth anomaly patterns
- SSRF review on any "fetch URL" or webhook relay feature
- Threat model updated for newest payment/auth flow
Conclusion
The OWASP Top 10 is ordered by prevalence and impact in the wild — start with access control and cryptography, then injection and authentication. Most entries are preventable with parameterized queries, consistent authorization checks, modern hashing, and logging that security teams can actually query.
Next steps: Run a focused code review on your top five :id endpoints, decode a production JWT sample with /tools/jwt-decoder to audit claims and expiry, and align OAuth implementation with the OAuth 2.0 guide if federated login is in scope.
Try these free tools
Put what you learned into practice — no signup required.