Why APIs Need Their Own Top 10
The web application OWASP Top 10 was written with server-rendered applications in mind. APIs have a fundamentally different attack surface: they expose raw data operations, their clients are diverse and often automated, they carry business-critical operations without traditional UI guardrails, and their authentication models differ significantly from session-cookie-based web apps.
OWASP published the API Security Top 10 (2019, updated 2023) to address this. The risks are distinct enough that "just apply the web top 10" isn't sufficient. Here's each one explained with concrete examples.
API1: Broken Object Level Authorization (BOLA / IDOR)
BOLA is the most common API vulnerability by a significant margin. It occurs when an API endpoint uses an object ID from the request without verifying that the authenticated user is authorised to access that specific object. This is also known as IDOR (Insecure Direct Object Reference).
# Vulnerable β no check that order belongs to current user @app.get("/api/orders/{order_id}") def get_order(order_id: int, current_user = Depends(get_current_user)): order = db.query(Order).filter(Order.id == order_id).first() return order # returns any order, not just the user's # Fixed β filter by BOTH order_id AND user_id @app.get("/api/orders/{order_id}") def get_order(order_id: int, current_user = Depends(get_current_user)): order = db.query(Order).filter( Order.id == order_id, Order.user_id == current_user.id ).first() if not order: raise HTTPException(status_code=404) return order
An attacker logged in as user 1001 requests GET /api/orders/5000. Without the user check, they get order 5000 regardless of who it belongs to. With sequential or guessable IDs, an attacker can enumerate all orders in the system.
API2: Broken Authentication
API authentication failures include weak JWT implementations, no token expiry, tokens not validated on every request, and API keys sent in URL parameters (where they appear in server logs). The API-specific angle: mobile apps and SPAs frequently store tokens in localStorage or in the app bundle β both are higher-risk than HttpOnly cookies for web contexts.
JWT algorithm confusion: JWTs specify the signing algorithm in the header. A classic attack is changing "alg": "RS256" to "alg": "none" β if the server accepts this, the signature isn't verified. Also watch for alg: "HS256" with a weak or empty secret.
API3: Broken Object Property Level Authorization (Mass Assignment)
Mass assignment occurs when an API accepts more fields than it should from client input, allowing attackers to set properties that should only be set server-side β like role, is_admin, account_balance, or subscription_tier.
# Vulnerable β updates all fields from request body including privileged ones @app.put("/api/users/me") def update_profile(body: dict, current_user = Depends(get_current_user)): for key, value in body.items(): setattr(current_user, key, value) # sets role=admin if attacker sends it db.commit() # Fixed β explicit allowlist of updatable fields class UserUpdateSchema(BaseModel): display_name: str | None = None bio: str | None = None # role, is_admin, subscription_tier NOT in schema @app.put("/api/users/me") def update_profile(body: UserUpdateSchema, current_user = Depends(get_current_user)): update_data = body.model_dump(exclude_unset=True) for key, value in update_data.items(): setattr(current_user, key, value) db.commit()
API4: Unrestricted Resource Consumption
APIs without rate limiting and resource controls are vulnerable to DoS through resource exhaustion β expensive queries, large payload uploads, unbounded pagination, or simply flooding with requests. Also includes lack of limits on bulk operations.
# Vulnerable β attacker requests page_size=1000000 @app.get("/api/products") def list_products(page_size: int = 20): return db.query(Product).limit(page_size).all() # Fixed β enforce maximum page size @app.get("/api/products") def list_products(page_size: int = Query(default=20, le=100)): return db.query(Product).limit(page_size).all()
API5: Broken Function Level Authorization
Different from BOLA β this is about endpoint-level access control, not object-level. Admin endpoints accessible to regular users, HTTP method-level bypasses (GET is allowed but POST isn't, and the POST does privileged things), or internal endpoints exposed externally without authentication.
A common pattern we've seen: An API has GET /api/users/{id} (public) and DELETE /api/users/{id} (admin only). The authentication middleware checks the user role for GET but the DELETE check is missing or accidentally set to the wrong required role. The endpoints look identical from a routing perspective, making it easy to miss.
API6: Unrestricted Access to Sensitive Business Flows
Some API flows are sensitive not because they require admin access but because unrestricted access enables abuse: bulk purchasing to corner a limited inventory, automated promo code generation, scraping user data at scale, or mass account creation for spam. These require business logicβlevel rate limiting, not just technical rate limiting.
API7: Server-Side Request Forgery (SSRF)
SSRF occurs when the API accepts a URL from client input and fetches it server-side β enabling attackers to probe internal services, hit cloud metadata endpoints, or access services behind firewalls. Webhooks, URL preview features, and import-from-URL functionality are classic SSRF vectors. Validate URLs against an allowlist of permitted destinations before making outbound requests.
API8: Security Misconfiguration
API-specific misconfigurations include CORS policies that allow any origin with credentials, verbose error messages leaking stack traces, HTTP methods enabled that shouldn't be (TRACE, OPTIONS returning sensitive info), and APIs exposing Swagger/OpenAPI documentation in production without authentication β giving attackers a complete map of every endpoint.
API9: Improper Inventory Management
Shadow APIs β endpoints that exist but aren't documented, monitored, or maintained β are a major risk. Old API versions (v1, v2) often lack the security controls added in newer versions. API gateways that don't route to all endpoints still leave those endpoints exposed if they're directly reachable. Maintain an API inventory and retire old versions actively.
API10: Unsafe Consumption of APIs
When your API consumes third-party APIs, you inherit their security posture. If a third-party API you trust returns malicious content (via compromise or supply chain attack), and you process it without validation, that content can affect your users. Validate and sanitise data from external APIs. Don't blindly trust TLS as proof of data integrity β the server on the other end may be legitimate but compromised.
Testing Your APIs
Combining SAST and DAST gives the best coverage:
- SAST catches BOLA patterns (missing user ownership checks), mass assignment (no input schema), and weak auth (no signature verification)
- DAST tests running APIs with actual requests β useful for BOLA enumeration, rate limit testing, and authentication bypass
- Manual review for business logic (API6) β automated tools can't understand your specific flow semantics
- Schema-based fuzzing β use your OpenAPI spec to generate edge-case payloads
Test Your APIs Against the OWASP API Top 10
AquilaX combines SAST and DAST to test REST and GraphQL APIs for BOLA, mass assignment, authentication failures, and more β automatically on every deployment.
Start Free Scan