The OWASP Top 10 is the most widely recognized standard for web application security awareness. Published by the Open Web Application Security Project (OWASP), this list represents a broad consensus about the most critical security risks facing web applications. The 2021 edition introduced significant changes from its 2017 predecessor, reflecting how the threat landscape has evolved over the past several years.
For development teams, understanding the OWASP Top 10 is not optional — it is the baseline. Compliance frameworks like PCI DSS, HIPAA, SOC 2, and ISO 27001 either directly reference OWASP or expect equivalent coverage. More importantly, these ten categories account for the vast majority of real-world breaches that destroy companies, expose customer data, and erode user trust.
In this guide, we walk through every category in the OWASP Top 10 2021 list, explain what makes each dangerous, provide code examples of vulnerable and secure patterns, and describe how automated security scanning tools detect these risks before they reach production.
Why this matters: According to research aggregated by OWASP, over 500,000 applications were analyzed to produce the 2021 list. The data shows that 94% of applications tested had some form of broken access control, making it the single most common vulnerability class.
A01:2021 — Broken Access Control Critical
Broken Access Control moved from the fifth position in 2017 to the number one spot in 2021. This category covers any failure where users can act outside their intended permissions. This includes accessing other users' data, modifying access rights, elevating privileges, or bypassing authorization checks entirely.
Common manifestations include Insecure Direct Object References (IDOR), missing function-level access control, CORS misconfiguration, and path traversal. The reason this jumped to the top is that 94% of applications tested showed some form of broken access control, with an average incidence rate of 3.81%.
Vulnerable Code Example
// INSECURE: No authorization check - any user can access any account
app.get('/api/account/:id', (req, res) => {
const account = db.getAccount(req.params.id);
res.json(account);
});
// INSECURE: Client-side role check only
app.get('/admin/users', (req, res) => {
// Relying on frontend to hide this route from non-admins
const users = db.getAllUsers();
res.json(users);
});
Secure Code Example
// SECURE: Verify the authenticated user owns the requested resource
app.get('/api/account/:id', authenticate, (req, res) => {
const account = db.getAccount(req.params.id);
if (account.ownerId !== req.user.id) {
return res.status(403).json({ error: 'Access denied' });
}
res.json(account);
});
// SECURE: Server-side role enforcement
app.get('/admin/users', authenticate, authorize('admin'), (req, res) => {
const users = db.getAllUsers();
res.json(users);
});
How automated scanning detects this: Static Application Security Testing (SAST) tools analyze code paths to identify API endpoints that lack authorization middleware. They flag routes where request parameters (like user IDs) flow directly into database queries without ownership validation. Dynamic Application Security Testing (DAST) tools actively probe endpoints by manipulating session tokens and resource identifiers to test for IDOR vulnerabilities.
A02:2021 — Cryptographic Failures Critical
Previously known as "Sensitive Data Exposure," this category was renamed to focus on the root cause rather than the symptom. Cryptographic failures happen when applications fail to properly protect data in transit or at rest. This includes using deprecated algorithms (MD5, SHA1 for hashing passwords), transmitting data in cleartext, using weak or predictable keys, and improper certificate validation.
Real-world impact is severe: breaches at major organizations have exposed hundreds of millions of records because passwords were stored with weak hashing, database connections lacked TLS, or encryption keys were hardcoded in source code.
Vulnerable Code Example
import hashlib
# INSECURE: MD5 is broken for password hashing, no salt
def hash_password(password):
return hashlib.md5(password.encode()).hexdigest()
# INSECURE: Hardcoded encryption key
SECRET_KEY = "mySecretKey123"
encrypted = aes_encrypt(data, SECRET_KEY)
Secure Code Example
import bcrypt
import os
# SECURE: bcrypt with automatic salting and configurable work factor
def hash_password(password):
return bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
# SECURE: Key retrieved from a secrets manager at runtime
secret_key = secrets_manager.get_secret("encryption-key")
encrypted = aes_gcm_encrypt(data, secret_key)
How automated scanning detects this: SAST tools identify usage of weak cryptographic algorithms by matching API calls to known-insecure functions. They also detect hardcoded keys and secrets using pattern matching and entropy analysis. Secrets scanners specifically target cryptographic material, connection strings, and API tokens embedded in source code.
A03:2021 — Injection Critical
Injection dropped from the number one position (held since the first OWASP Top 10 in 2003) to number three. This category includes SQL injection, NoSQL injection, OS command injection, LDAP injection, and Expression Language injection. An application is vulnerable when user-supplied data is not validated, filtered, or sanitized before being passed to an interpreter.
Despite years of awareness, injection remains devastating. A single SQL injection vulnerability can allow an attacker to dump an entire database, bypass authentication, or even gain operating system access.
Vulnerable Code Example
// INSECURE: String concatenation creates SQL injection vulnerability
String query = "SELECT * FROM users WHERE username = '"
+ request.getParameter("user") + "' AND password = '"
+ request.getParameter("pass") + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(query);
// Attacker input: user = ' OR '1'='1' --
// Resulting query: SELECT * FROM users WHERE username = '' OR '1'='1' --'
Secure Code Example
// SECURE: Parameterized query prevents injection
String query = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement stmt = connection.prepareStatement(query);
stmt.setString(1, request.getParameter("user"));
stmt.setString(2, request.getParameter("pass"));
ResultSet rs = stmt.executeQuery();
// The database engine treats parameters as data, never as executable SQL
How automated scanning detects this: SAST engines perform taint analysis, tracing user input from entry points (HTTP parameters, headers, request bodies) through the application to dangerous sinks (database queries, OS commands, LDAP queries). When untrusted data reaches a sink without passing through a sanitization function, the scanner flags it as an injection vulnerability. DAST tools send crafted payloads to form fields and URL parameters, monitoring responses for error messages or behavior changes indicating successful injection.
A04:2021 — Insecure Design High
This is a new category in 2021, and it represents a fundamental shift in thinking. Insecure design is about flaws in the architecture and design of an application that cannot be fixed by a perfect implementation. No amount of secure coding can fix a flawed design. For example, if a password recovery flow allows unlimited attempts without rate limiting, the design itself is insecure regardless of how well the code is written.
This category emphasizes the need for threat modeling, secure design patterns, and reference architectures. Development teams should integrate security thinking from the earliest stages of the software development lifecycle, not bolt it on afterward.
- Threat modeling — Identify trust boundaries, entry points, and data flows before writing code
- Abuse case development — Write "as an attacker, I want to..." user stories alongside functional stories
- Rate limiting and circuit breakers — Design for hostile input from day one
- Principle of least privilege — Ensure every component has the minimum access it needs
How automated scanning detects this: Design flaws are harder for automated tools to detect, but scanners can identify symptoms. Missing rate limiting on authentication endpoints, lack of CAPTCHA on sensitive forms, overly permissive CORS policies, and missing security headers are all detectable indicators of insecure design patterns.
A05:2021 — Security Misconfiguration High
Security misconfiguration moved up from the sixth position. With the rise of cloud infrastructure, containers, and microservices, the configuration surface area has exploded. This category includes missing security hardening, improperly configured permissions, unnecessary features enabled, default accounts and passwords, overly verbose error handling that exposes stack traces, and missing security headers.
The former category "XML External Entities (XXE)" from 2017 is now merged into this category, as XXE is fundamentally a parser misconfiguration issue.
# INSECURE: Debug mode in production, verbose errors DEBUG = True ALLOWED_HOSTS = ['*'] SECRET_KEY = 'django-insecure-default-key' # Missing security headers # No HSTS, no CSP, no X-Frame-Options
# SECURE: Production-hardened configuration
DEBUG = False
ALLOWED_HOSTS = ['app.example.com']
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
# Security headers enabled
SECURE_HSTS_SECONDS = 31536000
SECURE_CONTENT_TYPE_NOSNIFF = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
X_FRAME_OPTIONS = 'DENY'
How automated scanning detects this: DAST scanners probe for common misconfigurations by checking HTTP response headers, testing for directory listing, probing default admin paths, and verifying TLS configuration. Infrastructure-as-Code (IaC) scanners analyze Terraform, CloudFormation, and Kubernetes manifests for insecure defaults before deployment.
A06:2021 — Vulnerable and Outdated Components High
This category (previously "Using Components with Known Vulnerabilities") addresses the risk of using libraries, frameworks, and other software modules with known security flaws. Modern applications depend on hundreds or even thousands of open-source packages, each of which can contain vulnerabilities.
The Log4Shell vulnerability (CVE-2021-44228) demonstrated the catastrophic impact a single vulnerable dependency can have across the entire software ecosystem. Organizations that lacked visibility into their dependency trees were unable to respond quickly.
- Maintain a Software Bill of Materials (SBOM) for every application
- Continuously monitor dependencies for newly published CVEs
- Remove unused dependencies to minimize attack surface
- Pin dependency versions and review updates before adopting them
How automated scanning detects this: Software Composition Analysis (SCA) tools parse manifest files (package.json, pom.xml, requirements.txt, go.mod) and lockfiles to build a complete dependency graph. They cross-reference every direct and transitive dependency against vulnerability databases (NVD, GitHub Advisory, OSV) and flag packages with known CVEs, assigning severity scores based on CVSS metrics and exploitability.
A07:2021 — Identification and Authentication Failures High
Previously called "Broken Authentication," this expanded category now also covers identification failures. Applications that allow credential stuffing, brute force attacks, weak passwords, broken session management, or improper multi-factor authentication fall into this category.
// INSECURE: No rate limiting, no account lockout, weak session
app.post('/login', (req, res) => {
const user = db.findUser(req.body.email, req.body.password);
if (user) {
// Session ID is sequential and predictable
req.session.userId = user.id;
res.json({ success: true });
}
});
// SECURE: Rate limiting, lockout, strong session management
const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5 });
app.post('/login', limiter, async (req, res) => {
const user = await db.findUserByEmail(req.body.email);
if (!user || !(await bcrypt.compare(req.body.password, user.hash))) {
await db.incrementFailedAttempts(req.body.email);
return res.status(401).json({ error: 'Invalid credentials' });
}
if (user.failedAttempts >= 5) {
return res.status(423).json({ error: 'Account locked' });
}
req.session.regenerate(() => {
req.session.userId = user.id;
res.json({ success: true });
});
});
How automated scanning detects this: DAST tools test login forms for brute force susceptibility, weak password policies, session fixation, and missing multi-factor authentication. SAST tools can identify insecure session management, missing password complexity validation, and hardcoded default credentials in source code.
A08:2021 — Software and Data Integrity Failures High
Another new category in 2021, this covers assumptions made about software updates, critical data, and CI/CD pipelines without verifying integrity. Insecure deserialization (previously its own category in 2017) is now merged here. This also covers situations where applications rely on plugins, libraries, or modules from untrusted sources, content delivery networks, or public repositories without integrity verification.
Supply chain attacks fall squarely into this category. When a build pipeline pulls dependencies without verifying checksums or signatures, an attacker who compromises the package registry can inject malicious code that executes in your production environment.
Key defense: Always verify package integrity using checksums, lock files, and signed releases. Implement Subresource Integrity (SRI) for CDN-hosted scripts. Use code signing for your own build artifacts.
How automated scanning detects this: SCA tools analyze lock files for integrity hash mismatches. SAST tools detect insecure deserialization patterns. CI/CD security scanners audit pipeline configurations for missing verification steps and unsigned artifact deployment.
A09:2021 — Security Logging and Monitoring Failures Medium
This category was elevated from the tenth position in 2017, reflecting the industry's recognition that without adequate logging and monitoring, breaches go undetected for weeks or months. Studies consistently show the average time to detect a breach exceeds 200 days.
Applications should log all authentication events (successful and failed), access control failures, server-side validation failures, and transactions with sufficient context for forensic analysis. These logs must be protected against tampering and integrated with monitoring and alerting systems.
- Log authentication successes and failures with timestamps and source IPs
- Detect and alert on patterns indicating brute force, credential stuffing, or privilege escalation
- Ensure logs are stored in append-only, tamper-resistant storage
- Integrate with a SIEM or log analysis platform that supports anomaly detection
- Never log sensitive data such as passwords, tokens, or personally identifiable information in cleartext
How automated scanning detects this: SAST tools can identify logging gaps by analyzing code paths that handle authentication and authorization events. They flag exception handlers that silently swallow errors without logging. DAST tools check whether failed authentication attempts produce log entries and whether security-relevant events are captured.
A10:2021 — Server-Side Request Forgery (SSRF) High
SSRF is a new entry in the 2021 list, added based on community survey data rather than incidence data alone. SSRF occurs when a web application fetches a remote resource without validating the user-supplied URL. This allows attackers to force the application to send requests to unexpected destinations, even when protected by a firewall, VPN, or network access control list.
The Capital One breach of 2019 famously exploited an SSRF vulnerability to access the AWS metadata service and retrieve temporary credentials, resulting in the exposure of over 100 million customer records.
# INSECURE: User-controlled URL passed directly to HTTP client
@app.route('/fetch')
def fetch_url():
target = request.args.get('url')
response = requests.get(target) # Can access internal services!
return response.text
# Attacker request: /fetch?url=http://169.254.169.254/latest/meta-data/
# SECURE: URL validation with allowlist and private IP blocking
import ipaddress
from urllib.parse import urlparse
ALLOWED_DOMAINS = {'api.trusted-partner.com', 'cdn.example.com'}
@app.route('/fetch')
def fetch_url():
target = request.args.get('url')
parsed = urlparse(target)
# Validate scheme
if parsed.scheme not in ('http', 'https'):
return 'Invalid scheme', 400
# Validate domain against allowlist
if parsed.hostname not in ALLOWED_DOMAINS:
return 'Domain not allowed', 400
# Resolve and validate IP is not private/internal
ip = ipaddress.ip_address(socket.gethostbyname(parsed.hostname))
if ip.is_private or ip.is_loopback or ip.is_link_local:
return 'Internal addresses blocked', 400
response = requests.get(target, timeout=5)
return response.text
How automated scanning detects this: SAST tools trace user input to HTTP client calls (requests.get, fetch, HttpClient) and flag instances where URLs are not validated against allowlists. DAST tools send requests with internal IP addresses, cloud metadata URLs, and DNS rebinding payloads to detect SSRF vulnerabilities in running applications.
OWASP Top 10 2021 Summary Table
| Category | Risk | Key Detection Method |
|---|---|---|
| A01 Broken Access Control | Critical | SAST + DAST |
| A02 Cryptographic Failures | Critical | SAST + Secrets Scanning |
| A03 Injection | Critical | SAST (taint analysis) + DAST |
| A04 Insecure Design | High | Threat Modeling + DAST |
| A05 Security Misconfiguration | High | DAST + IaC Scanning |
| A06 Vulnerable Components | High | SCA + SBOM |
| A07 Auth Failures | High | DAST + SAST |
| A08 Integrity Failures | High | SCA + CI/CD Audit |
| A09 Logging Failures | Medium | SAST + Log Analysis |
| A10 SSRF | High | SAST + DAST |
Protecting Against All 10 Categories
No single tool or practice can address all ten OWASP categories. A comprehensive application security program requires layered defenses:
- Static Application Security Testing (SAST) catches vulnerabilities in source code before deployment, including injection, broken access control patterns, and cryptographic misuse
- Software Composition Analysis (SCA) continuously monitors dependencies for known vulnerabilities and license risks
- Dynamic Application Security Testing (DAST) probes running applications for misconfigurations, authentication weaknesses, and SSRF
- Secrets scanning prevents hardcoded credentials from reaching repositories
- Security logging and monitoring provides visibility into attacks in progress and enables rapid incident response
The challenge for most organizations is not knowing what to do — it is operationalizing these practices at scale across dozens or hundreds of repositories, each with their own technology stacks, deployment pipelines, and development teams. This is where an integrated Application Security Posture Management (ASPM) platform delivers transformative value by correlating findings across all scanner types into a single, prioritized view.
Bottom line: The OWASP Top 10 is the starting point, not the finish line. Treat it as the minimum standard your application security program must address. Then build layered, automated defenses that scale with your development velocity.
Detect OWASP Top 10 Vulnerabilities Automatically
Security Factor 365 combines SAST, SCA, DAST, and secrets scanning to detect all OWASP Top 10 categories across your entire application portfolio.
Explore the Platform