The OWASP Top 10 is the industry-standard list of the most critical web application security risks. It is not a compliance checklist and it is not exhaustive — it is a ranking of what actually goes wrong most often, drawn from data across hundreds of thousands of applications.
Treating these ten as a baseline will stop the overwhelming majority of real-world attacks. Below is each one in plain English: what it is, what it looks like in a real codebase, how a tester finds it, and what to do about it.
The list at a glance
| Risk | The one-line version |
|---|---|
| A01 Broken Access Control | Users doing things they should not be allowed to do |
| A02 Cryptographic Failures | Sensitive data exposed because it was not properly protected |
| A03 Injection | Untrusted input changing the meaning of a query or command |
| A04 Insecure Design | The flaw is in the architecture, not the code |
| A05 Security Misconfiguration | Defaults left on, hardening left off |
| A06 Vulnerable Components | A dependency with a known CVE, still shipping |
| A07 Authentication Failures | Weak identity handling, sessions and passwords |
| A08 Integrity Failures | Trusting code or data whose origin you cannot verify |
| A09 Logging & Monitoring Failures | The breach happened and nobody noticed |
| A10 Server-Side Request Forgery | Your server tricked into fetching something internal |
1. Broken Access Control
The most common serious finding in real applications, and the one that most often leads directly to a data breach. Access control decides who may do what; it breaks when that decision is made anywhere other than the server, or is not made at all.
What it looks like
GET /api/invoices/1042 → 200 OK your invoice
GET /api/invoices/1043 → 200 OK someone else's invoice
# The server checked that you are logged in.
# It never checked that invoice 1043 belongs to you.
How it is found
A tester logs in as two separate low-privilege users and replays one user’s requests with the other’s session, incrementing every identifier in every URL and body. They also replay an admin request as a normal user. It takes minutes and it works far more often than teams expect.
How to defend it
- Deny by default. Every route requires an explicit grant, never an explicit block
- Check ownership, not just authentication — “is this record yours?” on every read and write
- Enforce on the server for every request. A hidden menu item is not access control
- Use unguessable identifiers (UUIDs) so enumeration is harder, as defence in depth only
- Write an automated test per role that asserts the forbidden action returns 403
2. Cryptographic Failures
Formerly called Sensitive Data Exposure. The failure is rarely a broken algorithm — it is data that was never encrypted, encrypted with something obsolete, or protected by a key sitting next to it in the repository.
What it looks like
# Found in a real .env committed to git:
DB_PASSWORD=Summer2024!
JWT_SECRET=secret
# And in the users table:
password_hash = md5(password) # MD5 is not a password hash
How it is found
Grep the repository history for secrets, check TLS configuration and certificate validity, inspect how passwords are stored, and look for personal data written to application logs or sent to third-party analytics.
How to defend it
- Classify what is actually sensitive first — you cannot protect everything equally
- TLS everywhere, with HSTS. No mixed content, no optional HTTP
- Hash passwords with Argon2id or bcrypt, never MD5, SHA-1 or plain SHA-256
- Encrypt sensitive fields at rest, and keep keys in a secret manager, not in the repo
- Never log tokens, card data, or identity documents — logs are copied everywhere
3. Injection
Untrusted input crosses a boundary and is interpreted as code rather than data. SQL injection is the famous case; the same shape appears in NoSQL queries, OS commands, LDAP filters, XPath and template engines. Cross-site scripting is injection into the browser.
What it looks like
# Vulnerable: the input becomes part of the query
cur.execute("SELECT * FROM users WHERE email = '" + email + "'")
# email = anything' OR '1'='1
# → returns every user in the table
# Safe: the input can only ever be a value
cur.execute("SELECT * FROM users WHERE email = %s", (email,))
How it is found
Automated scanners find the obvious cases; manual testing finds the rest by probing every input that reaches a query — including headers, cookies, filenames and JSON fields that never appear in the UI.
How to defend it
- Use parameterised queries everywhere. String concatenation into a query is the bug itself
- Prefer an ORM, but know where it drops to raw SQL — that is where injection returns
- Validate input against an allow-list of what is acceptable, not a block-list of what is not
- Encode on output, contextually — HTML, attribute, JavaScript and URL contexts differ
- Set a Content-Security-Policy so an XSS payload cannot easily load or exfiltrate
4. Insecure Design
A category for flaws that no amount of careful coding fixes, because the design itself is wrong. A password reset that emails a six-digit code with no rate limit is not badly implemented — it is badly designed. This one is cheapest to fix before anything is built.
How it is found
Threat modelling, not scanning. Walk each critical flow and ask what an attacker who understands the business would do: order a negative quantity, refund twice, register with someone else’s email, cancel after delivery.
How to defend it
- Threat-model the critical flows before writing them — an afternoon per flow
- Design for least privilege and fail-safe defaults from the start
- Put limits in the design: rate limits, transaction ceilings, cool-down periods
- Write abuse cases alongside user stories — what should be impossible, stated explicitly
- Model state machines explicitly for anything with an approval step
5. Security Misconfiguration
The broadest category and the easiest to accumulate. Default credentials, verbose error pages, directory listing, an open storage bucket, a debug endpoint left enabled, missing security headers, an admin panel reachable from the internet.
What it looks like
# Headers a hardened response should carry
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: default-src 'self'; object-src 'none'
Referrer-Policy: strict-origin-when-cross-origin
How it is found
Header inspection, default-credential checks, port scanning, and looking for the endpoints that exist but are not linked — /debug, /.env, /backup.zip, /phpinfo.php.
How to defend it
- Harden every environment identically. Staging with debug on is a production risk
- Remove what you do not use — sample apps, unused ports, dormant admin accounts
- Automate configuration so a hardened state is reproducible, not hand-applied
- Return generic error messages to users; keep stack traces in server-side logs
- Never serve archives, backups or dumps from the web root
6. Vulnerable and Outdated Components
Modern applications are mostly other people’s code. A single dependency with a known CVE is an open door, and attackers scan for known-vulnerable versions at internet scale within days of disclosure.
What it looks like
$ npm audit
found 23 vulnerabilities (4 moderate, 17 high, 2 critical)
$ pip-audit
Found 6 known vulnerabilities in 4 packages
How it is found
Software composition analysis against a dependency manifest. This is the one risk that is almost entirely automatable, which is why leaving it unfixed is difficult to defend afterwards.
How to defend it
- Keep an inventory of every dependency, direct and transitive
- Run
npm audit,pip-auditor Dependabot in CI and fail the build on critical - Patch on a schedule, not on an incident. Monthly beats annually by a wide margin
- Remove unused packages — the safest dependency is the one you deleted
- Pin versions and use lockfiles so builds are reproducible
7. Identification and Authentication Failures
Weak passwords, no rate limiting, guessable reset flows, sessions that never expire, tokens that survive a logout. Credential stuffing — replaying passwords leaked from other breaches — is now the dominant attack against login endpoints.
What it looks like
# Session handling that fails
session.id unchanged after login → session fixation
JWT no exp claim → token valid forever
logout clears the cookie only → token still works
How it is found
Testing the login, registration, reset and logout flows for rate limiting, user enumeration (does a wrong password differ from a wrong email?), session rotation on privilege change, and token invalidation after logout.
How to defend it
- Enforce MFA, especially for admin accounts. It defeats credential stuffing outright
- Rate-limit and progressively delay authentication attempts per account and per IP
- Check new passwords against known-breached password lists
- Rotate the session identifier on login and on any privilege change
- Give tokens a short expiry and make logout invalidate server-side, not just client-side
8. Software and Data Integrity Failures
Trusting code, updates or serialised data whose origin and integrity you have not verified. This covers insecure deserialisation, unsigned auto-updates, and the CI/CD pipeline itself — the supply chain attacks that have dominated recent years live here.
What it looks like
<!-- No integrity check: if the CDN is compromised, so are you -->
<script src="https://cdn.example.com/lib.js"></script>
<!-- Verified: the browser refuses a modified file -->
<script src="https://cdn.example.com/lib.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9Gq..."
crossorigin="anonymous"></script>
How it is found
Reviewing how third-party code is loaded, whether artefacts are signed, who can push to the pipeline, and whether any endpoint deserialises data it did not create.
How to defend it
- Use Subresource Integrity on every external script and stylesheet
- Never deserialise untrusted input into objects. Use plain JSON with a schema
- Sign build artefacts and verify signatures before deploy
- Lock down CI/CD: least-privilege tokens, protected branches, reviewed pipeline changes
- Pull dependencies from a trusted registry and review lockfile changes in code review
9. Security Logging and Monitoring Failures
Not an attack, but the reason attacks succeed for months. The industry median time to detect a breach is still measured in months, and in most incidents the evidence needed to answer “whose data?” either was never recorded or had already rotated away.
What it looks like
# What a useful auth log line contains
ts=2026-08-21T14:03:11Z event=login.failed user=a***@ex.com
ip=203.0.113.9 ua="curl/8.4" attempt=47 window=60s
How it is found
Reviewing what is logged, where logs go, how long they are kept, and whether anything raises an alert. Under DPDP the 72-hour reporting clock makes this directly a compliance question as well as a security one.
How to defend it
- Log authentication, access control failures, and every server-side input validation failure
- Keep logs somewhere the application cannot rewrite, with enough retention to investigate
- Alert on patterns, not single events — failed logins per account, 403 spikes per user
- Never log secrets or personal data; mask before writing
- Rehearse an investigation once, so you learn which log answers the scope question
10. Server-Side Request Forgery (SSRF)
Your server is tricked into making a request on an attacker’s behalf — usually to somewhere they cannot reach themselves, such as an internal service or a cloud metadata endpoint. Any feature that fetches a user-supplied URL is a candidate.
What it looks like
POST /api/import { "url": "https://example.com/data.csv" }
# Attacker substitutes:
{ "url": "http://169.254.169.254/latest/meta-data/iam/" }
# → cloud credentials returned to the attacker
How it is found
Testing every feature that accepts a URL — webhooks, imports, PDF generation, image fetching, link previews — against internal ranges, localhost, and metadata addresses, including redirect and DNS-rebinding bypasses.
How to defend it
- Allow-list destination hosts and schemes. Block-lists are bypassed routinely
- Resolve the hostname and validate the resulting IP, then pin it for the request
- Block link-local, loopback and private ranges explicitly, including IPv6
- Disable redirect following, or re-validate the target after every redirect
- Require IMDSv2 on AWS and segment the network so an SSRF reaches nothing useful
Where to start
You do not have to fix all ten at once, and the order matters more than the coverage. In practice this sequence closes the most exposure per hour spent.
| Order | Do this | Why first |
|---|---|---|
| 1 | Turn on MFA everywhere | Same-day, free, defeats the most common attack |
| 2 | Run a dependency scan and patch critical | Fully automatable, and scanned for at internet scale |
| 3 | Add security headers and a CSP | One config change, reduces the impact of several risks |
| 4 | Audit access control on every endpoint | Highest-severity findings live here |
| 5 | Fix logging so an incident is answerable | Determines whether you can ever prove scope |
After that, a manual test is what finds the rest. Automated scanners are good at injection and outdated components and close to useless at broken access control and insecure design, because those require understanding what the application is for.
Frequently asked questions
Is the OWASP Top 10 a compliance standard?
No. It is an awareness document and a starting baseline. For a testing standard use the OWASP Web Security Testing Guide or the Application Security Verification Standard; for a control framework use ISO 27001 or SOC 2. The Top 10 tells you what breaks most often, which is a different and useful question.
Does a scanner cover this?
Partly. Scanners reliably find injection, missing headers and vulnerable dependencies. They systematically miss broken access control, insecure design and business-logic abuse — which together account for the most severe findings in most engagements. Use both.
How often should we test?
Continuously for dependencies, on every release for automated checks, and manually at least annually or after any significant change to authentication, authorisation or payment flows.
What does a test cost?
A focused assessment of one application starts at ₹16,000. We publish a full sample quotation and the ten-phase methodology on the penetration testing page, and every engagement includes a free retest after you have fixed what we found. See what software costs in India for how that sits against build budgets.
We run manual web application security audits against the OWASP Top 10 and hand you a prioritised, plain-language report with the fixes.
Explore cyber security →