· Updated 2026-08-06

Web Application Security: OWASP Top 10 Explained for Developers (2026)

Most web application vulnerabilities are not sophisticated. They are the same classes of mistake — SQL injection, broken access control, security misconfiguration — appearing in new codebases written by developers who learned to build features before they learned to build secure features.

The OWASP Top 10 documents these classes. It has been maintained since 2003 and updated regularly to reflect how the threat landscape and development practices change. Understanding it is not optional for any developer who ships code to users.

This guide explains each of the 10 categories in terms developers can act on: what the vulnerability is, what it looks like in real code, and what the correct implementation is.


The OWASP Top 10 (2021)

Rank Category Core risk
A01 Broken Access Control Users access data or functions they should not
A02 Cryptographic Failures Sensitive data not protected in transit or at rest
A03 Injection User input alters the logic of queries or commands
A04 Insecure Design Security not considered in the design phase
A05 Security Misconfiguration Insecure defaults, unnecessary features, verbose errors
A06 Vulnerable and Outdated Components Dependencies with known CVEs in production
A07 Identification and Authentication Failures Weak session management, no MFA, credential stuffing
A08 Software and Data Integrity Failures Insecure deserialization, unsigned CI/CD pipelines
A09 Security Logging and Monitoring Failures No visibility into attacks in progress
A10 Server-Side Request Forgery (SSRF) Server fetches attacker-controlled URLs

A01: Broken Access Control

Broken Access Control jumped to #1 in the 2021 update because it is now the most common finding in real penetration tests — present in 94% of applications tested.

What it looks like

// Vulnerable: fetches whatever order ID is in the URL
app.get('/api/orders/:id', authenticate, async (req, res) => {
  const order = await Order.findById(req.params.id);
  res.json(order);
});

// Attacker changes /api/orders/1001 to /api/orders/1002
// and gets another user's order details

The fix

// Correct: scopes the query to the authenticated user
app.get('/api/orders/:id', authenticate, async (req, res) => {
  const order = await Order.findOne({
    _id: req.params.id,
    userId: req.user.id  // ← mandatory ownership check
  });
  if (!order) return res.status(404).json({ error: 'Not found' });
  res.json(order);
});

The check must happen at the data layer on every query that references a user-owned resource. There is no framework default that handles this — it requires deliberate implementation on every endpoint.


A02: Cryptographic Failures

Passwords in plaintext or with weak hashing

# Wrong: MD5 is fast to brute-force
import hashlib
hashed = hashlib.md5(password.encode()).hexdigest()

# Wrong: SHA-256 is also fast to brute-force
hashed = hashlib.sha256(password.encode()).hexdigest()

# Correct: Argon2id is slow and designed for passwords
from argon2 import PasswordHasher
ph = PasswordHasher()
hashed = ph.hash(password)

Sensitive data in transit without HTTPS

Every request that carries credentials, session tokens, personal data, or any sensitive value must use HTTPS. Redirect HTTP to HTTPS. Set HSTS with a long max-age. Test with curl -I https://yourdomain.com to confirm headers.

Sensitive data in logs

Never log passwords, tokens, credit card numbers, or government identifiers — even to internal systems. An internal log pipeline is not a security boundary.


A03: Injection

SQL injection remains the most severe injection class.

# Vulnerable: string concatenation with user input
query = f"SELECT * FROM users WHERE email = '{email}'"
cursor.execute(query)
# Attacker sends: email = "' OR '1'='1"
# Resulting query: SELECT * FROM users WHERE email = '' OR '1'='1'
# Returns all users

# Correct: parameterised query
cursor.execute("SELECT * FROM users WHERE email = %s", (email,))

The same principle applies to every system that executes instructions based on user input: LDAP queries, OS commands, XML parsers (XXE), and template engines (server-side template injection).

XSS (Stored and Reflected):

// Vulnerable in React: bypasses the framework's auto-escaping
<div dangerouslySetInnerHTML={{ __html: userComment }} />

// Safe: React's default rendering escapes HTML automatically
<div>{userComment}</div>

A04: Insecure Design

Insecure design is the only OWASP category that cannot be fixed by adding a security control after the fact — it requires revisiting the architecture.

Examples of design-level security failures:

  • An API that signs requests client-side with a key embedded in the binary (the entire key can be extracted — see the Flutter reverse engineering research)
  • A password reset flow that sends the new password in the email rather than a reset link
  • An admin panel accessible from the same domain and port as the user-facing application
  • A multi-tenant SaaS where tenant isolation is enforced only in the application layer, not at the database level

The fix is threat modelling: systematically identifying what can go wrong in a design before it is built, and making design choices that limit the blast radius of any single component being compromised.


A05: Security Misconfiguration

Common misconfiguration patterns

# Wrong: directory listing enabled
autoindex on;

# Wrong: verbose error pages showing stack traces
error_page 500 /internal-error.html; # served with full trace

# Wrong: default credentials unchanged
admin:admin, admin:password, root:root

Cloud misconfiguration is the most costly form in 2026: public S3 buckets, overly permissive IAM policies, unrestricted security groups, and unencrypted databases are responsible for the majority of large cloud data breaches.

Tools that detect misconfiguration automatically: AWS Security Hub, Azure Defender for Cloud, GCP Security Command Center for cloud resources; OWASP ZAP, Nikto for web application configuration.


A06: Vulnerable and Outdated Components

A single critical CVE in a widely used dependency can affect thousands of applications simultaneously. The Equifax breach (147 million records) was caused by an unpatched Apache Struts vulnerability that had a fix available for two months before the breach.

Automation is the only sustainable approach:

# GitHub Dependabot configuration
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10

Prioritise CVSS Critical (9.0–10.0) and High (7.0–8.9) CVEs. Patch or mitigate within 24–72 hours for Critical, within one sprint for High.


A07: Identification and Authentication Failures

Secure session management

// Correct session cookie configuration (Express.js)
app.use(session({
  secret: process.env.SESSION_SECRET,  // long, random, from env
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true,      // HTTPS only
    httpOnly: true,    // not accessible from JavaScript
    sameSite: 'strict', // no cross-site requests
    maxAge: 30 * 60 * 1000  // 30 minute expiry
  }
}));

Rate limiting authentication endpoints

const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minutes
  max: 10,                    // 10 attempts per window
  message: 'Too many login attempts',
  standardHeaders: true,
});
app.post('/login', loginLimiter, loginHandler);

Secure password reset

The reset flow must: generate a cryptographically random token (not a predictable value like a timestamp), store its hash (not the token itself), set a short expiry (15–60 minutes), invalidate the token after single use, and send the token in the URL — never send a new password in the email.


A08: Software and Data Integrity Failures

This category covers two distinct risks:

Insecure deserialization: Untrusted data passed to a deserializer can execute arbitrary code if the deserializer is vulnerable. Never deserialise untrusted data with Java's native serialization, Python's pickle, or Ruby's Marshal. Use JSON with schema validation instead.

Unsigned CI/CD pipeline artifacts: If your build pipeline can be manipulated to include malicious code — through a compromised dependency, a tampered build server, or an unsigned artifact — the malicious code ships to production. Controls: verify dependency integrity (npm package-lock.json, pip hash verification), sign build artifacts, and use SLSA (Supply-chain Levels for Software Artifacts) framework controls.


A09: Security Logging and Monitoring Failures

Attacks that are not detected cannot be stopped. Logging must capture:

  • Authentication events (success and failure) with user ID, IP, and timestamp
  • Access control failures (403 responses with the resource attempted)
  • Input validation failures (potentially malicious inputs)
  • High-value transactions (payments, account changes, privilege escalations)

What not to log:

  • Passwords, tokens, or secrets (even failed attempts — the attacker may have entered the correct value)
  • Full credit card numbers or government identifiers

Set alerts for: > 10 failed authentication attempts per minute per IP, access control failures in bulk (IDOR probing pattern), and unusual spikes in 5xx error rates.


A10: Server-Side Request Forgery (SSRF)

SSRF occurs when a server makes an HTTP request to a URL supplied by the user — allowing an attacker to make the server fetch internal resources.

Attacker sends: POST /fetch-image
body: {"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}

Server fetches AWS instance metadata → attacker gets IAM credentials

Prevention:

  • Validate and allowlist URLs before making server-side requests
  • Block requests to private IP ranges (10.x.x.x, 172.16.x.x–172.31.x.x, 192.168.x.x, 169.254.x.x, 127.x.x.x)
  • Use a dedicated egress proxy that enforces allowlisted domains
  • Disable URL schemes other than HTTPS (file://, gopher://, ftp://)

Security as a Development Practice

The OWASP Top 10 is not a one-time checklist. It is a framework for building security thinking into development practice:

  • Threat model new features before they are built — what can go wrong?
  • Include security requirements alongside functional requirements
  • Run SAST (static analysis) on every commit
  • Conduct access control reviews on every endpoint that handles user data
  • Penetration test before major releases or new attack surface is exposed

For teams that want a professional security assessment — including active testing against the OWASP Top 10 — get in touch or explore our Mobile Security & Reverse Engineering service. Our API Security Best Practices guide covers the server-side API layer in detail.

Need Expert Guidance?

Planning custom software for your business?

Book a free consultation with our team to discuss architecture, product strategy, and the right build approach for your goals.

Book Free Consultation