Skip to content
OWASP Top 10 for APIs: The Security Threats Every Developer Must Know 3 min

OWASP Top 10 for APIs: The Security Threats Every Developer Must Know

SD
ScaleDojo
May 11, 2026
3 min read812 words
OWASP Top 10 for APIs: The Security Threats Every Developer Must Know

The Equifax Breach: 147 Million Records from One Unpatched Vulnerability

The OWASP Top 10 for APIs helps developers identify and prevent common API security risks before they can be exploited by attackers.In 2017, Equifax suffered the most devastating data breach in history. Attackers exploited a known Apache Struts vulnerability (CVE-2017-5638) that had a patch available for TWO MONTHS before the breach. Through this single entry point, they accessed names, Social Security numbers, birth dates, and addresses of 147 million Americans. Equifax eventually paid over $700 million in settlements. The breach hit OWASP's top categories: vulnerable components (unpatched library), broken access control (lateral movement after entry), and insufficient logging (the breach went undetected for 76 days).

The OWASP API Security Top 10

OWASP API Security Top 10 (2023):

  Rank  Vulnerability                  Real-World Example           Prevalence
  ----  ---------------------------    -------------------------    ----------
  1     Broken Object-Level AuthZ      IDOR on user profiles        Very High
  2     Broken Authentication          No rate limit on login       High
  3     Broken Object Property-Level   API returns password hash    Very High
  4     Unrestricted Resource Usage     No pagination limits         High
  5     Broken Function-Level AuthZ    User calls admin endpoints   High
  6     Server-Side Request Forgery    SSRF via URL parameter       Medium
  7     Security Misconfiguration      CORS *, debug mode on        Very High
  8     Lack of Protection from Bots   Credential stuffing          High
  9     Improper Asset Management      Old API v1 still running     High
  10    Unsafe API Consumption         Trusting third-party APIs    Medium

1. Broken Object-Level Authorization (BOLA/IDOR)

The #1 API Vulnerability:

  VULNERABLE:
  GET /api/users/42/orders         <- Alice (user 42) sees her orders
  GET /api/users/43/orders         <- Alice changes 42 to 43
                                      Sees Bob's orders! No check!

  The code:
  @app.get('/users/{user_id}/orders')
  def get_orders(user_id: int):
      return db.query(Order).filter(Order.user_id == user_id).all()
      # BUG: never checks if requesting user == user_id

  FIXED:
  @app.get('/users/{user_id}/orders')
  def get_orders(user_id: int, current_user = Depends(get_current_user)):
      if current_user.id != user_id and not current_user.is_admin:
          raise HTTPException(403, 'Access denied')
      return db.query(Order).filter(Order.user_id == user_id).all()

  BETTER (eliminate the vector entirely):
  @app.get('/me/orders')           <- No user_id in URL at all!
  def get_my_orders(current_user = Depends(get_current_user)):
      return db.query(Order).filter(Order.user_id == current_user.id).all()

3. Broken Object Property-Level Access

Excessive Data Exposure:

  VULNERABLE - API returns entire user object:
  GET /api/users/42
  {
    "id": 42,
    "name": "Alice",
    "email": "alice@example.com",
    "password_hash": "$2b$12$LJ3m4...",    <- EXPOSED!
    "ssn": "123-45-6789",                  <- EXPOSED!
    "internal_notes": "VIP customer",       <- EXPOSED!
    "created_at": "2024-01-15"
  }

  Frontend just shows name and email, but attacker reads raw response.

  FIXED - Explicit response schema:
  class UserResponse(BaseModel):
      id: int
      name: str
      email: str
      created_at: datetime
      # Only these fields. Nothing else leaves the server.

  Mass Assignment (the flip side):
  VULNERABLE:
  PUT /api/users/42  { "name": "Alice", "role": "admin" }
  user.update(**request.json())     <- User just made themselves admin!

  FIXED:
  class UserUpdate(BaseModel):
      name: str
      email: str
      # role is NOT in this schema - cannot be set by users

4. Injection Attacks

SQL Injection (still the most exploited vulnerability):

  VULNERABLE:
  query = f"SELECT * FROM users WHERE name = '{user_input}'"

  Attacker input: ' OR '1'='1'; DROP TABLE users; --
  Resulting query: SELECT * FROM users WHERE name = '' OR '1'='1'; DROP TABLE users; --'

  FIXED (parameterized queries):
  cursor.execute('SELECT * FROM users WHERE name = %s', (user_input,))
  # Database treats user_input as DATA, never as SQL code.
  # Even if input is "DROP TABLE", it searches for that literal string.

  NoSQL Injection:
  VULNERABLE:
  db.users.find({ "username": req.body.username, "password": req.body.password })
  Attacker sends: { "password": { "$ne": "" } }  <- Matches any non-empty password!

  FIXED:
  Validate types before querying. password must be a string, not an object.

  Command Injection:
  VULNERABLE:
  os.system(f'convert {user_filename} output.png')  <- user_filename = '; rm -rf /'
  FIXED:
  subprocess.run(['convert', user_filename, 'output.png'])  <- List form, no shell

Security Hardening Checklist

  • BOLA: always verify resource ownership, prefer /me/ endpoints over /users/{id}/ for self-access.

  • Authentication: rate limit login to 5 attempts/minute, implement account lockout, use bcrypt with cost 12+.

  • Data exposure: use explicit response schemas (whitelist fields), never return raw database objects.

  • Injection: parameterized queries everywhere, validate input types, use ORM methods (not raw SQL).

  • Mass assignment: explicit update schemas, never apply raw request body to database models.

  • SSRF: validate and whitelist URLs, block internal IP ranges (169.254.x.x, 10.x.x.x, 127.0.0.1).

  • CORS: never use Access-Control-Allow-Origin: * on authenticated endpoints. Whitelist specific origins.

  • Dependencies: automated vulnerability scanning (Dependabot, Snyk), update within 48 hours of critical CVEs.

  • Logging: log all auth failures, privilege escalation attempts, and unusual patterns. Alert on anomalies.

Interview Tip

When security comes up in system design interviews, mention OWASP proactively - it shows you think about security from the start. Lead with BOLA (the #1 API vulnerability) and explain the /me/ endpoint pattern that eliminates IDOR vectors entirely. For any database interaction, mention parameterized queries. When designing APIs, show explicit request/response schemas that whitelist allowed fields (prevents both data exposure and mass assignment). The advanced move: mention defense in depth - even if application code has a bug, database-level protections (RLS, parameterized queries) prevent exploitation.

Key Takeaway

BOLA (broken authorization) is the #1 API vulnerability - always verify resource ownership, not just authentication. Never trust client input, never expose unnecessary data, always use parameterized queries, and rate limit everything. Security is not a feature to add later - it is a requirement from day one.

Enjoyed this article?

Share it with your network to help others level up their system design skills.

Discussion0

Join the Discussion

Sign in to leave comments, reply to others, or like insights.

Sign In to ScaleDojo

No comments yet. Be the first to start the thread!

Related Articles

Enjoyed this content?

Your support keeps us creating free resources

We put a lot of hours into researching and writing these guides. If it helped you, consider buying us a coffee. Every bit goes toward keeping ScaleDojo's content free and growing.

$

One-time payment via Stripe. ScaleDojo account required.

ScaleDojo Logo
Initializing ScaleDojo