OWASP Top 10: The Most Critical Web Application Security Risks Explained

OWASP Top 10: The Most Critical Web Application Security Risks Explained

Why Every Developer Should Know the OWASP Top 10

The OWASP Top 10 is the most widely referenced standard for web application security risks. Published by the Open Web Application Security Project, it represents a broad consensus about the most critical security threats to web applications. If you build software that touches the internet, understanding these vulnerabilities isn't optional—it's essential.

The current list (2021, with 2025 update in progress) reflects a shift toward design flaws and architectural weaknesses rather than just implementation bugs.

The OWASP Top 10 (2021)

#RiskDescriptionMoved From
A01Broken Access ControlUsers act outside intended permissions#5 → #1
A02Cryptographic FailuresSensitive data exposure through weak crypto#3 → #2
A03InjectionSQL, NoSQL, OS, LDAP injection#1 → #3
A04Insecure DesignMissing security architecture and design patternsNew
A05Security MisconfigurationDefault configs, open cloud storage, verbose errors#6 → #5
A06Vulnerable ComponentsUsing components with known vulnerabilities#9 → #6
A07Auth FailuresBroken authentication and session management#2 → #7
A08Software & Data IntegrityCode and infrastructure without integrity verificationNew
A09Security Logging FailuresInsufficient logging and monitoring#10 → #9
A10SSRFServer-Side Request ForgeryNew

A01: Broken Access Control

The #1 risk. 94% of applications tested had some form of broken access control.

Vulnerable code:

# INSECURE: No authorization check
@app.route('/api/users/<user_id>/profile')
def get_profile(user_id):
    # Any authenticated user can access ANY user's profile
    user = User.query.get(user_id)
    return jsonify(user.to_dict())

# INSECURE: Client-side role check only
@app.route('/api/admin/users')
def admin_users():
    # Only checking role in frontend JavaScript, not backend
    return jsonify([u.to_dict() for u in User.query.all()])

Fixed code:

# SECURE: Proper authorization
@app.route('/api/users/<user_id>/profile')
@login_required
def get_profile(user_id):
    # Users can only access their own profile
    if str(current_user.id) != user_id and not current_user.is_admin:
        abort(403)
    user = User.query.get_or_404(user_id)
    return jsonify(user.to_dict())

# SECURE: Server-side role verification
@app.route('/api/admin/users')
@login_required
@require_role('admin')
def admin_users():
    return jsonify([u.to_dict() for u in User.query.all()])

Common patterns:

  • IDOR (Insecure Direct Object Reference): Changing /api/orders/123 to /api/orders/124
  • Privilege escalation: Regular user accessing admin endpoints
  • Missing function-level access control
  • CORS misconfiguration allowing unauthorized origins

A02: Cryptographic Failures

Vulnerable patterns:

# INSECURE: MD5 for password hashing
import hashlib
password_hash = hashlib.md5(password.encode()).hexdigest()

# INSECURE: Hardcoded secret key
SECRET_KEY = "my-super-secret-key-123"

# INSECURE: HTTP for sensitive data
API_URL = "http://api.example.com/payments"

Fixed code:

# SECURE: bcrypt with salt
from bcrypt import hashpw, gensalt, checkpw
password_hash = hashpw(password.encode(), gensalt(rounds=12))

# SECURE: Environment variable for secrets
import os
SECRET_KEY = os.environ["SECRET_KEY"]

# SECURE: HTTPS enforced
API_URL = "https://api.example.com/payments"

A03: Injection

Injection dropped from #1 to #3, but remains dangerous. Modern frameworks help, but raw queries still appear in production code.

SQL Injection example:

# INSECURE: String concatenation in SQL
@app.route('/api/search')
def search():
    query = request.args.get('q')
    # Attacker sends: q='; DROP TABLE users; --
    results = db.execute(
        f"SELECT * FROM products WHERE name LIKE '%{query}%'"
    )
    return jsonify(results)

# SECURE: Parameterized query
@app.route('/api/search')
def search():
    query = request.args.get('q', '')
    results = db.execute(
        "SELECT * FROM products WHERE name LIKE :query",
        {"query": f"%{query}%"}
    )
    return jsonify(results)

NoSQL Injection (MongoDB):

// INSECURE: Direct user input in query
app.post('/api/login', (req, res) => {
  // Attacker sends: {"username": {"$gt": ""}, "password": {"$gt": ""}}
  User.findOne({
    username: req.body.username,
    password: req.body.password
  });
});

// SECURE: Input validation + sanitization
app.post('/api/login', (req, res) => {
  const username = String(req.body.username);
  const password = String(req.body.password);
  User.findOne({ username })
    .then(user => bcrypt.compare(password, user.passwordHash));
});

A04: Insecure Design

New in 2021. This isn't about implementation bugs—it's about missing threat modeling and security patterns at the design phase.

Example: An e-commerce site allows unlimited password reset attempts with a 4-digit code. Even with rate limiting per IP, an attacker with a botnet can brute-force any account.

Secure design patterns:

  • Threat modeling during architecture phase (STRIDE, PASTA)
  • Secure design patterns library (reference architectures)
  • Paved road: Make the secure way the easy way for developers
  • Abuse case testing alongside functional testing

A05: Security Misconfiguration

Common Misconfigurations:

☐ Default credentials on admin panels (admin/admin)
☐ Unnecessary HTTP methods enabled (PUT, DELETE, TRACE)
☐ Directory listing enabled on web server
☐ Stack traces exposed in error responses
☐ Cloud storage buckets publicly accessible (S3, GCS)
☐ CORS set to Access-Control-Allow-Origin: *
☐ Debug mode enabled in production
☐ Default security headers missing

Security headers every application should set:

# Nginx security headers
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "0" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

A06: Vulnerable and Outdated Components

78% of codebases contain at least one vulnerability in third-party dependencies. Automated scanning is essential:

# npm audit — check Node.js dependencies
npm audit
npm audit fix

# pip-audit — check Python dependencies
pip install pip-audit
pip-audit

# Snyk — multi-language vulnerability scanner
snyk test
snyk monitor  # continuous monitoring

# GitHub Dependabot — automatic PRs for vulnerable dependencies
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"

A07-A10: Remaining Risks

A07 - Authentication Failures: Weak passwords, missing MFA, credential stuffing. Use established libraries (Passport.js, Django auth, NextAuth) rather than custom implementations.

A08 - Software & Data Integrity: Unverified CI/CD pipelines, unsigned updates, deserialization attacks. Always verify integrity of code and data through signatures and checksums.

A09 - Security Logging Failures: If you can't detect a breach, you can't respond. Log authentication events, access control failures, input validation failures, and application errors.

A10 - SSRF (Server-Side Request Forgery): The server makes requests on behalf of an attacker. Cloud metadata endpoints (169.254.169.254) are prime targets. Validate and sanitize all URLs, use allowlists.

Integrating OWASP into Your Development Process

Secure Development Lifecycle (SDL):

 Requirements → Threat Modeling (A04: Insecure Design)
      │
   Design → Security Architecture Review
      │
 Development → SAST scanning, secure coding standards
      │
  Testing → DAST scanning, penetration testing
      │
 Deployment → Security hardening, dependency scanning
      │
 Operations → WAF, monitoring, incident response
      │
   Review → Lessons learned, update threat model

The OWASP Top 10 isn't a checklist to complete—it's a framework for thinking about application security. Organizations that internalize these risks into their development culture build more resilient software.

Sources: OWASP Top 10 (2021), OWASP Testing Guide, OWASP Cheat Sheet Series