OWASP Top 10 (2025): Every Vulnerability Explained
A technical breakdown of all 10 OWASP vulnerability categories with real attack scenarios, code examples, and specific fixes. The reference guide for web application security.
What Is the OWASP Top 10?
The OWASP Top 10 is a consensus list of the most critical security risks for web applications, published by the Open Worldwide Application Security Project. The 2025 edition is the latest version, building on the 2021 framework with updated exploitation data. It is based on data from over 500,000 applications and hundreds of thousands of real vulnerabilities. It is not a checklist — it is a prioritization framework. If you fix nothing else, fix these ten categories first.
The 2021 edition introduced three new categories (Insecure Design, Software and Data Integrity Failures, SSRF) and reshuffled priorities based on actual exploitation data. The 2025 update further adjusted rankings — Injection moved from #3 to #5 as frameworks improve, but it still causes massive damage with 14,000+ CVEs. Broken Access Control remains at #1. Injection dropped from #1 in earlier editions because frameworks now parameterize queries by default — but it still causes massive damage when developers bypass the ORM.
Each category below includes: what it is, how it gets exploited, a real-world example, and how to fix it.
A01: Broken Access Control
Moved from #5 to #1 in 2021. 94% of applications tested had some form of broken access control. This category covers any situation where users can act outside their intended permissions. The most common form: Insecure Direct Object References (IDOR). A user changes /api/orders/1234 to /api/orders/1235and sees another user's order.
Real example:
// Vulnerable: no authorization check
app.get('/api/orders/:id', async (req, res) => {
const order = await db.orders.findById(req.params.id);
res.json(order); // Anyone can access any order
});
// Fixed: verify ownership
app.get('/api/orders/:id', async (req, res) => {
const order = await db.orders.findById(req.params.id);
if (order.userId !== req.user.id) {
return res.status(403).json({ error: 'Forbidden' });
}
res.json(order);
});Other forms of broken access control:
- Bypassing access controls by modifying URLs, HTML, or API requests
- Elevation of privilege — acting as admin without being logged in as admin
- Metadata manipulation — tampering with JWT tokens or hidden form fields
- CORS misconfiguration allowing unauthorized cross-origin API access
- Force-browsing to authenticated pages as an unauthenticated user
- Missing function-level access control — admin endpoints accessible to regular users
How to test:Log in as a regular user and try accessing admin URLs. Change ID parameters in API requests. Test horizontal access — can user A see user B's data? Test vertical access — can a regular user perform admin actions?
Fix: Deny by default. Every endpoint checks authorization server-side on every request. Never rely on hiding URLs or buttons in the UI. Use role-based or attribute-based access control. Log access control failures and alert on repeated attempts. See the OWASP A01 reference and the OWASP Authorization Testing Guide.
A02: Cryptographic Failures
Previously “Sensitive Data Exposure.” The rename shifts focus from the symptom (data exposure) to the cause (bad cryptography). This is about data that needs encryption but does not get it, or gets it wrong. Passwords in plaintext. Credit cards in the database without encryption. Session tokens over HTTP. MD5 or SHA-1 for password hashing.
Real example:
# Vulnerable: MD5 password hash (cracked in seconds with rainbow tables)
import hashlib
password_hash = hashlib.md5(password.encode()).hexdigest()
# Fixed: bcrypt with salt rounds (computationally expensive to crack)
import bcrypt
password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt(12))Checklist:
- Classify data by sensitivity. PII, financial data, and health records need encryption at rest and in transit
- Use TLS 1.2+ for all data in transit (see our TLS guide)
- Hash passwords with bcrypt, scrypt, or Argon2id — never MD5, SHA-1, or plain SHA-256
- Use authenticated encryption (AES-256-GCM, ChaCha20-Poly1305) for data at rest
- Do not hardcode encryption keys. Use a key management service (AWS KMS, HashiCorp Vault)
- Disable caching for responses containing sensitive data (
Cache-Control: no-store) - Ensure old/deprecated protocols and algorithms are disabled (SSLv3, TLS 1.0, RC4, DES)
Reference: OWASP A02.
A03: Injection
Injection happens when untrusted data is sent to an interpreter as part of a command or query. SQL injection is the classic form, but this category also covers NoSQL injection, OS command injection, LDAP injection, and Cross-Site Scripting (XSS) — which was merged into this category in the 2021 edition. Dropped from #1 to #3 because frameworks now parameterize queries by default, but it still causes massive damage when developers bypass the ORM or build dynamic queries.
SQL injection example:
// Vulnerable: string concatenation in query
const query = "SELECT * FROM users WHERE email = '" + email + "'";
// Attacker input: ' OR '1'='1' --
// Result: SELECT * FROM users WHERE email = '' OR '1'='1' --'
// Returns ALL users from the database
// Fixed: parameterized query
const query = "SELECT * FROM users WHERE email = $1";
const result = await db.query(query, [email]);OS command injection example:
# Vulnerable: shell command with user input
import os
os.system(f"ping {user_input}")
# Attacker input: 127.0.0.1; cat /etc/passwd
# Executes: ping 127.0.0.1; cat /etc/passwd
# Fixed: use subprocess with argument list (no shell interpretation)
import subprocess
subprocess.run(["ping", user_input], shell=False)Prevention rules:
- Use parameterized queries / prepared statements for all database operations
- Use ORM frameworks that parameterize by default (Prisma, SQLAlchemy, Eloquent)
- Validate and sanitize all user input server-side — whitelist allowed characters, do not blacklist
- For XSS: encode output based on context (HTML, JavaScript, URL, CSS). See our XSS deep-dive
- Use
LIMITon queries to prevent mass data disclosure if injection occurs - Never pass user input to
eval(),exec(),system(), or shell commands
Reference: OWASP A03 and the OWASP SQL Injection Prevention Cheat Sheet.
A04: Insecure Design
New in 2021. This is about flaws in the design itself, not the implementation. A perfectly coded feature can be insecure if the design is wrong. A password reset that sends a 4-digit code via SMS: even with rate limiting, 10,000 combinations is brute-forceable with distributed requests. The design is insecure regardless of how well you write the code.
Real-world patterns:
- Missing rate limiting on business-critical flows— Account creation, password reset, coupon redemption. An e-commerce site allows unlimited discount code attempts. An attacker brute-forces valid codes and shares them publicly.
- Trusting client-side validation— Price calculations done in JavaScript, quantity limits enforced only in the UI. An attacker modifies the request body directly and the server accepts it because it never validates.
- Overly generous APIs— An endpoint returns entire user objects including hashed passwords, internal IDs, or admin flags. The frontend just hides fields it does not need. An attacker calls the API directly and gets everything.
- No abuse cases in requirements— The spec says “user can upload a profile picture” but nobody considered: what if they upload 10 GB? What if the file is an executable? What if they upload 10,000 times per minute?
Fix:Threat model during design, not after implementation. Use abuse cases alongside user stories. Ask “what happens if an attacker does X?” for every feature. Establish secure design patterns as reusable components: authentication flows, input validation libraries, authorization middleware. Reference: OWASP A04.
A05: Security Misconfiguration
The most common category by volume. The server is running, the code is secure, but the configuration is wrong. This is what automated scanners catch most often.
Examples:
- Default credentials left on admin panels, databases, or cloud services
- Unnecessary features enabled: directory listing, debug pages, sample applications
- Missing security headers (see our HTTP Security Headers guide)
- Overly permissive cloud storage — public S3 buckets, open Firebase rules
- Verbose error messages exposing stack traces, SQL queries, or file paths to users
- A Django app deployed with
DEBUG=True— every error shows the full stack trace and environment variables to anyone who triggers a 500 - Exposed
.env,.git,phpinfo.php, orwp-adminendpoints - Permissive CORS that reflects any origin with credentials
How to test: Check for default admin credentials. Try accessing /server-status, /phpinfo.php, /.env, /.git/config. Verify error pages do not leak technical details. Check all security headers with our scanner.
Fix: Hardened, repeatable deployment process (Infrastructure as Code). Remove all default accounts and unnecessary features. Separate development, staging, and production configs with different credentials. Return generic error messages to users; log details server-side only. Automated scanning in CI/CD to detect misconfigurations before they reach production. Reference: OWASP A05 and the OWASP Configuration Testing Guide.
A06: Vulnerable and Outdated Components
Your application is only as secure as its weakest dependency. A single vulnerable npm package, Python library, or Docker base image can compromise everything. The 2021 Log4Shell vulnerability (CVE-2021-44228) demonstrated this at global scale: one logging library in Java, millions of affected applications, trivial remote code execution with a single crafted string.
The 2017 Equifax breach — 147 million records exposed — was caused by an unpatched Apache Struts vulnerability. The fix had been available for two months before the breach.
You are vulnerable if:
- You do not know the versions of all components you use (client and server side)
- The software is unsupported or out of date (OS, web server, DBMS, libraries)
- You do not scan for vulnerabilities regularly
- You do not fix or upgrade dependencies in a timely fashion
- You do not test compatibility of updated libraries before deploying
Fix:
- Maintain a Software Bill of Materials (SBOM) — know what you ship
- Run
npm audit,pip-audit,cargo audit, or enable Dependabot / Renovate - Monitor NVD and OSV for CVEs affecting your stack
- Pin dependency versions and update deliberately, not blindly
- Remove unused dependencies — every dependency is attack surface
- Use lock files and verify checksums in CI
Reference: OWASP A06.
A07: Identification and Authentication Failures
Previously “Broken Authentication.” Dropped from #2 to #7 because frameworks handle authentication better now, but implementation mistakes persist. If an attacker can log in as someone else, everything else is irrelevant.
Common failures:
- Permitting brute-force attacks — no lockout, no rate limiting, no CAPTCHA after failures
- Allowing weak passwords (“password123”, “admin”, “123456”)
- Using knowledge-based recovery (“What was your first pet?”) — guessable from social media
- Storing passwords in plaintext or with weak hashes (MD5, SHA-1)
- Missing or broken multi-factor authentication
- Exposing session IDs in URLs (bookmarkable, logged, shared)
- Not rotating session IDs after login (session fixation attack)
- Not invalidating sessions on logout or after a password change
Real example:A site allows passwords like “123456” and has no rate limiting on the login endpoint. An attacker runs a credential stuffing attack using leaked password databases (billions of credentials are publicly available) and compromises thousands of accounts overnight.
Fix:
- Multi-factor authentication for all accounts, not just admins
- Check passwords against breached password lists (Have I Been Pwned API)
- Rate limit login attempts: 5 failures per 15 minutes, then lockout or CAPTCHA
- Rotate session IDs after login. Set
HttpOnly,Secure,SameSitecookie flags - Use established authentication libraries or services — do not build your own auth system
- Invalidate all sessions when a user changes their password
Reference: OWASP A07.
A08: Software and Data Integrity Failures
New in 2021. This covers code and infrastructure that does not protect against integrity violations. The most notorious example: the 2020 SolarWinds supply chain attack, where attackers compromised the build pipeline and injected malware into legitimate software updates distributed to 18,000 organizations including US government agencies.
Patterns that lead to integrity failures:
- Insecure CI/CD pipelines— Build processes that pull dependencies without checksum verification. An attacker compromises a package registry and distributes malicious updates.
- Auto-update without verification— Applications that download and execute updates without verifying digital signatures.
- Insecure deserialization— Accepting serialized objects from untrusted sources. In Java, deserializing untrusted data can lead to remote code execution via gadget chains. In Python,
pickle.loads()on untrusted data is arbitrary code execution. - CDN script loading without Subresource Integrity— Loading JavaScript from a CDN without SRI hashes. If the CDN is compromised, your users run attacker code.
<!-- Without SRI: CDN compromise = game over -->
<script src="https://cdn.example.com/lib.js"></script>
<!-- With SRI: browser rejects modified files -->
<script src="https://cdn.example.com/lib.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxAh..."
crossorigin="anonymous"></script>Fix: Verify digital signatures on all software updates. Use SRI hashes for CDN resources. Use lock files (package-lock.json, poetry.lock) and verify checksums. Review CI/CD pipeline access controls — treat build infrastructure as production. Never deserialize untrusted data. Reference: OWASP A08.
A09: Security Logging and Monitoring Failures
You cannot detect breaches you do not log. The average time to detect a breach is 197 days (IBM Cost of a Data Breach Report). Most breaches are discovered by external parties — not by the victim's own monitoring. Without logging, attackers operate freely for months.
What to log:
- All login attempts (successful and failed), with IP address, user agent, and timestamp
- Access control failures (403 responses, unauthorized access attempts)
- Input validation failures (potential injection attempts)
- Server-side errors (5xx responses with context)
- Administrative actions (user creation, permission changes, config updates)
- High-value transactions (payments, data exports, password changes)
What NOT to log:
- Passwords (even hashed ones)
- Session tokens or API keys
- Full credit card numbers or SSNs
- Any data that would create a second breach if the logs are compromised
Fix:Centralize logs (ELK stack, Datadog, Splunk, Grafana Loki). Set up alerts for suspicious patterns: spike in 403s, login attempts from unusual geolocations, admin actions outside business hours, data exports exceeding normal volume. Store logs securely and separately — an attacker who compromises your server should not be able to delete the logs. Test your alerting: run a simulated attack and verify you get notified. Reference: OWASP A09.
A10: Server-Side Request Forgery (SSRF)
New in 2021. SSRF happens when a web application fetches a remote resource using a user-supplied URL without validation. The attacker makes the server send requests to internal services that are not reachable from the public internet.
Attack scenario:
# Normal request: fetch a URL for preview
POST /api/preview
{ "url": "https://example.com/page" }
# SSRF attack: access AWS metadata service
POST /api/preview
{ "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/" }
# SSRF attack: scan internal network
POST /api/preview
{ "url": "http://192.168.1.1:3306/" }
# SSRF attack: read local files
POST /api/preview
{ "url": "file:///etc/passwd" }On AWS, the metadata endpoint at 169.254.169.254returns IAM credentials. This is how the 2019 Capital One breach happened — SSRF through a misconfigured WAF gave the attacker access to IAM temporary credentials, which gave access to S3 buckets containing 100 million customer records.
Fix:
- Validate and sanitize all user-supplied URLs on the server side
- Use an allowlist of permitted domains and protocols (HTTPS only, block
file://,gopher://,ftp://) - Block private IP ranges:
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,127.0.0.0/8 - On AWS: use IMDSv2 which requires a PUT request with a token header (not exploitable via simple SSRF)
- Do not return raw responses from fetched URLs — parse and sanitize before returning
- Use network segmentation so the application server cannot reach sensitive internal services directly
Reference: OWASP A10.
Scan for Vulnerabilities
Our scanner checks for exposed admin panels, sensitive files (.env, .git, phpinfo), missing security headers, outdated TLS, open ports, technology fingerprinting, CVE lookups, and other misconfigurations that map directly to the OWASP Top 10. Free scan, 30 seconds, no account needed.
Automated scanning catches misconfigurations and known vulnerabilities (A02, A05, A06). For business logic flaws (A01, A04, A07), you need manual testing or penetration testing. Start with automation, then invest in manual review for your most critical features.
Check your website right now
110 security checks in 60 seconds. Free, no signup required.
Scan My Website (Free)ismycodesafe.com Security Team
We run automated security scans on thousands of websites daily, combining static analysis, SSL/TLS inspection, header auditing, and CVE lookups. Our team tracks OWASP, NIST, and evolving compliance requirements (GDPR, NIS2, PCI DSS) to keep these guides accurate and practical.
Related Articles
Cross-Site Scripting (XSS): How Attackers Steal User Data
Reflected, stored, and DOM-based XSS with attack examples and prevention techniques.
SQL Injection Is Still the #1 Database Threat
How SQLi works and parameterized query examples for every major language.
The Complete Guide to Web Security in 2026
TLS, security headers, CORS, cookies. The defensive layer that prevents many OWASP Top 10 attacks.