core/security/owasp_top_10.md

OWASP Top 10:2025 Web Application Security Risks

The current OWASP Top 10:2025 web application security risk categories with concrete mitigation guidance and code-level examples for each, grounded in the official OWASP release.

Introduction / Overview

The OWASP Top 10 is the standard, community-consensus awareness document for the most critical web application security risks, maintained by the Open Worldwide Application Security Project. The OWASP Top 10:2025 revision is the current release as of this writing and introduces two new categories relative to the 2021 edition - Software Supply Chain Failures and Mishandling of Exceptional Conditions - reflecting the industry's growing exposure to dependency-based attacks and the reality that unhandled error paths are a persistent, exploitable source of information disclosure and logic bypass.

This document walks through each of the ten categories in their official 2025 order, with mitigation guidance and minimal code examples per category. It should be read alongside Secure Coding Practices, which covers the underlying language-agnostic techniques (validation, encoding, least privilege) that address most of these categories simultaneously.

When to use this guidance: Threat-modeling a new feature, conducting a security review of existing code, prioritizing a security backlog, or educating a team on the current highest-impact risk categories for web applications.

Core Concepts

  • Risk category, not vulnerability list: Each OWASP Top 10 entry is a class of risk (e.g., "Injection") encompassing many specific vulnerability types (SQL injection, command injection, template injection), not a single CVE or bug pattern.
  • Ordered by prevalence and impact, not severity alone: The ranking reflects a combination of how often each category is found in tested applications and the typical business impact when exploited - a rare-but-catastrophic risk and a common-but-low-impact risk can both appear mid-list.
  • Shift from injection to systemic risks: The 2025 ordering reflects a broader industry shift - access control and configuration issues (systemic, architecture-level) now rank above classic injection (a code-level defect), reflecting where real-world breaches are concentrated.
  • Defense in depth across categories: Most real incidents involve chained failures across two or more of these categories (e.g., a misconfiguration exposing an endpoint that has an access control flaw, logged via a supply-chain-compromised logging library).

The OWASP Top 10:2025 Categories

A01:2025 - Broken Access Control

Access control enforces that a user can only act within their intended permissions. This category - the top risk for both the 2021 and 2025 editions - includes privilege escalation, insecure direct object references (IDOR), and (newly consolidated into this category for 2025) Server-Side Request Forgery (SSRF).

Mitigation:

@app.get("/invoices/{invoice_id}")
async def get_invoice(
    invoice_id: int,
    current_user: Annotated[User, Depends(get_current_user)],
    invoice_service: Annotated[InvoiceService, Depends(get_invoice_service)],
):
    invoice = await invoice_service.get(invoice_id)
    if invoice is None or invoice.owner_id != current_user.id:
        raise HTTPException(status_code=404)  # 404, not 403 - avoid confirming existence
    return invoice
  • Enforce authorization checks server-side on every request, scoped to the authenticated principal - never rely on a client-supplied ID being sufficient proof of ownership.
  • For SSRF specifically: validate and allowlist outbound destination hosts/schemes before the application makes any server-initiated request based on user input (webhooks, URL previews, image fetchers).

A02:2025 - Security Misconfiguration

Security Misconfiguration rose from #5 in 2021 to #2 in 2025, driven by increasingly complex cloud-native stacks (Kubernetes, managed services, IaC) where a single misconfigured default can expose an entire environment. Covers unnecessary features enabled, default credentials, verbose error messages, permissive CORS, and missing security headers.

Mitigation:

class Settings(BaseSettings):
    debug: bool = False
    cors_allowed_origins: list[str] = []  # explicit allowlist, never "*" with credentials
  • Harden every layer of the stack (application framework, container runtime, cloud IAM, orchestrator) against its insecure defaults - see Docker Best Practices for the container-level equivalent.
  • Automate configuration auditing (e.g., kube-bench, cloud security posture management tooling) as a recurring, not one-time, check.

A03:2025 - Software Supply Chain Failures

New for 2025. Covers compromised or malicious dependencies, unverified build pipelines, typosquatted packages, and unsigned artifacts - the class of risk behind incidents like the event-stream, xz-utils, and various dependency-confusion attacks.

Mitigation:

- name: Software composition analysis
  run: pip-audit --strict --vulnerability-service osv
- name: Verify image signature before deploy
  run: cosign verify --key cosign.pub myregistry.example.com/myapp:${{ github.sha }}
  • Generate and retain an SBOM for every build (see Docker Best Practices).
  • Pin dependency versions with lockfiles, verify package integrity hashes, and run SCA scanning on every pipeline execution (see CI/CD Patterns).
  • Require signed commits (see Git Best Practices) and signed build artifacts so provenance is verifiable end-to-end.

A04:2025 - Cryptographic Failures

Covers weak or missing encryption in transit and at rest, use of broken/deprecated algorithms (MD5, SHA-1 for passwords, unpadded RSA), hardcoded keys, and improper certificate validation.

Mitigation:

from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
hashed = pwd_context.hash(plaintext_password)
  • Use vetted libraries (argon2/bcrypt for passwords, TLS 1.2+ with modern cipher suites for transport) - never implement cryptographic primitives directly (see Secure Coding Practices).
  • Never disable certificate verification, even temporarily in "debug" code paths that can leak into production configuration.

A05:2025 - Injection

Includes SQL, NoSQL, OS command, LDAP, and template injection - any case where untrusted data is interpreted as code or query structure by a downstream interpreter. Fell from #3 to #5 in 2025, reflecting the effectiveness of widespread ORM/parameterized-query adoption, though it remains a top-tier risk.

Mitigation:

cursor.execute("SELECT * FROM users WHERE email = %s", (email,))  # parameterized
subprocess.run(["ping", "-c", "1", hostname], shell=False)          # no shell=True with user input
  • Always use parameterized queries/prepared statements or ORM query builders; never string-concatenate or f-string untrusted input into a query, shell command, or template.
  • See Secure Coding Practices for the full pattern.

A06:2025 - Insecure Design

Represents architectural and threat-modeling failures - missing rate limiting on sensitive operations, business logic that assumes trust it should not, absence of abuse-case analysis during design. Distinct from an implementation bug: the design itself lacks a necessary security control.

Mitigation:

  • Conduct threat modeling (e.g., STRIDE) during design for any feature handling money, PII, or privileged actions, before implementation begins.
  • Apply rate limiting and anomaly detection to sensitive flows (password reset, MFA enrollment, payment) as a design requirement, not an afterthought.
  • Reference SOLID Principles and Microservices Architecture for architectural practices that make secure design easier to reason about and review.

A07:2025 - Authentication Failures

Covers credential stuffing susceptibility, weak password policies, missing multi-factor authentication (MFA) on sensitive accounts, session fixation, and improperly implemented password recovery flows.

Mitigation:

class LoginRequest(BaseModel):
    email: EmailStr
    password: str = Field(min_length=8, max_length=128)
  • Enforce MFA for privileged accounts and sensitive operations; use vetted session/token libraries with secure cookie flags (HttpOnly, Secure, SameSite=Strict).
  • Apply progressive delays or account lockout with care (avoid enabling denial-of-service via lockout) combined with rate limiting on authentication endpoints.

A08:2025 - Software or Data Integrity Failures

Covers insecure deserialization, unsigned/unverified auto-update mechanisms, and CI/CD pipelines that allow unverified code or artifacts to be promoted to production.

Mitigation:

  • Avoid native deserialization of untrusted data (Python pickle, Java native serialization); prefer schema-validated formats (JSON via Pydantic, Protocol Buffers).
  • Verify integrity (checksums, signatures) of any auto-updated component or third-party artifact before use, and enforce it at the CI/CD gate (see CI/CD Patterns).

A09:2025 - Security Logging and Alerting Failures

Insufficient or absent logging of authentication events, access control failures, and high-value transactions delays breach detection - median time-to-detect a breach remains measured in weeks to months industry-wide when logging is inadequate.

Mitigation:

logger.warning("access_denied", extra={"user_id": user.id, "resource": resource_id, "action": "delete"})
  • Log security-relevant events (authentication success/failure, access denials, privilege changes) with sufficient context to reconstruct an incident, while excluding sensitive data values (see Secure Coding Practices).
  • Ensure logs are shipped to a system the attacker cannot trivially delete from the compromised host itself, and wire alerting thresholds for anomalous patterns (repeated auth failures, privilege escalation attempts).

A10:2025 - Mishandling of Exceptional Conditions

New for 2025. Covers error paths that fail open instead of fail closed, verbose stack traces or debug information leaked to clients, unhandled exceptions that bypass security checks further down a call chain, and inconsistent error handling across a distributed system.

Mitigation:

@app.exception_handler(Exception)
async def unhandled_exception_handler(request: Request, exc: Exception):
    logger.exception("unhandled_error", extra={"path": request.url.path})
    return JSONResponse(status_code=500, content={"detail": "Internal server error"})  # no stack trace to client
  • Centralize exception handling (see FastAPI Best Practices) so every unhandled error returns a generic, safe response rather than leaking a stack trace, and defaults to a fail-closed state for security-relevant checks (e.g., an authorization check that errors should deny, not permit, access).
  • Audit multi-step transactional flows for partial-failure states that could leave the system in an inconsistent, exploitable condition.

Best Practices Summary Table

CategoryPrimary Code-Level ControlPrimary Architectural Control
A01 Broken Access ControlServer-side authorization check per requestCentralized authorization middleware/policy engine
A02 Security MisconfigurationSecure-by-default settings classesAutomated configuration auditing (IaC scanning)
A03 Software Supply Chain FailuresLockfiles, integrity hashesSBOM + signed artifacts + SCA gate in CI
A04 Cryptographic FailuresVetted libraries (argon2, TLS)Centralized secrets/key management
A05 InjectionParameterized queriesLeast-privilege database accounts
A06 Insecure DesignRate limiting on sensitive endpointsThreat modeling during design phase
A07 Authentication FailuresStrong session/token handling, MFACentralized identity provider
A08 Data Integrity FailuresSchema-validated deserializationSigned CI/CD artifact provenance
A09 Logging and Alerting FailuresStructured security event loggingCentralized log aggregation + alerting
A10 Mishandling of Exceptional ConditionsCentralized exception handlers, fail-closedConsistent error-handling policy across services

Common Pitfalls & Anti-Patterns

Pitfall: Treating the OWASP Top 10 as a Compliance Checklist Rather Than a Threat Model

Problem: Checking "we have input validation" as a one-time box without ongoing verification (tests, scans) gives false assurance while the underlying risk category remains unaddressed for new code paths.

Recommended approach: Wire each category's primary control into CI as an enforced gate (SCA scanning, SAST rules, dependency signing verification) so coverage does not silently regress - see CI/CD Patterns.

Pitfall: Focusing Only on Injection and Ignoring Access Control and Configuration

Problem: Engineering teams often over-index on classic injection defenses (parameterized queries) while under-investing in access control and configuration review - precisely the two categories now ranked highest in 2025.

Recommended approach: Explicitly review authorization logic and deployment configuration (cloud IAM policies, container security context, CORS policy) with the same rigor historically reserved for injection defenses.

Pitfall: Assuming a Rare, Slow-Moving Top 10 List Means Static Guidance

Problem: Treating a single training/review session on an older Top 10 edition as permanently sufficient misses category reordering and new entries (like Software Supply Chain Failures in 2025) that reflect real, current attacker behavior.

Recommended approach: Revisit the current official list (https://owasp.org/Top10/) periodically and update internal security review checklists to match the current edition rather than an internalized older version.

Testing Strategies

  • Automated SAST/DAST in the pipeline: Combine static analysis (SAST) for code-level issues (A05 Injection, A08 Integrity Failures) with dynamic analysis (DAST, e.g., OWASP ZAP) against a running instance for A01/A02-class issues that only manifest at runtime.
  • Authorization-specific test suites: Write explicit tests asserting that User A cannot access User B's resources (IDOR regression tests) - this is rarely caught by generic functional tests and must be deliberate.
  • Dependency and artifact provenance verification tests: Assert in CI that signature verification and SBOM generation steps actually pass/fail correctly (test the control itself, not just its presence).
  • Fault injection for A10: Deliberately inject exceptions/timeouts into dependent services during integration testing (see Integration Testing Strategies) to verify the system fails closed rather than open.
  • Regular external penetration testing: Complements automated scanning by finding logic flaws (A06 Insecure Design) that tools cannot reason about.

Performance, Security & Scalability Considerations

  • Authorization checks at scale: Centralize policy evaluation (e.g., a policy engine or well-tested authorization library) rather than duplicating access-control logic per endpoint, which becomes both a performance and consistency liability as the API surface grows.
  • Supply chain scanning cost: SCA and SBOM generation add pipeline time; cache scan results keyed on lockfile hash to avoid rescanning unchanged dependency trees on every commit.
  • Logging volume vs. signal: High-volume security event logging (A09) must balance completeness against log ingestion cost and alert fatigue - prioritize logging decisions (auth failures, privilege changes) over routine successful requests.
  • Fail-closed error handling under load: Ensure that fail-closed exception handling (A10) does not itself become a denial-of-service vector under high error rates (e.g., an overly aggressive lockout policy triggered by transient upstream failures).

Edge Cases & Advanced Usage

  • Multi-region/multi-tenant SSRF risk (A01): Outbound request allowlists must account for cloud metadata endpoints (e.g., 169.254.169.254) which are common SSRF targets to exfiltrate cloud credentials - explicitly block link-local and internal ranges by default.
  • Supply chain risk in AI/LLM-assisted development (A03/A08): AI code-generation tools may suggest package names that do not exist or are typosquatted; verify package names and provenance before adding any AI-suggested dependency.
  • Distributed exception handling (A10): In a microservices topology, an unhandled exception in one service must not cause an upstream service to silently treat a security-relevant call (e.g., an authorization check) as successful by default - design explicit timeout/error handling that defaults deny for security-critical calls.
  • Legacy systems unable to adopt modern crypto (A04): When constrained to maintain compatibility with legacy clients using deprecated algorithms, isolate the legacy path behind additional compensating controls (network segmentation, reduced privilege) rather than weakening the primary system's cryptographic baseline.

When Not to Use / Alternatives

The OWASP Top 10 is a prioritization tool, not an exhaustive security standard.

DimensionOWASP Top 10:2025Full Threat Modeling (STRIDE/PASTA)Compliance Framework (SOC 2, PCI-DSS, ISO 27001)
ScopeWeb application risk categoriesSystem-specific, exhaustiveOrganizational controls, broader than code
Best forPrioritizing a security backlog, educationHigh-value/high-risk feature designRegulatory/contractual requirements
ExhaustivenessTop 10 by prevalence/impact onlyAs exhaustive as the exercise is scopedExhaustive within its specific domain

Use full threat modeling for high-value features (payments, PII handling) where a prevalence-ranked top-10 list is too coarse to identify system-specific risks. Use a compliance framework when regulatory or contractual obligations require controls (audit logging retention, encryption-at-rest mandates) beyond what OWASP's awareness document covers.

Glossary

  • IDOR (Insecure Direct Object Reference): An access control flaw where an application exposes a reference (ID, filename) to an internal object without verifying the requester is authorized to access it.
  • SSRF (Server-Side Request Forgery): A vulnerability allowing an attacker to induce the server to make requests to unintended destinations, often internal services or cloud metadata endpoints.
  • SBOM (Software Bill of Materials): A structured inventory of every component in a software artifact, used to assess supply chain risk.
  • Fail closed vs. fail open: Fail closed denies access/action by default when an error occurs; fail open permits it - security-relevant checks must fail closed.
  • SAST / DAST: Static and Dynamic Application Security Testing - analyzing code without execution versus testing a running instance, respectively.