BytePane

OWASP Top 10 (2025): Most Critical Web Application Security Risks

Security20 min read

Key Takeaways

  • The OWASP Top 10 2025 is the eighth edition — not annual. Previous editions: 2010, 2013, 2017, 2021, 2025. It was built from data on 2.8 million applications and 175,000+ CVE records from the National Vulnerability Database.
  • Two new categories debut in 2025: A03 Software Supply Chain Failures (highest average exploit score of any category) and A10 Mishandling of Exceptional Conditions (which absorbs the former standalone SSRF category).
  • Broken Access Control stays at #1 for the second edition in a row — it appeared in 94% of tested applications. It is the most common vulnerability and often the easiest to miss because it lives in business logic, not code syntax.
  • Security Misconfiguration jumps from #5 to #2, driven by widespread cloud infrastructure misconfigurations, open S3 buckets, and default credentials left unchanged in containerized deployments.
  • PCI DSS 4.0 explicitly requires testing against the OWASP Top 10 for web-facing cardholder data applications. Addressing this list is increasingly a compliance requirement, not just a best practice.

The Misconception That Gets Teams Complacent

“We checked the OWASP Top 10 last year.” This is the statement that keeps security engineers up at night — because the OWASP Top 10 does not update annually. The previous edition was 2021. The current one is 2025. Four years of evolving attack patterns, supply chain compromises, and cloud-native misconfigurations are baked into this release.

The 2025 edition is the most data-intensive yet. The Open Worldwide Application Security Project analyzed 175,000+ CVE records from the National Vulnerability Database (up from 125,000 in the 2021 methodology), mapped against 643 unique CWEs (up from 241 in 2021), and combined that with contributed data from 2.8 million real applications via partners including Accenture, Bugcrowd, Contrast Security, and Veracode.

The result is not a list of hypothetical threats. It is a ranked snapshot of what attackers are actually exploiting, right now, across the web applications that power most of the internet.

OWASP Top 10 2025 — Quick Reference

CategoryNamevs 2021CWEsIncidence
A01:2025Broken Access ControlUnchanged #13455.97%
A02:2025Security MisconfigurationMoved up from #52043.84%
A03:2025Software Supply Chain FailuresNEW category115.19% avg
A04:2025Cryptographic FailuresMoved down from #22946.44%
A05:2025InjectionMoved down from #32819.09%
A06:2025Insecure DesignMoved down from #4403.81%
A07:2025Authentication FailuresRenamed (was Identification & Auth)3614.84%
A08:2025Software & Data Integrity FailuresUnchanged #8192.05%
A09:2025Security Logging & Alerting FailuresUnchanged #946.51%
A10:2025Mishandling of Exceptional ConditionsNEW category244.17%

Incidence rate = percentage of tested applications containing at least one instance of the vulnerability. Source: OWASP Top 10 2025 methodology, NVD CVE data.

A01:2025 — Broken Access Control

Broken access control found in 94% of applications tested in the OWASP 2025 dataset — the highest incidence of any category. It means users can act outside their intended permissions: accessing another user’s account data, calling admin-only endpoints, viewing resources they were never authorized to see.

What makes this so pervasive is that access control bugs live in business logic, not in syntax that a linter can catch. You can have perfectly valid code that does exactly the wrong thing:

// VULNERABLE: user ID comes from query param — attacker sets it to any value
app.get('/api/orders/:orderId', async (req, res) => {
  const userId = req.query.userId   // ← controlled by attacker
  const order = await db.orders.findOne({
    where: { id: req.params.orderId, userId }
  })
  return res.json(order)
})

// FIXED: pull userId from the verified JWT session, not the request
app.get('/api/orders/:orderId', authenticate, async (req, res) => {
  const userId = req.user.id   // ← from cryptographically verified token
  const order = await db.orders.findOne({
    where: { id: req.params.orderId, userId }
  })
  if (!order) return res.status(404).json({ error: 'Not found' })
  return res.json(order)
})

The pattern above is called an Insecure Direct Object Reference (IDOR) — it accounts for a huge proportion of bug bounty payouts. The fix is always the same: derive authorization context from the server-side session, never from user-supplied parameters.

Other common broken access control patterns: missing function-level access control (any logged-in user can hit admin endpoints), directory traversal (../../../etc/passwd), and JWT with the role claim in the payload but no server-side authorization checks.

A02:2025 — Security Misconfiguration (Up from #5)

Security misconfiguration’s jump from #5 to #2 tracks directly with cloud adoption. The attack surface is no longer just your app — it is every S3 bucket, every IAM role, every container, every default credential left unchanged. Per the 2025 Verizon Data Breach Investigations Report, misconfiguration is the leading cause of cloud-related breaches.

# Common misconfigurations to audit:

# 1. Exposed debug endpoints in production
GET /debug/vars       # Go's expvar — leaks runtime metrics, goroutines, env vars
GET /.env             # Environment file served by misconfigured nginx
GET /actuator/health  # Spring Boot actuator — sometimes exposes heap dumps

# 2. Default credentials
mongodb://localhost:27017  # MongoDB ≤ 2.4 had no auth by default
admin:admin, root:root     # Still #1 in Shodan scans

# 3. Overly permissive CORS
app.use(cors({ origin: '*', credentials: true }))  # Browsers BLOCK this
# (wildcard + credentials is rejected by browsers, but it signals poor config hygiene)

# 4. HTTP security headers missing
Content-Security-Policy: default-src 'none'  # blocks XSS exfiltration
X-Frame-Options: DENY                         # clickjacking protection
Permissions-Policy: camera=(), microphone=() # disable unused browser APIs

# 5. Cloud: S3 bucket public read
aws s3api put-bucket-acl --bucket prod-data --acl public-read  # NEVER

# Fix: use bucket policies + block public access setting
aws s3api put-public-access-block   --bucket prod-data   --public-access-block-configuration     "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

The most effective mitigation is Infrastructure as Code with security policy enforcement: use tools like Checkov, tfsec, or AWS Config rules to catch misconfigurations before they reach production. Manual review does not scale — your IaC pipeline must.

A03:2025 — Software Supply Chain Failures (New)

The most consequential new category in 2025 — and possibly the hardest to fully address. Supply chain failures cover risks introduced through the software you did not write: third-party npm packages, build tools, Docker base images, CI/CD pipelines, and update mechanisms.

The SolarWinds attack (2020) injected malicious code into the Orion build process, reaching 18,000 organizations including US government agencies before detection. The xz-utils backdoor (2024) was a two-year social engineering operation that embedded a remote access backdoor in a compression library shipped in most Linux distributions. These are not theoretical — they are documented supply chain attacks against production systems.

Despite only 11 CWEs mapped to this category (fewest of any), it has the highest average exploit and impact scores in the OWASP 2025 dataset. When supply chains are compromised, the blast radius is enormous.

# Mitigations by attack surface:

# 1. Pin dependency versions (npm)
# Bad: "express": "^4.18.0"  — automatically installs minor/patch updates
# Good: lock exact versions via package-lock.json + npm ci in CI

# 2. Enable npm audit in CI pipeline
npm audit --audit-level=high && npm ci

# 3. Verify package integrity via checksums
# package-lock.json stores SHA-512 integrity hashes for every package
# npm ci verifies them — use it instead of npm install in CI/CD

# 4. Docker: pin base image digests (not just tags)
# Bad:  FROM node:20-alpine   ← tag can be overwritten
# Good: FROM node:20-alpine@sha256:abc123... ← content-addressed, immutable

# 5. Software Bill of Materials (SBOM)
# Generate with: npm sbom --sbom-format=spdx2 --sbom-type=package
# Or with syft: syft packages . -o spdx-json > sbom.json
# SBOM lets you scan for known-vulnerable components at any point

# 6. Sigstore / cosign for artifact signing
# Sign your Docker images so consumers can verify you built them
cosign sign --key cosign.key myregistry/myapp:latest

A04:2025 — Cryptographic Failures

Cryptographic failures cover the gap between “we have encryption” and “we have correct encryption.” Found in 46.44% of tested applications, it is the second-most pervasive category by incidence rate. Common failures:

  • MD5/SHA-1 for passwords — both are fast hash functions, meaning GPU rigs can test billions of candidates per second. Use Argon2id (OWASP recommendation), bcrypt (cost factor ≥ 12), or scrypt instead.
  • Storing secrets in plaintext — API keys, database passwords, and JWT secrets in .env files committed to version control. Use a secrets manager (HashiCorp Vault, AWS Secrets Manager, Doppler).
  • Weak TLS configuration — accepting TLS 1.0/1.1, using RC4 or DES cipher suites. Enforce TLS 1.2+ with strong AEAD ciphers (AES-GCM, ChaCha20-Poly1305).
  • ECB mode encryption — Electronic Codebook mode produces identical ciphertext for identical plaintext blocks, leaking data patterns. Use AES-GCM or AES-CTR with a random nonce.
// Password hashing: use argon2id (Node.js)
import { hash, verify } from '@node-rs/argon2'

const hashed = await hash(password, {
  memoryCost: 65536,  // 64 MiB — makes GPU attacks expensive
  timeCost: 3,        // 3 iterations
  parallelism: 4,
  algorithm: Algorithm.Argon2id,  // hybrid of Argon2i + Argon2d
})

// Verification — returns boolean, constant-time comparison
const isValid = await verify(hashed, inputPassword)

// Symmetric encryption: AES-256-GCM (authenticated encryption)
import { randomBytes, createCipheriv } from 'crypto'

const key = randomBytes(32)   // 256-bit key — store in secrets manager
const iv = randomBytes(12)    // 96-bit nonce — random per message
const cipher = createCipheriv('aes-256-gcm', key, iv)

let encrypted = cipher.update(plaintext, 'utf8', 'hex')
encrypted += cipher.final('hex')
const authTag = cipher.getAuthTag()  // 128-bit authentication tag
// Store: iv + authTag + encrypted (all needed for decryption + verification)

A05:2025 — Injection

Injection moves from #3 to #5, not because it is less dangerous, but because modern frameworks and ORMs have made the worst forms harder to accidentally introduce. That said, SQL injection still accounts for a significant share of critical CVEs annually, and newer injection vectors (OS command injection, LDAP injection, Server-Side Template Injection) are less well-understood.

// SQL Injection — the classic
// VULNERABLE: string concatenation
const q = "SELECT * FROM users WHERE email = '" + req.body.email + "'"
// Input: ' OR '1'='1  →  bypasses authentication entirely

// FIXED: parameterized queries (any driver)
const { rows } = await pool.query(
  'SELECT * FROM users WHERE email = $1',
  [req.body.email]   // driver escapes and type-checks
)

// OS Command Injection (often overlooked)
// VULNERABLE: user input in shell command
exec(`convert ${req.body.filename} output.png`)
// Input: "; rm -rf /"  →  catastrophic

// FIXED: use library APIs, never shell out with user input
import sharp from 'sharp'
await sharp(sanitizedPath).resize(800).toFile('output.png')

// Server-Side Template Injection (SSTI)
// VULNERABLE in Python/Jinja2:
template = Template(f"Hello {user_input}")
template.render()
# Input: {{config.__class__.__init__.__globals__['os'].popen('id').read()}}

// FIXED: never render user input as a template — use variable substitution
template = Template("Hello {{ name }}")
template.render(name=user_input)  # safe: input treated as data, not code

Cross-Site Scripting (XSS) is classified under Injection in OWASP 2025. It targets the browser rather than the server, but the root cause is the same: unsanitized user input reflected into an execution context. React and Vue escape HTML by default — but dangerouslySetInnerHTML bypasses that protection and is a common source of DOM XSS in React apps.

A06:2025 — Insecure Design

Introduced in 2021 and retained in 2025, Insecure Design represents a category shift: it is not about implementation bugs, but architectural decisions that cannot be corrected by patching code. The 40 mapped CWEs include problems like:

  • Missing rate limiting on authentication endpoints — credential stuffing attacks succeed because there is no defense against automated login attempts. A password reset endpoint that allows 10,000 requests/hour is insecurely designed, not just misconfigured.
  • Business logic flaws — a shopping cart that applies coupon codes server-side but validates quantity client-side; an API that processes payment before checking inventory.
  • Trust boundary violations — a multi-tenant SaaS where tenant data is separated by a query parameter rather than by database isolation.

The fix requires threat modeling — before writing code, map data flows, trust boundaries, and identify adversarial use cases. OWASP recommends using the STRIDE framework (Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, Elevation of Privilege) during design reviews.

A07:2025 — Authentication Failures

Renamed from “Identification and Authentication Failures” in 2021, this category maps 36 CWEs related to how applications confirm identity. Present in 14.84% of tested applications, the most common failure modes are:

FailureAttackMitigation
No MFA on admin accountsCredential stuffing from leaked databasesTOTP MFA (not SMS) + enforce for privileged roles
Weak session IDsSession prediction or brute forceUse framework session management; 128-bit random ID
No account lockoutBrute force on login endpointExponential backoff + CAPTCHA after N failures
Hardcoded credentialsSource code leak exposes accessSecrets manager; git-secrets pre-commit hook
Insecure password resetToken prediction or long expiry windowSingle-use tokens, 15-min expiry, sent to verified email

NIST SP 800-63B is the authoritative password guidance: no mandatory rotation (it causes predictable patterns), allow up to 64 character passwords, check against known-breached password databases (HaveIBeenPwned API), do not require composition rules. Most legacy systems violate at least three of these.

A08:2025 — Software & Data Integrity Failures

Distinct from Supply Chain Failures (A03), this category focuses on lower-level integrity failures: insecure deserialization, untrusted update mechanisms, and CI/CD pipeline compromises that affect data rather than build artifacts. The 2020 npm event-stream attack — where a malicious maintainer added code that stole cryptocurrency wallets — is a canonical example.

# Insecure deserialization — Python pickle example
# VULNERABLE: deserializing untrusted data with pickle
import pickle
data = pickle.loads(user_supplied_bytes)  # arbitrary code execution
# Attacker can embed __reduce__ to execute any shell command

# FIXED: use safe formats for external data
import json
data = json.loads(user_supplied_bytes)  # only data, no code execution

# For Python objects: use dataclasses + dacite, or Pydantic
from pydantic import BaseModel
class Order(BaseModel):
    item_id: int
    quantity: int
order = Order.model_validate_json(user_supplied_bytes)

# CI/CD integrity: verify webhook signatures before acting
# VULNERABLE: process any POST to /webhook/deploy
# FIXED: verify HMAC-SHA256 signature from GitHub/GitLab
import hmac, hashlib
sig = request.headers.get('X-Hub-Signature-256', '')
expected = 'sha256=' + hmac.new(
    SECRET, request.body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(sig, expected):
    return 403  # reject unsigned payloads

A09:2025 — Security Logging & Alerting Failures

The average time to detect a breach is 194 days per IBM Cost of a Data Breach Report 2024 — and insufficient logging is the primary reason attacks go undetected for so long. This category is unusual in the OWASP list: it is not about preventing attacks, but about detecting them.

Critical events that must be logged (and alerted on):

  • All authentication attempts (success and failure) with IP, timestamp, user agent
  • Access control failures — every 403/401 on sensitive endpoints
  • Input validation failures on server-side (potential attack probing)
  • High-value transactions: password changes, email changes, privilege escalations
  • API requests with invalid or expired tokens

Logs must be structured (JSON), shipped to an immutable log store the application cannot modify, and include a correlation ID to trace requests across services. A SIEM (Splunk, Elastic SIEM, Datadog Security) should alert on anomaly patterns: >5 auth failures per minute from one IP, access to admin endpoints from non-admin accounts, off-hours database dumps.

A10:2025 — Mishandling of Exceptional Conditions (New)

The second new category in 2025 consolidates error handling failures — and absorbs Server-Side Request Forgery (SSRF), which was its own standalone category in 2021. The 24 mapped CWEs focus on what happens when your application hits an edge case it was not designed for.

The most dangerous pattern is failing open: when an exception is caught and the system continues as if the operation succeeded, often granting access or completing a transaction without verification.

// VULNERABLE: failing open — exception bypasses authorization
async function isAdmin(userId: string): Promise<boolean> {
  try {
    const user = await db.users.findById(userId)
    return user.role === 'admin'
  } catch (err) {
    return true  // ← BUG: on DB error, everyone becomes admin
  }
}

// FIXED: fail closed — deny access on any error
async function isAdmin(userId: string): Promise<boolean> {
  try {
    const user = await db.users.findById(userId)
    return user?.role === 'admin' ?? false
  } catch (err) {
    logger.error({ userId, err }, 'auth check failed')
    return false  // ← deny on uncertainty
  }
}

// SSRF (now under A10): server makes HTTP requests to attacker-controlled URL
// VULNERABLE:
app.post('/fetch', async (req, res) => {
  const data = await fetch(req.body.url)  // fetches internal services
  // Input: http://169.254.169.254/latest/meta-data/  → AWS metadata
})

// FIXED: allowlist + block internal ranges
import { isPrivateIP } from 'is-private-ip'
const url = new URL(req.body.url)
if (!['https:'].includes(url.protocol)) throw new Error('Bad protocol')
if (isPrivateIP(url.hostname)) throw new Error('Private IP blocked')
const data = await fetch(url.toString(), { signal: AbortSignal.timeout(5000) })

Scanning Tools for the OWASP Top 10

ToolTypeOWASP Categories AddressedCost
OWASP ZAPDASTA01, A02, A05, A07Free / Open source
SemgrepSASTA01, A04, A05, A08Free tier / $
npm audit / SnykSCAA03, A08Free tier / $$
Checkov / tfsecIaC scanningA02Free / Open source
Burp Suite ProDAST / ManualA01, A02, A05, A07, A10$$$

SAST (Static Application Security Testing) runs against source code; DAST (Dynamic) attacks a running application; SCA (Software Composition Analysis) scans dependencies. A mature security program uses all three plus manual penetration testing.

Frequently Asked Questions

What is the OWASP Top 10?
The OWASP Top 10 is a standard awareness document published by the Open Worldwide Application Security Project. It lists the most critical security risks to web applications, ranked by data from 2.8 million applications and 175,000+ CVEs. It is not a compliance standard, but a prioritized guide for what to address first.
How often is the OWASP Top 10 updated?
Not annually — editions were published in 2010, 2013, 2017, 2021, and 2025. The 2025 edition is the eighth release. OWASP updates when the data and practitioner survey results justify significant changes, which takes several years to accumulate.
What is broken access control and why is it #1?
Broken access control means users can act outside their intended permissions — accessing others' data, calling admin endpoints, or modifying unauthorized resources. It ranks #1 because it appears in 94% of tested applications. It lives in business logic rather than syntax, making it invisible to linters and easy to miss in code review.
What are the two new categories in OWASP Top 10 2025?
A03 Software Supply Chain Failures (risks from third-party packages, build tools, and CI/CD pipelines — highest exploit/impact scores of any category) and A10 Mishandling of Exceptional Conditions (improper error handling, failing open, and SSRF — which was its own category in 2021 but is now merged here).
Is OWASP Top 10 a compliance requirement?
Not directly, but PCI DSS 4.0 requires testing against OWASP Top 10 for web-facing cardholder data applications. SOC 2 auditors use it as a reference for application security controls. Failing to address it creates compliance gaps in regulated environments.
What is the difference between A05 Injection and A04 Cryptographic Failures?
Injection is an active attack where malicious input is interpreted as a command. Cryptographic Failures is a passive exposure: sensitive data is stored or transmitted without adequate protection. Both are critical but require different mitigations — parameterized queries and output encoding for injection; Argon2id and TLS 1.3 for cryptographic failures.
How should a development team prioritize OWASP Top 10 fixes?
Start with Broken Access Control (A01) and Injection (A05) — high impact, clear remediation, and automatable with SAST/DAST. Then address Cryptographic Failures (A04). Supply Chain Failures (A03) and Insecure Design (A06) require architectural changes — plan them into quarterly roadmap rather than trying to fix in a single sprint.

Security Tools for Developers

Use BytePane’s free developer tools to inspect and harden your application’s security posture:

  • SSL Checker — verify TLS configuration, certificate chain validity, and cipher suite strength (addresses A04)
  • DNS Lookup — check CAA records, DMARC/SPF, and DNSSEC configuration (addresses A02)
  • Password Generator — generate cryptographically secure random secrets (addresses A07)
  • Hash Generator — compute SHA-256 checksums for artifact integrity verification (addresses A03, A08)
Check SSL Certificate

Related Articles