Most web application breaches do not rely on exotic zero-day exploits. They exploit familiar mistakes: a missing permission check, an unpatched library, a password stored with a weak hash, an error message that reveals too much. The OWASP Top 10 exists precisely to catalogue those recurring mistakes, and for any team building or buying software it remains the most practical starting point for web application security.
In this guide we treat the OWASP Top 10 (2021) as a working checklist rather than a poster on the wall. For each category we explain what goes wrong in plain language, how it typically shows up in real business applications, and which controls actually prevent it. We finish with what changed in the newer 2025 edition and how to build these checks into your development lifecycle so security is not a last-minute scramble.
Whether you are a CTO reviewing a supplier, a business owner commissioning a new portal or a developer hardening an existing system, this is the list to work through.
What the OWASP Top 10 is, and what it is not
The OWASP Top 10 is published by the Open Worldwide Application Security Project, a non-profit foundation that produces free, vendor-neutral security resources. It ranks broad categories of risk using vulnerability data contributed by security firms and a survey of practitioners. Each category groups related weaknesses, catalogued as Common Weakness Enumerations (CWEs).
Two caveats matter. First, it is an awareness document, not a complete standard. An application can avoid all ten categories and still have serious flaws in its business logic. Second, it describes categories, not tests. To turn it into verifiable requirements, pair it with the OWASP Application Security Verification Standard (ASVS), which defines testable controls at different assurance levels.
Even so, if your team can demonstrate solid controls for every item below, you will have closed off the routes attackers use most often.
The OWASP Top 10 (2021) at a glance
| Category | What goes wrong | Core controls |
|---|---|---|
| A01 Broken Access Control | Users can act outside their intended permissions | Deny by default, server-side authorisation on every request, ownership checks |
| A02 Cryptographic Failures | Sensitive data exposed through weak or missing encryption | TLS everywhere, strong password hashing, managed keys |
| A03 Injection | Untrusted input interpreted as code or commands (including XSS) | Parameterised queries, output encoding, input validation |
| A04 Insecure Design | Missing or ineffective security controls by design | Threat modelling, abuse cases, secure design patterns |
| A05 Security Misconfiguration | Default settings, verbose errors, open storage, missing headers | Hardened, repeatable configuration as code |
| A06 Vulnerable and Outdated Components | Known-vulnerable libraries, frameworks or runtimes | Inventory, automated dependency scanning, patching cadence |
| A07 Identification and Authentication Failures | Weak login, session and credential handling | MFA, rate limiting, secure session management |
| A08 Software and Data Integrity Failures | Untrusted updates, pipelines or deserialised data | Signed artefacts, protected CI/CD, safe deserialisation |
| A09 Security Logging and Monitoring Failures | Attacks go undetected or cannot be investigated | Security event logging, alerting, incident response |
| A10 Server-Side Request Forgery (SSRF) | Server tricked into fetching attacker-chosen URLs | Allow-lists, network segmentation, URL validation |
Access control and authentication (A01 and A07)
A01 Broken Access Control
Broken access control sits at the top of the 2021 list for good reason. Typical examples include changing an ID in a URL from /invoices/1042 to /invoices/1043 and seeing another customer’s invoice (an insecure direct object reference), calling an admin API endpoint that the interface merely hides, or a regular user modifying a hidden form field to escalate their role.
Controls that work:
- Deny by default. Every route and API endpoint should require an explicit authorisation rule; anything not explicitly allowed is refused.
- Enforce on the server. Hiding a button in the user interface is not access control. Check permissions in the backend on every request.
- Check ownership, not just role. Being a “customer” does not entitle you to every customer record. Scope queries to the authenticated user or tenant.
- Centralise the logic. Use the framework’s policy or permission system, such as Laravel policies and gates, Django permissions or ASP.NET Core authorisation policies, rather than scattering ad hoc checks.
- Write negative tests that prove a user cannot access another user’s data.
A07 Identification and Authentication Failures
This covers weak passwords, credential stuffing, poor session handling and flawed account recovery. Offer multi-factor authentication, and require it for administrators. Rate-limit and monitor login attempts. Check new passwords against lists of known-breached passwords rather than imposing arbitrary complexity rules. Regenerate session identifiers on login, set cookies with Secure, HttpOnly and an appropriate SameSite attribute, and make sure logout and password changes invalidate existing sessions. Where possible, rely on your framework’s mature authentication components or a reputable identity provider instead of building your own.
Protecting data and handling untrusted input (A02, A03 and A10)
A02 Cryptographic Failures
Formerly called “Sensitive Data Exposure”, this category focuses on the root cause: cryptography that is missing, weak or misused. Serve everything over TLS and enable HTTP Strict Transport Security. Hash passwords with an algorithm designed for the purpose, such as Argon2, bcrypt or PBKDF2 with appropriate work factors, never with plain or fast hashes. Encrypt sensitive data at rest, keep keys in a managed service such as Azure Key Vault or AWS KMS rather than in source code, and do not collect or retain personal information you do not need.
A03 Injection
Injection happens when untrusted input is sent to an interpreter as part of a command or query. SQL injection is the classic example, but the category also covers OS command injection, LDAP injection and, since 2021, cross-site scripting (XSS). The fix is to keep data and code separate:
- Use parameterised queries or your ORM’s query builder. Eloquent, the Django ORM and Entity Framework parameterise by default; the risk returns when developers concatenate strings into raw SQL.
- Rely on context-aware output encoding. Blade, Django templates and Razor escape output by default, so treat every use of “raw” or “safe” output as a code review red flag.
- Add a Content Security Policy header as a second line of defence against XSS.
- Validate input against strict allow-lists for format, length and type.
A10 Server-Side Request Forgery
SSRF arises when an application fetches a URL supplied by the user, for example to import an image, preview a link or call a webhook. An attacker supplies an internal address instead and uses your server to reach systems that are not exposed to the internet, including cloud metadata endpoints that may reveal credentials. Validate destinations against an allow-list, block private and link-local address ranges after DNS resolution, disable unnecessary redirects, and segment the network so web servers cannot reach sensitive internal services. On AWS, requiring IMDSv2 for instance metadata reduces one common SSRF impact.
Design, configuration and dependencies (A04, A05 and A06)
A04 Insecure Design
Introduced in 2021, this category recognises that some flaws cannot be fixed by perfect implementation because the design itself is unsafe. Examples include a password reset flow based on guessable security questions, or a voucher system that allows unlimited redemptions because nobody considered abuse. The remedy is to think about security before coding: run lightweight threat modelling sessions, write abuse cases alongside user stories (“as an attacker, I want to reuse a voucher code”), and apply proven patterns for things like payments and multi-tenancy. In our process this belongs firmly in the Structuring Phase, where the system’s architecture is defined.
A05 Security Misconfiguration
Debug mode left on in production, default admin credentials, directory listing, publicly readable storage buckets, permissive CORS rules and verbose stack traces all fall here, as does XML External Entity (XXE) processing. Treat configuration as code: define environments through infrastructure as code, keep production settings in version control (secrets excluded), and apply security headers consistently. Automated configuration checks in your pipeline catch drift before it reaches users.
A06 Vulnerable and Outdated Components
Modern applications are mostly third-party code. If you cannot list your dependencies and their versions, you cannot know when one of them becomes a risk. Maintain a software bill of materials, enable automated dependency scanning (for example GitHub Dependabot, composer audit, pip-audit or npm audit), track the support lifecycle of your framework and runtime, and schedule regular upgrade work rather than waiting for an emergency. Unsupported frameworks are a common finding when we audit older systems.
Integrity, logging and monitoring (A08 and A09)
A08 Software and Data Integrity Failures
This category covers code and data that are trusted without verification: auto-update mechanisms without signature checks, scripts loaded from untrusted CDNs without Subresource Integrity, insecure deserialisation, and, increasingly, compromised CI/CD pipelines. Protect your build pipeline as carefully as production. Restrict who can modify workflows, require reviews on protected branches, pin third-party actions and packages to known versions, and use short-lived credentials for deployments. Our guide to CI/CD pipelines for small teams covers these practices in more detail.
A09 Security Logging and Monitoring Failures
If an attacker probes your login page for a week and nobody notices, every other control is weakened. Log authentication events, access control failures, input validation failures and high-value transactions with enough context to investigate, but without writing passwords, tokens or unnecessary personal information to logs. Centralise logs, protect them from tampering, and alert on suspicious patterns. Just as importantly, agree in advance who responds to an alert and how.
For South African organisations this connects directly to the Protection of Personal Information Act. Section 19 of POPIA requires appropriate, reasonable technical and organisational measures to secure personal information, and section 22 requires notification of the Information Regulator and affected data subjects when there are reasonable grounds to believe personal information has been accessed by an unauthorised person. You cannot meet that obligation if you cannot detect a compromise in the first place.
What changed in the OWASP Top 10:2025
OWASP has since published a 2025 edition of the Top 10. Much of the 2021 list carries forward, so work done against it is not wasted, but there are notable changes:
- Broken Access Control remains first, and Server-Side Request Forgery is now folded into it rather than standing alone.
- Security Misconfiguration moves up to second place.
- Software Supply Chain Failures is a new, broader category that expands on the old “Vulnerable and Outdated Components”, covering the wider ecosystem of dependencies, build systems and distribution.
- Mishandling of Exceptional Conditions is a new category covering improper error handling, failing open and similar logic flaws when systems meet unexpected states.
- Several categories are renamed, for example “Authentication Failures” and “Security Logging and Alerting Failures”, which places more emphasis on acting on logs, not only collecting them.
Our practical advice: if a contract, policy or audit references the 2021 list, keep using it as the primary checklist and map the new 2025 categories onto your controls. Supply chain security and robust exception handling deserve attention regardless of which edition you cite. Always refer to the official OWASP project page for the authoritative wording.
Building security into your development lifecycle
A checklist is only useful if it is applied continuously. Security reviewed once before launch decays with every release. These are the practices we recommend embedding into everyday delivery:
- Design: include threat modelling and abuse cases in architecture and backlog refinement.
- Develop: follow framework conventions, use secure defaults, and refer developers to the OWASP Cheat Sheet Series for specific topics.
- Review: add security items to your pull request template, particularly any raw SQL, raw output, authorisation changes or new dependencies.
- Test: combine positive tests (authorised users can do their job) with negative tests (unauthorised users cannot). Our article on positive and negative testing explains why both matter.
- Automate: run static analysis, dependency scanning and secret scanning on every pull request, and dynamic scanning against a staging environment.
- Deploy and monitor: use hardened, reproducible infrastructure, then watch logs and alerts in production.
- Revisit: commission independent penetration testing for high-risk systems and after major changes.
Precision in software development includes precision in security. Attention to detail is what separates an application that merely works from one that can be trusted with your customers’ data.
How Adriel Corp can help
Security is built into how we work, from technology selection in the Conceptual Phase and architecture in the Structuring Phase to the positive and negative tests our QA team runs before anything is deployed. If you have an existing application and are not sure how it measures up against the OWASP Top 10, our Code Audit & Refactoring Sprint is designed to identify weaknesses such as outdated dependencies, missing access checks and risky code patterns, and then fix the most important ones.
Whether you need a review of a legacy platform or a new web application designed securely from day one, our team would be glad to help.