A01 Broken Access Control
The #1 category since 2021. Access control enforces that users can only perform actions and access data they are authorised to. When it breaks, users can access other users' data, perform admin actions, or bypass restrictions entirely.
The most common form: Insecure Direct Object Reference (IDOR) β a user changes a numeric ID in a URL and gets someone else's data.
# GET /api/invoices/4821 β attacker changes 4821 to 4822 @app.get("/api/invoices/{invoice_id}") def get_invoice(invoice_id: int, current_user=Depends(get_current_user)): invoice = db.query(Invoice).filter(Invoice.id == invoice_id).first() return invoice # returns any invoice regardless of owner! # Fixed β verify ownership before returning @app.get("/api/invoices/{invoice_id}") def get_invoice(invoice_id: int, current_user=Depends(get_current_user)): invoice = db.query(Invoice).filter( Invoice.id == invoice_id, Invoice.owner_id == current_user.id # ownership check ).first() if not invoice: raise HTTPException(status_code=404) return invoice
A02 Cryptographic Failures
Sensitive data (passwords, credit cards, health records, PII) transmitted or stored without proper encryption, or encrypted with weak/deprecated algorithms. Common examples: MD5 or SHA-1 for password hashing, HTTP instead of HTTPS, weak TLS configuration, hardcoded encryption keys.
import hashlib from passlib.context import CryptContext # VULNERABLE: MD5 is not a password hashing algorithm hashed = hashlib.md5(password.encode()).hexdigest() # VULNERABLE: SHA-256 without salt is also broken for passwords hashed = hashlib.sha256(password.encode()).hexdigest() # SECURE: bcrypt or argon2 with automatic salting pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") hashed = pwd_context.hash(password) is_valid = pwd_context.verify(plain_password, hashed)
A03 Injection
Untrusted data sent to an interpreter as part of a command or query. The most famous is SQL injection, but this category includes OS command injection, LDAP injection, NoSQL injection, and more. The fix is always the same: use parameterised queries or prepared statements β never build queries by string concatenation.
# VULNERABLE: string concatenation username = request.form["username"] query = f"SELECT * FROM users WHERE username = '{username}'" # Attack: username = "' OR '1'='1" β dumps entire users table # SECURE: parameterised query query = "SELECT * FROM users WHERE username = ?" cursor.execute(query, (username,))
A04 Insecure Design
Design-level flaws that cannot be fixed by good implementation β the architecture itself is unsafe. Examples: no rate limiting on a password reset endpoint (unlimited brute-force attempts allowed by design), recovering passwords by emailing them in plaintext, or a payment flow that trusts client-side price calculation.
The distinction from implementation bugs: A SQL injection is a coding mistake. An insecure design is when the design never considered the threat β no amount of careful coding can fix it. The solution requires rethinking the feature, not patching the code.
A05 Security Misconfiguration
The broadest category. Default credentials left unchanged, debug mode enabled in production, verbose error messages exposing stack traces, cloud storage buckets set to public, unnecessary features/ports left open, missing security headers.
# Check for missing security headers $ curl -I https://yourapp.com | grep -iE "x-frame|x-xss|content-security|strict-transport" # Check S3 bucket public access $ aws s3api get-bucket-acl --bucket your-bucket-name # Check for default Django DEBUG=True in production $ grep -rn "DEBUG = True" --include="settings*.py" . # Check for exposed .git directory on web server $ curl -s https://yourapp.com/.git/HEAD
A06 Vulnerable and Outdated Components
Using libraries, frameworks, or operating system components with known vulnerabilities. This is the SCA problem β your code may be secure, but a dependency you pulled in three years ago has had five CVEs published since then.
# npm projects $ npm audit --audit-level=high # Python projects $ pip install safety && safety check # Any language (using Grype) $ grype dir:. --fail-on high # Docker images $ trivy image --severity HIGH,CRITICAL myapp:latest
A07 Identification and Authentication Failures
Broken login mechanisms: no brute-force protection, weak password policies, session tokens that don't expire, session IDs in URLs, or insecure "remember me" implementations. Also covers JWT vulnerabilities (see our article on algorithm confusion attacks).
- Implement account lockout or exponential backoff after N failed login attempts.
- Use short-lived session tokens and invalidate them server-side on logout.
- Always use HTTPS for session cookie transmission; set
Secure,HttpOnly, andSameSite=Strictcookie flags. - Implement MFA for accounts with access to sensitive data.
A08 Software and Data Integrity Failures
Code and infrastructure that doesn't verify the integrity of software updates, critical data, and CI/CD pipelines. Includes insecure deserialization (where deserializing attacker-controlled data can execute arbitrary code) and using unsigned or unverified packages.
import pickle, base64 # VULNERABLE: deserializing user-controlled data data = pickle.loads(base64.b64decode(request.cookies["session"])) # Attacker crafts a pickle payload that runs os.system("rm -rf /") # SECURE: use a signed, structured format instead from itsdangerous import URLSafeTimedSerializer s = URLSafeTimedSerializer(app.secret_key) data = s.loads(request.cookies["session"], max_age=3600)
A09 Security Logging and Monitoring Failures
Without adequate logging, security incidents go undetected. This category covers failing to log authentication events, not alerting on suspicious activity, storing logs where attackers can modify them, and logging sensitive data (passwords, tokens) in cleartext.
- Log all authentication events (success and failure) with timestamps, IP addresses, and user identifiers.
- Log all access to sensitive data and all administrative actions.
- Alert on anomalous patterns: too many failed logins, accessing hundreds of records in minutes, logins from new geographies.
- Never log passwords, session tokens, or API keys β log the fact of the event, not the credential.
A10 Server-Side Request Forgery (SSRF)
SSRF occurs when a server fetches a URL supplied by the user without validating whether that URL is safe. The most dangerous target is the cloud instance metadata endpoint at 169.254.169.254, which exposes IAM credentials in AWS, GCP, and Azure environments.
import ipaddress, socket from urllib.parse import urlparse BLOCKED = [ ipaddress.ip_network("169.254.0.0/16"), # AWS IMDS ipaddress.ip_network("10.0.0.0/8"), ipaddress.ip_network("172.16.0.0/12"), ipaddress.ip_network("192.168.0.0/16"), ipaddress.ip_network("127.0.0.0/8"), ] def safe_fetch(url: str): parsed = urlparse(url) if parsed.scheme not in ("http", "https"): raise ValueError("Only http/https allowed") ip = ipaddress.ip_address(socket.gethostbyname(parsed.hostname)) if any(ip in net for net in BLOCKED): raise ValueError("Private/internal addresses not allowed") return requests.get(url, timeout=5)
For more on SSRF in cloud environments: see our deep-dive on SSRF to AWS Credential Theft via IMDSv1 β covering the complete exploit chain and IMDSv2 limitations.
Automatically detect OWASP Top 10 vulnerabilities in your code
AquilaX SAST identifies all OWASP Top 10 categories across 30+ languages β with taint-flow analysis, low false-positive rates, and fix suggestions in every report.
Try SAST scanning β