APIs Are Now the Primary Attack Surface
In 2024, Gartner estimated that API attacks had become the most frequent web application attack vector. This isn't a surprising trend β it's the logical consequence of the shift to API-first architecture, microservices, and mobile applications that all depend on REST and GraphQL APIs.
The problem: API security is qualitatively different from web security. Traditional web scanners understand HTML forms and cookies. They don't understand REST semantics, JWT token structures, multi-tenant data models, or GraphQL introspection. A Nessus scan of an API-first application tells you very little about its actual security posture.
OWASP API Security Top 10: OWASP published a dedicated API Security Top 10 project precisely because API vulnerabilities don't map cleanly to the web application Top 10. The 2023 edition is the current reference.
OWASP API Security Top 10: What Matters Most
The full list is worth reading, but in practice, five categories account for the vast majority of real-world API breaches:
- API1 - Broken Object Level Authorization (BOLA): The API equivalent of IDOR. Trusting user-supplied object IDs without verifying ownership. Most common API vulnerability we find.
- API2 - Broken Authentication: Weak JWT implementations, missing token expiry, API keys in URLs, no token rotation.
- API3 - Broken Object Property Level Authorization: Exposing internal fields (admin flags, cost prices, other users' data) in API responses when only a subset should be visible.
- API4 - Unrestricted Resource Consumption: No rate limiting, allowing attackers to scrape your entire dataset, brute-force credentials, or cause DoS through expensive operations.
- API8 - Security Misconfiguration: Debug endpoints left on, CORS wildcard origins, overly permissive HTTP verbs, missing security headers.
Authentication Flaws in APIs
API authentication has more moving parts than web app auth β and more ways to go wrong. The most common issues we find:
JWT implementation mistakes
# Vulnerable β accepts 'none' algorithm, no expiry check def verify_token(token: str): payload = jwt.decode( token, options={"verify_signature": False} # never do this ) return payload # Correct β explicit algorithm, signature and expiry verified def verify_token(token: str): payload = jwt.decode( token, SECRET_KEY, algorithms=["HS256"], # explicit allowlist options={"require": ["exp", "iat", "sub"]} ) return payload
API keys in URLs
API keys in query parameters (?api_key=abc123) appear in server logs, browser history, Referer headers, and third-party analytics. They should always be in an Authorization header or a non-logged header.
Missing token scope validation
A token issued for read access being accepted by write endpoints. Scopes should be validated on every request, not just at token issuance.
Rate Limiting and Why Its Absence Is Critical
An API without rate limiting is an invitation to credential stuffing, data scraping, and account enumeration. We've seen APIs where a single attacker could make 10,000 login attempts per minute β equivalent to checking every 4-digit PIN in under a minute.
from slowapi import Limiter from slowapi.util import get_remote_address limiter = Limiter(key_func=get_remote_address) @app.post("/auth/login") @limiter.limit("5/minute") # 5 attempts per minute per IP async def login(request: Request, credentials: LoginSchema): ...
Rate limiting should be applied at multiple levels: per-IP, per-account, and per-endpoint. Brute-force protection on auth endpoints is non-negotiable. Scraping protection on data-heavy endpoints is equally important β even if the data isn't sensitive, allowing bulk download gives competitors or bad actors your entire dataset for free.
Rate limit at the infrastructure level too: Application-level rate limiting can be bypassed if the attacker can hit multiple app servers. Gateway-level or CDN-level rate limiting is harder to circumvent.
Mass Assignment Vulnerabilities
Mass assignment occurs when an API automatically maps request body fields to model properties β including fields the user shouldn't control. The classic example: a user registration endpoint that accepts is_admin: true in the JSON body because the ORM blindly maps all incoming fields.
# Vulnerable β accepts all fields from request body @app.post("/users") def create_user(body: dict): user = User(**body) # attacker sends {"email":"x","is_admin":true} db.add(user) # Fixed β explicit schema with only allowed fields class UserCreate(BaseModel): email: str password: str display_name: str # is_admin is NOT here β users can't set it @app.post("/users") def create_user(body: UserCreate): user = User(email=body.email, display_name=body.display_name, password_hash=hash_password(body.password)) db.add(user)
Excessive Data Exposure
APIs commonly return full database objects and rely on the client to filter what it displays. This is convenient for developers and catastrophic for security β the full object is transmitted and visible in network traffic, regardless of what the UI shows.
Common over-exposed fields we find in API responses:
- Password hashes in user profile responses
- Internal cost prices in product APIs
- Admin flags and role fields visible to regular users
- Other users' email addresses or phone numbers in team listings
- Internal database IDs that enable BOLA attacks
The fix is explicit response schemas that define exactly what each role should receive. Never return the full database object β always project to a response type.
Tools for API Security Testing
- Burp Suite: The industry standard for manual API testing. Intercepting and modifying API requests, testing BOLA by replacing IDs, replaying requests with different auth tokens.
- Postman: Good for building structured test collections. The Postman Security testing collection covers many OWASP API Top 10 checks.
- OWASP ZAP: Free, open-source DAST scanner with API support. Can test OpenAPI/Swagger specs automatically.
- sqlmap: For testing API endpoints for SQL injection (specify the endpoint URL and request format).
- jwt_tool: Specifically for testing JWT implementation weaknesses β algorithm confusion, none algorithm, weak secrets.
Import your OpenAPI spec: Tools like OWASP ZAP and commercial DAST scanners can import OpenAPI/Swagger specs to automatically discover and test all endpoints β including ones you might forget to test manually.
CI/CD Integration for API Security
name: API Security Gates on: [push, pull_request] jobs: api-security: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: SAST scan uses: aquilax/scan-action@v1 with: scan-type: sast,secrets fail-on: high - name: Start API server run: docker-compose up -d api - name: DAST API scan run: zap-api-scan.py -t http://localhost:8000/openapi.json -f openapi
API Security Checklist
- Every endpoint verifies ownership of requested objects (no BOLA)
- JWTs validated with explicit algorithm allowlist and expiry enforcement
- API keys in Authorization headers β never in query strings or response bodies
- Rate limiting on auth endpoints (5 req/min), search/data endpoints (varies)
- Explicit response schemas β no full object passthrough
- Input validated with explicit schemas β no mass assignment
- CORS configured to specific origins β no wildcard in production
- No debug endpoints reachable in production
- HTTP method restrictions on all endpoints (OPTIONS returns only allowed methods)
- API versioning strategy β old versions deprecated and removed, not just undocumented
Scan Your APIs for OWASP API Top 10
AquilaX combines SAST and DAST to detect BOLA, mass assignment, excessive data exposure, and authentication flaws in your REST APIs.
Start Free Scan