🔟 The OWASP Top 10, With Real Examples
The OWASP Top 10, With Real Examples
The OWASP Top 10 is an awareness document: ten broad categories of web application risk, published by the Open Worldwide Application Security Project. Everything below describes the 2025 edition — the eighth release of the list and the current published version. The 2021 edition it replaced is still quoted verbatim across compliance annexes, vendor questionnaires, scanner rule packs and contract language, so the mapping section further down places every 2021 category in its 2025 home. Expect to have to recognise both lists for some years yet.
Two things it is not: a prediction of what will happen to you specifically, and a certification checklist — a clean pass on all ten categories is not a statement that an application is secure. It is a shared vocabulary, so that "that is an A01" means the same thing to everyone in the room. Because the numbering moves between editions, always say which edition you mean; "A10" alone is ambiguous. What follows is one paragraph of meaning, one minimal example and the specific defence, per category.
A01:2025 — Broken Access Control
The application knows who you are but fails to enforce what you may do. It covers object-level authorisation (reading someone else's record), function-level authorisation (reaching an admin endpoint), and privilege escalation through parameters the client controls. In 2025 it also took in Server-Side Request Forgery — the server being talked into reaching a resource it should not — and Cross-Site Request Forgery, the user's browser being talked into acting with authority it holds. Both are access-control failures in a different costume, which is why they now sit here.
GET /api/invoices/1043 Authorization: Bearer <valid token for user 7>
# The handler loads invoice 1043 and returns it.
# It never asks: does invoice 1043 belong to user 7?
Defence: deny by default for everything not deliberately public, and route every read and write through one central authorisation layer that takes the acting identity and the target object together. Scope queries by owner (WHERE id = ? AND owner_id = ?) rather than checking after fetching. Enforce business limits in the domain model, not in the UI. Never trust a role, tenant id or price that arrived from the client. Hiding a link is not access control, and replacing sequential ids with UUIDs is obscurity, not authorisation. Log authorisation failures and alert on them — a burst is one of the highest-signal detections available — and rate limit the endpoints that enumerate. For the SSRF half of the category: allowlist destinations, validate the address DNS actually returned and then connect to that address, restrict schemes, filter egress at the network layer, and require token-based cloud metadata access. For the CSRF half: SameSite cookies plus a per-session token, and no state change on a GET. test a host's CORS configuration → · probe which methods an endpoint answers →
A02:2025 — Security Misconfiguration
Up from fifth, and the clearest statement of what this edition is about: the code can be flawless and the deployment still hands the system over. Default credentials left in place, debug modes in production, verbose errors, directory listings, sample applications never removed, unnecessary ports, services and accounts enabled, over-permissive cloud storage, missing or wrong security headers. XML External Entities lives here too, because XXE is nothing more than an XML parser configured to resolve external entities.
DEBUG = True # Django in production: an unhandled exception renders
# a stack trace, local variables and settings to the browser
Defence: one hardened, repeatable build process used for every environment, with configuration in version control and the difference between environments limited to values rather than settings. Ship a minimal platform — remove unused features, components, sample applications and documentation. Segment the architecture so a misconfigured component is not adjacent to everything else. Review configuration whenever you patch, because upgrades quietly re-enable things. Verify automatically: a test asserting that DEBUG is off, the admin path is unreachable and the expected headers are present is worth more than a hardening page nobody reads. Review object-storage permissions explicitly, and prefer federated identity and short-lived credentials over secrets embedded in configuration. grade a host's security headers → · check your own site for exposed files such as /.env and /.git →
A03:2025 — Software Supply Chain Failures
The broadened successor to "Vulnerable and Outdated Components", and a real change of scope rather than a new label. The old category asked one question: are you running a library with a published vulnerability? This one asks whether the entire path by which software is built, distributed and updated can be trusted. That takes in compromised vendor updates, malicious code introduced through a dependency, unsecured artifact repositories and build systems, CI/CD pipelines with no integrity controls, weak change management, and transitive dependencies nobody chose. OWASP's illustrative scenarios for the category span both halves of that: the SolarWinds vendor compromise and the self-propagating Shai-Hulud npm worm on the supply-chain side, Log4Shell and the Struts 2 remote-execution flaw on the known-vulnerability side.
$ npm ls minimist
app@1.0.0
└─┬ some-build-tool@3.1.0
└─┬ mkdirp@0.5.1
└── minimist@0.0.8 # transitive, three levels down, nobody chose it
Defence: generate and centrally track an SBOM of what actually ships, including transitive dependencies and container base images, and monitor it continuously against vulnerability data — OWASP's own Dependency-Track, Dependency-Check and retire.js are the reference tooling. Fail the build on unfixed criticals. Remove unused dependencies, the fastest way to stop tracking a component. Obtain packages only from official sources over secure connections, preferring signed artefacts. Then secure the pipeline itself, because that is the part the old category ignored: multi-factor authentication and least privilege on repositories, CI/CD systems and artifact storage, separation of duties, reviewed pipeline changes, and a record of what changed where. Roll updates out in stages rather than everywhere at once, so a poisoned release has a blast radius rather than a fleet. fingerprint what a page actually loads, and what versions it discloses →
A04:2025 — Cryptographic Failures
Down two places, unchanged in substance. It spans data in transit and at rest: missing TLS, obsolete protocol versions, weak or misused algorithms, predictable initialisation vectors, keys generated or stored badly, and passwords stored with fast hashes. The name points at the cause rather than the symptom — the older "Sensitive Data Exposure" described what the user sees once the cryptographic decision has already gone wrong.
-- Users table, real-world pattern
password = md5(:plaintext) -- unsalted, and MD5 is trivially bulk-crackable
Defence: classify data first — you cannot protect what you have not identified, and the cheapest control is not storing it at all. TLS 1.2 or better everywhere, with HSTS; retire SSL 3.0, TLS 1.0 and 1.1. For data at rest use authenticated encryption (AES-GCM or ChaCha20-Poly1305) with a unique nonce per message from a cryptographically secure generator, and keys held in an HSM or key management service, never a constant in the repository. For passwords use a deliberately slow, adaptive function — Argon2, scrypt, or PBKDF2-HMAC-SHA-512 where a validated primitive is required. Retire MD5, SHA-1 and unauthenticated CBC mode. Do not design your own construction. The 2025 guidance adds one item the 2021 edition did not carry: begin planning the migration to post-quantum algorithms, with 2030 named as the date to be ready by. inspect a host's certificate and TLS configuration →
A05:2025 — Injection
Down two places from third, and still one of the largest categories by weakness count. Untrusted input is parsed as part of a command, query or markup. Cross-Site Scripting remains inside this category, characterised by OWASP as high frequency but low impact relative to the rest of it, alongside SQL, NoSQL, ORM, OS command, LDAP and expression-language injection.
$sql = "SELECT id FROM users WHERE email = '" . $_GET['email'] . "'";
// email = ' OR '1'='1 → the WHERE clause is now always true
Defence: keep data out of the syntax. Parameterised queries and safe APIs; contextual output encoding for anything rendered; allowlist validation for the parts that genuinely cannot be parameterised, such as a sort column name. Validate server-side — client-side validation is a usability feature, not a control. Put static analysis, dynamic analysis and fuzzing into the pipeline so a regression is caught before release rather than in a report. The full mechanics of each class, and why escaping and blocklists lose over time, are covered in the injection module of this track.
A06:2025 — Insecure Design
Down two places, and still the category people find hardest, because there is no line of code to point at. It is a missing or ineffective control: the flaw is in what was specified, not in how it was built. A secure design can of course be implemented badly — that is what the other nine categories are for — but a perfectly implemented insecure design is simply insecure, and no amount of code review will fix it.
Password reset flow:
1. User supplies email
2. User answers "What was your first pet's name?"
3. User sets a new password
# Implemented flawlessly. The knowledge factor is public information.
The business-logic version has the same shape. A booking system with no cap on how many discounted seats one account may hold, or a product launch with no bot protection, is not malfunctioning — it is doing exactly what it was designed to do, at a scale nobody specified against.
Defence: threat model during design, not after. Write abuse cases next to user stories ("an attacker submits this form 10,000 times"). Put the limits into the requirements: rate limits per business flow, spend ceilings, approval steps on irreversible actions, and a documented answer to "what happens when this is automated". Keep a library of secure design patterns instead of re-deciding each time, validate at every tier rather than only at the edge, write tests that check critical flows against the threat model, and segregate layers and tenants by design rather than by convention. Use established reference architectures instead of inventing an authentication scheme.
A07:2025 — Authentication Failures
Renamed from "Identification and Authentication Failures" to fit the weaknesses it actually contains. The failure is that an attacker gets the system to accept an invalid or incorrect user as legitimate: credential stuffing and password spraying with no throttling, weak or breached passwords accepted, missing multi-factor authentication, inadequate password storage, session identifiers exposed in URLs, sessions that never expire or are not invalidated at logout.
POST /login email=victim@example.com&password=Summer2025!
# No rate limit, no lockout, no MFA, no alerting.
# The attacker replays a credential dump at a few requests per second,
# then runs the same list again with the year incremented.
Defence: multi-factor authentication, preferring phishing-resistant methods; screen new passwords against known-breached corpora; drop composition rules and forced rotation in favour of length, in line with NIST SP 800-63B; rate limit and alert on authentication failures per account and per source; respond identically whether or not an account exists, so login and reset are not enumeration oracles; use a server-side session manager that issues high-entropy identifiers, rotate them on login and on privilege change, and enforce idle and absolute timeouts. Where you reasonably can, adopt a well-trusted existing authentication system rather than writing one. The sessions module in this track covers the mechanics. check a password against breach data → · decode a token to see what is in it →
A08:2025 — Software or Data Integrity Failures
Code or data is treated as trusted and valid without anything having verified it. That covers auto-update mechanisms with no signature check, CI/CD pipelines that pull unverified build steps, third-party scripts loaded without integrity checks, and deserialisation of untrusted objects. The line between this and A03 is worth holding onto: A03 is about the pipeline that produces and delivers an artefact, A08 is about your application accepting an artefact — or a serialised blob, or a script tag — without checking it at the point of use.
<script src="https://cdn.example.net/widget.js"></script>
<!-- No integrity attribute. Whoever controls that CDN account,
now or in six months, controls your page. -->
Defence: verify digital signatures or attestations on dependencies and artefacts; consume packages only from repositories you trust; pin versions and review lockfile changes as code; add Subresource Integrity (integrity="sha384-..." with crossorigin) to third-party scripts, or better, self-host them; treat the CI configuration and its secrets as production infrastructure, with reviewed changes and segregation of duties; and reject serialised data arriving from untrusted clients rather than reconstructing arbitrary object types from it.
A09:2025 — Security Logging and Alerting Failures
Renamed from "Security Logging and Monitoring Failures", and the new word carries the point: logs nobody acts on are storage, not a control. The failure is that the breach is not detected, or is detected too late, or cannot be reconstructed afterwards. Auditable events go unlogged, entries lack the context to be useful, logs are not protected from tampering, alert thresholds do not exist, and nobody has ever exercised the response path.
# Access log after a successful account takeover
10.0.0.5 - - [12/Mar/2026:04:11:02] "POST /login HTTP/1.1" 302 -
# No record of which account, no record of the 14,000 failures before it.
Defence: log authentication successes and failures, access-control denials, input-validation failures and high-value transactions, each with a timestamp, the acting identity, the source address and the outcome, in a format your log platform can parse. Ship logs off the host to storage the application cannot rewrite, and use append-only or otherwise integrity-protected audit trails. Encode user-controlled fields so an attacker cannot forge log lines by injecting newlines, and never log secrets, session identifiers or full card numbers. Then build the alerting half: a monitoring use case and a response playbook per detection, thresholds on rates rather than single events, behavioural baselines to keep the false-positive rate survivable — and a rehearsal, because an untested response plan is a document. parse a log sample and look at what is in it →
A10:2025 — Mishandling of Exceptional Conditions
New in 2025. OWASP describes three failings, any one of which qualifies: the application does not prevent an unusual situation from happening, it does not identify the situation as it is happening, and it responds poorly or not at all afterwards. In practice that gathers up fail-open error handling, exceptions caught far from where they arose and quietly swallowed, resources never released on the error path, multi-step transactions abandoned half-applied with no rollback, race conditions, and errors that hand internal detail back to the caller.
try:
allowed = authz.check(user, resource) # raises when authz times out
except Exception:
allowed = True # "do not block users if authz is having a bad day"
if allowed:
return resource # the outage is now an authorisation bypass
Defence: fail closed. Handle each exception where it happens, at the granularity that knows what to do about it, instead of letting one top-level handler decide for everything. Catch the specific exception you expect, never the base class. Release resources deterministically on every path — a finally block, a context manager, a scope guard — so an error cannot leak file handles, connections or locks until the process falls over. Roll multi-step transactions back atomically, so an interruption cannot leave money moved and not recorded. Put quotas and rate limits on anything an attacker can exhaust deliberately. Validate input strictly at the boundary, so fewer exceptional conditions arise at all. And log the failure with full internal detail while returning the caller a generic error and a correlation id — which is the A02 rule about verbose errors, seen from the other side.
2021 → 2025: where each category went
Older audit reports, compliance annexes, training decks and scanner rule packs still speak in 2021 identifiers, and will for a while. This maps them across.
| 2021 category | What happened | 2025 home |
|---|---|---|
| A01:2021 Broken Access Control | Kept the top spot; scope widened | A01:2025 Broken Access Control — now also holds SSRF and CSRF |
| A02:2021 Cryptographic Failures | Moved down two places; unchanged in substance | A04:2025 Cryptographic Failures |
| A03:2021 Injection | Moved down two places; still contains XSS | A05:2025 Injection |
| A04:2021 Insecure Design | Moved down two places | A06:2025 Insecure Design |
| A05:2021 Security Misconfiguration | Moved up three places; still contains XXE | A02:2025 Security Misconfiguration |
| A06:2021 Vulnerable and Outdated Components | Renamed and substantially broadened, from "is this library patched" to the whole build, distribution and update path | A03:2025 Software Supply Chain Failures |
| A07:2021 Identification and Authentication Failures | Renamed; same position | A07:2025 Authentication Failures |
| A08:2021 Software and Data Integrity Failures | Retitled ("and" became "or"); same position; still contains insecure deserialisation | A08:2025 Software or Data Integrity Failures |
| A09:2021 Security Logging and Monitoring Failures | Renamed to stress that alerting, not collection, is the control; same position | A09:2025 Security Logging and Alerting Failures |
| A10:2021 Server-Side Request Forgery | Merged away; no longer a standalone category | Folded into A01:2025 Broken Access Control |
| — (did not exist in 2021) | New category | A10:2025 Mishandling of Exceptional Conditions |
Nothing was dropped outright — every 2021 category is still represented somewhere — and the single structural removal is SSRF ceasing to have its own entry. Two practical consequences. First, an identifier without a year is ambiguous in exactly the place it matters: "A10" meant Server-Side Request Forgery in 2021 and means Mishandling of Exceptional Conditions in 2025, and "A02", "A03", "A04" and "A05" all changed hands. Second, plenty of compliance regimes, procurement questionnaires and internal standards still enumerate the 2021 categories by name, so findings written against the 2025 list may need translating back before they satisfy the paperwork. Keep the mapping to hand rather than arguing about which list is correct — both are, for their own edition.
The 2025 list at a glance
| ID | Category | Smells like | Primary defence |
|---|---|---|---|
| A01 | Broken Access Control | An id in the URL nobody checks ownership of; a URL the server will fetch on request | Deny by default; authorise per request against the acting identity |
| A02 | Security Misconfiguration | Debug on, defaults left, buckets open, sample apps still installed | One hardened repeatable build, minimal surface, verified automatically |
| A03 | Software Supply Chain Failures | A lockfile nobody has read and a build pipeline nobody has threat modelled | SBOM plus continuous monitoring; signed packages; a locked-down build system |
| A04 | Cryptographic Failures | Fast hashes, hardcoded keys, plaintext transport | TLS everywhere; authenticated encryption; a real password KDF |
| A05 | Injection | String concatenation reaching an interpreter | Parameterised queries; contextual output encoding |
| A06 | Insecure Design | A flow with no limit, no verification step, no abuse case | Threat modelling and security requirements before code |
| A07 | Authentication Failures | Unlimited login attempts, no MFA, immortal sessions | MFA, breach-list screening, throttling, session rotation |
| A08 | Software or Data Integrity Failures | Code or data trusted because of where it came from | Signatures and attestations; SRI; no untrusted deserialisation |
| A09 | Security Logging and Alerting Failures | An incident that has to be reconstructed from memory | Log auditable events centrally; alert on rates; rehearse response |
| A10 | Mishandling of Exceptional Conditions | A bare except that sets a permission to true | Fail closed; handle errors locally; release resources on every path |
- This module documents the 2025 edition. Always name the edition — categories move, merge and get renamed, so a bare "A10" says nothing.
- The 2021 list is still quoted across compliance material and tooling. Learn the mapping rather than swapping one list for the other.
- A01 Broken Access Control still heads the list and has absorbed SSRF and CSRF. Authorisation is per-request, per-object, server-side, deny-by-default.
- The 2025 ordering favours systemic risk: Security Misconfiguration at A02 and Software Supply Chain Failures at A03 now outrank both Cryptographic Failures and Injection.
- A03 is broader than "patch your libraries" — it covers building, distributing and updating software, and the systems that do it.
- A10 Mishandling of Exceptional Conditions is new: fail closed, handle errors where they happen, and never let a dependency's outage become an authorisation bypass.
- It remains an awareness document and a shared vocabulary, not a certification checklist and not a coverage guarantee.
- A separate OWASP API Security Top 10 exists for API-specific risks and is worth reading alongside this one.