Files
dev-workflows/skills/api-security-review/plays/api-security-review.md
Evandro Fonseca Junior bb3fe21766 fix(skills): vendor the support files the vendored skills point at
The vendored SKILL.md files were byte-identical to upstream, but the
playbooks, templates and schemas they reference were left behind in the
original repos, so a loading skill pointed at a file that does not exist
anywhere in this repository.

Vendored 1:1 into each skill directory (so they survive
`cp -r skills/* ~/.hermes/skills/`):

- plays/ for api-security-review, code-review-security, sca-audit,
  secrets-scan, web-security-review (OWASP/secure-agent-playbook)
- templates/finding.md for the four OWASP skills that cite it
- schemas/finding.schema.json for cve-triage, patch-prioritization,
  dependency-scanning (UnitOneAI/SecuritySkills)
- docs/sarif-output.md, docs/fixer-policy.md and
  docs/normalized-json-output.md for dependency-scanning

Path fixes, the only edit to vendored text:
- the three UnitOneAI skills referenced ../../../schemas/ and ../../../docs/
  which assumed upstream's skills/<category>/<skill>/ depth; this repo is
  flat, so the links now resolve inside the skill directory
- safedeps told the agent to run <skill_dir>/../../check_deps.py while the
  script is vendored next to SKILL.md; corrected to <skill_dir>/check_deps.py
  and documented the script's exit codes (1 = finding, not a crash)
- NOTICE.md records the new files, the path edits, and the remaining known
  gaps (hermaguard's kensei-pipeline references, the ASVS dataset citations,
  and hermaguard being behind upstream)

Verified: every backticked path in every SKILL.md now resolves;
check_deps.py runs from the installed location and returns live OSV.dev
data ([email protected] -> 2 advisories); hermaguard's 46 tests still pass.
2026-09-14 07:40:21 -03:00

39 KiB

Play: API Security Review

Comprehensive API security assessment against OWASP API Security Top 10 (2023) with real-world attack scenarios, automated testing integration, and detailed remediation guidance.

Trigger Conditions

Use this play when:

  • Reviewing OpenAPI/Swagger specifications or API documentation
  • Auditing REST, GraphQL, gRPC, or SOAP API implementations
  • Testing API authentication and authorization mechanisms
  • Reviewing API gateway configurations (Kong, nginx, Envoy, AWS API Gateway)
  • Conducting pre-deployment security assessments
  • Performing API penetration testing or security audits
  • A user asks to "test my API for security issues"

Inputs

  • OpenAPI/Swagger specification (YAML/JSON) or API documentation
  • API source code (routes, controllers, middleware, authentication logic)
  • API gateway and infrastructure configurations
  • Authentication implementation details (JWT, OAuth, API keys)
  • Environment information (production, staging, development)
  • Previous security assessment reports (if available)

Procedure

Phase 1: Discovery & Reconnaissance

1.1 API Surface Enumeration

With OpenAPI Spec:

# Parse and analyze OpenAPI spec
# Look for:
- Total endpoints and HTTP methods
- Authentication requirements per endpoint
- Request/response schemas
- Rate limiting documentation
- Deprecated endpoints

Without OpenAPI Spec (Code Analysis):

  • Scan route definitions in framework-specific patterns:
    • Express: app.get(), router.post()
    • Django: path(), url()
    • Rails: resources, get/post/put/delete
    • Spring: @GetMapping, @RequestMapping
    • FastAPI: @app.get(), decorator-based routes

Key Questions:

  • How many endpoints exist?
  • Which require authentication vs. public?
  • What data types are exchanged?
  • Are there file upload endpoints?
  • Are there admin/debug endpoints?

1.2 Authentication Mechanism Identification

Document all auth mechanisms in use:

Mechanism Indicators Security Considerations
JWT Authorization: Bearer eyJ... Algorithm, signing key strength, expiration
OAuth 2.0 /oauth/authorize, /oauth/token Flow type, PKCE, state parameter, redirect URI
API Keys X-API-Key, api_key query param Key rotation, transmission security, scope
Session Cookies Set-Cookie: sessionid=... HttpOnly, Secure, SameSite, expiration
mTLS Client certificate requirements Certificate validation, revocation
Basic Auth Authorization: Basic base64 HTTPS enforcement, credential storage

Phase 2: Authentication Security Assessment

2.1 JWT Security Testing

Common JWT Vulnerabilities:

  1. Algorithm Confusion (CVE-2015-9235)

    Attack: Change algorithm from RS256 to none or HS256
    Header: {"alg":"none","typ":"JWT"}
    Result: Token accepted without verification
    
  2. Weak Signing Keys

    # Test for weak secrets if using HS256
    # Use jwt_tool or hashcat for brute force
    jwt_tool.py token_here -C -d /usr/share/wordlists/rockyou.txt
    
  3. Missing Expiration

    Check: Does payload contain "exp" claim?
    Risk: Tokens valid forever if stolen
    
  4. Token Storage Issues

    // Insecure: localStorage (XSS accessible)
    localStorage.setItem('jwt', token);
    
    // More secure: httpOnly cookie
    document.cookie = "jwt=" + token + "; HttpOnly; Secure; SameSite=Strict";
    

Testing Checklist:

  • Test algorithm confusion (change alg to "none")
  • Attempt key confusion attacks (RS256 → HS256)
  • Check for missing expiration (exp claim)
  • Verify signature validation cannot be bypassed
  • Test token revocation mechanisms
  • Check for sensitive data in JWT payload

2.2 OAuth 2.0 Security Testing

Critical Checks:

  1. Authorization Code Flow

    Required: PKCE (Proof Key for Code Exchange)
    Check: code_challenge and code_verifier parameters
    Risk: Without PKCE, auth codes can be stolen and used
    
  2. State Parameter

    Required: Random state parameter to prevent CSRF
    Check: Is state validated on callback?
    Attack: Omit state → CSRF login attack
    
  3. Redirect URI Validation

    Check: Is redirect_uri strictly validated?
    Attack: redirect_uri=https://attacker.com/callback
    Risk: Authorization code/token sent to attacker
    
  4. Scope Validation

    Check: Can user modify requested scopes?
    Attack: Change scope=read to scope=admin
    Risk: Privilege escalation via scope manipulation
    

Testing Checklist:

  • Verify PKCE implementation for public clients
  • Test state parameter CSRF protection
  • Attempt redirect URI manipulation
  • Test scope escalation attacks
  • Check for open redirect vulnerabilities
  • Verify token endpoint authentication

2.3 API Key Security

Common Issues:

  1. Key Exposure

    # Search for hardcoded keys
    grep -r "api_key\|apikey\|api-key" --include="*.js" --include="*.py" --include="*.env"
    
    # Check client-side code
    # API keys in frontend code can be stolen
    
  2. Insufficient Scope

    Issue: Single key with full access
    Best Practice: Scoped keys (read-only, write, admin)
    
  3. Missing Rotation

    Check: Can keys be rotated without downtime?
    Risk: Compromised keys valid indefinitely
    

Phase 3: OWASP API Top 10 Deep Assessment

API1: Broken Object Level Authorization (BOLA/IDOR)

Risk: Attackers access other users' data by manipulating object IDs

Real-World Impact:

  • 2019: First American Financial exposed 885 million records via IDOR
  • 2020: Twitter API IDOR allowed viewing protected tweets
  • 2021: Facebook IDOR exposed private posts

Attack Scenarios:

  1. Sequential ID Manipulation

    GET /api/users/123/orders
    
    # Try:
    GET /api/users/124/orders  # Access other user's orders
    GET /api/users/1/orders    # Access admin's orders
    
  2. UUID Predictability

    If using predictable UUIDv1 (timestamp-based):
    Generate nearby UUIDs to find other users
    
  3. Batch Endpoint Bypass

    POST /api/orders/bulk
    {
      "order_ids": [1001, 1002, 1003, 1004]  # Can access any IDs?
    }
    
  4. Indirect Object References

    GET /api/orders?id=1001
    
    # vs
    
    GET /api/orders?id=1001&user_id=123  # Is user_id validated?
    

Testing Approach:

  1. Map Object IDs: Identify all endpoints accepting object IDs
  2. Cross-User Testing:
    • Create User A and User B accounts
    • Have User A create resources
    • Attempt to access with User B's credentials
  3. Batch Testing: Test if batch endpoints filter by ownership
  4. File Access: Test file download endpoints with different IDs
  5. IDOR Automation:
    # Use Burp Intruder or custom script
    # Iterate through IDs and check for 200 responses
    for i in {1..1000}; do
      curl -H "Authorization: Bearer $TOKEN" \
           "https://api.example.com/orders/$i" | grep -q "order_id" && echo "Found: $i"
    done
    

Remediation:

# BAD: No ownership check
def get_order(order_id):
    return Order.objects.get(id=order_id)

# GOOD: Verify ownership
def get_order(order_id, user):
    return Order.objects.get(id=order_id, user=user)
    # Or: Check ownership after retrieval
    order = Order.objects.get(id=order_id)
    if order.user_id != user.id:
        raise PermissionDenied()
    return order

API2: Broken Authentication

Risk: Weak authentication allows account takeover

Real-World Impact:

  • 2022: Uber breach via compromised contractor account
  • 2023: 23andMe breach via credential stuffing

Attack Scenarios:

  1. Credential Stuffing

    # Automated login attempts with breached credentials
    # APIs often lack CAPTCHA or rate limiting
    
  2. JWT None Algorithm

    Header: {"alg":"none","typ":"JWT"}
    Payload: {"user_id":1,"role":"admin"}
    Signature: (empty)
    
    Result: Server accepts unsigned token
    
  3. Weak Password Policy

    POST /api/login
    {"username":"admin","password":"123456"}
    
    # Test common passwords
    # Check minimum length requirements
    
  4. Session Fixation

    Attacker sets session ID
    Victim logs in with attacker's session ID
    Attacker uses same session ID to access account
    

Testing Approach:

  1. Brute Force Testing:

    # Test login endpoint rate limiting
    for i in {1..100}; do
      curl -X POST https://api.example.com/login \
           -d "username=admin&password=wrong$i"
    done
    # Check if account locks or IP is blocked
    
  2. Token Security:

    • Decode JWT and analyze claims
    • Test algorithm confusion
    • Check expiration and refresh mechanisms
    • Test token revocation
  3. Password Policy:

    • Test minimum length (should be 12+)
    • Test complexity requirements
    • Check common password blocking
    • Verify secure password reset flow
  4. Multi-Factor Authentication:

    • Is MFA enforced for sensitive operations?
    • Can MFA be bypassed?
    • Are backup codes secure?

Remediation:

# Implement rate limiting
from django_ratelimit.decorators import ratelimit

@ratelimit(key='ip', rate='5/m', method='POST')
def login(request):
    # Login logic
    pass

# Strong password validation
from django.contrib.auth.password_validation import validate_password

def validate_user_password(password):
    try:
        validate_password(password)
    except ValidationError as e:
        raise e

# Secure JWT configuration
JWT_SETTINGS = {
    'ALGORITHM': 'HS256',  # Use strong algorithms only
    'VERIFY_EXPIRATION': True,
    'EXPIRATION_DELTA': timedelta(minutes=15),
    'REFRESH_EXPIRATION_DELTA': timedelta(days=7),
}

API3: Broken Object Property Level Authorization

Risk: Mass assignment and data over-exposure

Real-World Impact:

  • 2012: GitHub mass assignment allowed unauthorized repo access
  • 2019: Facebook mass assignment exposed user data

Attack Scenarios:

  1. Mass Assignment

    POST /api/users
    {
      "username": "newuser",
      "email": "[email protected]",
      "password": "password123",
      "role": "admin",  # Should not be user-settable
      "is_verified": true  # Should not be user-settable
    }
    
  2. Response Over-Exposure

    GET /api/users/123
    
    Response:
    {
      "id": 123,
      "username": "john",
      "email": "[email protected]",
      "password_hash": "$2b$12$...",  # Should not be exposed!
      "ssn": "123-45-6789",  # PII exposure
      "internal_notes": "Difficult customer"
    }
    
  3. Field-Level Authorization Bypass

    PUT /api/users/123
    {
      "salary": 200000  # Can regular users modify this?
    }
    

Testing Approach:

  1. Mass Assignment Testing:

    # Send extra fields in POST/PUT requests
    payload = {
        "username": "test",
        "email": "[email protected]",
        "password": "password",
        "role": "admin",  # Try to escalate
        "is_admin": True,
        "credit_balance": 10000
    }
    
  2. Response Analysis:

    • Capture all API responses
    • Look for sensitive fields (passwords, tokens, PII)
    • Compare responses for different user roles
    • Check if internal fields are exposed
  3. GraphQL Over-Exposure:

    # Request fields you shouldn't see
    query {
      user(id: 123) {
        id
        name
        email
        password  # Should be rejected
        ssn       # Should be rejected
        salary    # Should be rejected for non-admins
      }
    }
    

Remediation:

# Use allowlist approach
ALLOWED_USER_FIELDS = ['username', 'email', 'password']

def update_user(user_id, data, current_user):
    # Filter input to allowed fields only
    filtered_data = {k: v for k, v in data.items() 
                     if k in ALLOWED_USER_FIELDS}
    
    # Check field-level permissions
    if 'role' in data and not current_user.is_admin:
        raise PermissionDenied("Cannot modify role")
    
    return User.objects.filter(id=user_id).update(**filtered_data)

# Response serialization
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username', 'email']  # Explicit allowlist
        # Never include: password, ssn, internal_notes

API4: Unrestricted Resource Consumption

Risk: DoS via resource exhaustion, financial drain

Real-World Impact:

  • 2023: OpenAI API abuse cost companies thousands
  • 2022: GraphQL query DoS attacks on multiple platforms

Attack Scenarios:

  1. Rate Limit Bypass

    # Distributed attack from multiple IPs
    # Or using different API keys
    # Or changing User-Agent headers
    
  2. Expensive Query Abuse

    # Deeply nested GraphQL query
    query {
      users {
        posts {
          comments {
            author {
              posts {
                comments {
                  author {
                    posts { ... }  # Infinite recursion potential
                  }
                }
              }
            }
          }
        }
      }
    }
    
  3. Large Payload Attacks

    POST /api/upload
    Content-Length: 1000000000  # 1GB file
    
    # Or JSON bomb:
    {"a":{"a":{"a":{"a":...}}}}  # Deeply nested JSON
    
  4. Pagination Abuse

    GET /api/items?limit=1000000  # Request all records
    GET /api/items?page=1&per_page=10000  # Large page size
    

Testing Approach:

  1. Rate Limit Testing:

    # Test from single IP
    for i in {1..1000}; do
      curl https://api.example.com/data
    done
    
    # Test bypass methods:
    # - Different API keys
    # - Rotating User-Agents
    # - Distributed requests
    
  2. GraphQL DoS:

    # Test query complexity
    # Test query depth
    # Test expensive operations
    
  3. Resource Exhaustion:

    • Upload extremely large files
    • Send deeply nested JSON
    • Request massive result sets
    • Trigger expensive database queries

Remediation:

# Rate limiting
from django_ratelimit.decorators import ratelimit

@ratelimit(key='user', rate='100/h', method=['GET', 'POST'])
def api_endpoint(request):
    pass

# GraphQL safeguards
GRAPHQL_SETTINGS = {
    'MAX_DEPTH': 10,
    'MAX_COMPLEXITY': 1000,
    'DISABLE_INTROSPECTION': True,  # In production
}

# Pagination limits
MAX_PAGE_SIZE = 100
DEFAULT_PAGE_SIZE = 20

def get_items(request):
    page_size = min(
        int(request.GET.get('limit', DEFAULT_PAGE_SIZE)),
        MAX_PAGE_SIZE
    )
    return Item.objects.all()[:page_size]

# File upload limits
MAX_UPLOAD_SIZE = 10 * 1024 * 1024  # 10MB

# Timeout configuration
REQUEST_TIMEOUT = 30  # seconds

API5: Broken Function Level Authorization

Risk: Access to admin/sensitive functions without proper authorization

Real-World Impact:

  • 2020: Twitter admin tool breach
  • 2021: Parler admin API exposed

Attack Scenarios:

  1. Admin Endpoint Discovery

    GET /api/admin/users  # Try common admin paths
    GET /api/v1/admin/config
    GET /api/internal/audit-logs
    GET /api/debug/info
    
  2. HTTP Method Switching

    GET /api/users/123    # Allowed for regular users
    DELETE /api/users/123 # Should require admin, but is it checked?
    PUT /api/users/123/role  # Can users change their own role?
    
  3. Parameter-Based Access Control

    POST /api/users
    {
      "username": "test",
      "role": "admin"  # Is this validated server-side?
    }
    

Testing Approach:

  1. Endpoint Enumeration:

    # Use wordlists to find admin endpoints
    # Common paths: /admin, /internal, /debug, /system
    
  2. Method Testing:

    # For each endpoint, test all HTTP methods
    curl -X GET /api/resource
    curl -X POST /api/resource
    curl -X PUT /api/resource
    curl -X DELETE /api/resource
    curl -X PATCH /api/resource
    
  3. Role Escalation:

    • Create regular user account
    • Attempt to access admin endpoints
    • Try to modify role/privilege fields

Remediation:

# Centralized authorization decorator
from functools import wraps

def require_admin(f):
    @wraps(f)
    def decorated_function(request, *args, **kwargs):
        if not request.user.is_admin:
            return Response(
                {"error": "Admin access required"}, 
                status=403
            )
        return f(request, *args, **kwargs)
    return decorated_function

@require_admin
def admin_endpoint(request):
    pass

# Middleware approach
class AdminAuthorizationMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        self.admin_paths = ['/api/admin/', '/api/internal/']
    
    def __call__(self, request):
        if any(request.path.startswith(path) for path in self.admin_paths):
            if not request.user.is_admin:
                return HttpResponseForbidden()
        return self.get_response(request)

API6: Unrestricted Access to Sensitive Business Flows

Risk: Automated abuse of business logic

Real-World Impact:

  • Ticket scalping: Bots buying all concert tickets
  • E-commerce: Inventory hoarding, price scraping
  • Crypto: Flash loan attacks via API manipulation

Attack Scenarios:

  1. Inventory Exhaustion

    POST /api/cart/add
    {"product_id": 123, "quantity": 999999}  # Buy all inventory
    
  2. Automated Purchasing

    # Script that completes purchase faster than humans
    # Bypassing any client-side rate limits
    
  3. Price Manipulation

    POST /api/order
    {
      "items": [...],
      "total": 1.00  # Is total calculated server-side?
    }
    
  4. Reservation Abuse

    POST /api/reservations
    {"resource_id": 123, "start": "2024-01-01", "end": "2025-12-31"}
    # Reserve resource for entire year
    

Testing Approach:

  1. Business Logic Testing:

    • Attempt to complete purchases with modified prices
    • Try to add negative quantities
    • Test race conditions in multi-step flows
    • Attempt to bypass payment steps
  2. Automation Testing:

    # Script to test if flows can be automated
    import requests
    import time
    
    start = time.time()
    for i in range(100):
        requests.post('/api/purchase', json={...})
    elapsed = time.time() - start
    
    # If no rate limiting, 100 requests complete quickly
    

Remediation:

# Server-side price calculation
def create_order(items, user):
    total = sum(item.product.price * item.quantity for item in items)
    # Never trust client-provided total
    
    # Inventory checks
    for item in items:
        if item.quantity > item.product.available_stock:
            raise ValidationError("Insufficient inventory")
    
    # Rate limiting per user
    if user.orders.filter(created_at__gte=timezone.now() - timedelta(hours=1)).count() > 10:
        raise RateLimitError("Too many orders")
    
    return Order.objects.create(user=user, items=items, total=total)

# CAPTCHA for sensitive operations
from django_recaptcha.fields import ReCaptchaField

class PurchaseForm(forms.Form):
    recaptcha = ReCaptchaField()
    # ... other fields

API7: Server-Side Request Forgery (SSRF)

Risk: Server makes requests to attacker-controlled or internal resources

Real-World Impact:

  • 2019: Capital One breach via SSRF (100M records)
  • 2021: Multiple cloud metadata service exploits

Attack Scenarios:

  1. Cloud Metadata Access

    GET /api/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
    # Retrieves AWS credentials
    
  2. Internal Service Access

    GET /api/fetch?url=http://localhost:8080/admin
    GET /api/fetch?url=http://internal-api.company.local/secrets
    
  3. URL Bypass Techniques

    http://127.0.0.1 → localhost bypass
    http://0177.0.0.1 → Octal encoding
    http://2130706433 → Decimal IP
    http://0x7f.0x0.0x0.0x1 → Hex encoding
    http://[::ffff:127.0.0.1] → IPv6
    http://127.1 → Shortened
    
  4. DNS Rebinding

    Attacker controls DNS that resolves to:
    - First request: legitimate.com → 1.2.3.4
    - Second request: attacker.com → 169.254.169.254
    

Testing Approach:

  1. Basic SSRF Testing:

    # Test internal IP access
    curl "https://api.example.com/fetch?url=http://169.254.169.254/"
    curl "https://api.example.com/fetch?url=http://localhost:22"
    curl "https://api.example.com/fetch?url=file:///etc/passwd"
    
  2. Bypass Testing:

    # Try various encodings
    curl "https://api.example.com/fetch?url=http://0177.0.0.1/"
    curl "https://api.example.com/fetch?url=http://2130706433/"
    curl "https://api.example.com/fetch?url=http://[::ffff:127.0.0.1]/"
    
  3. Port Scanning via SSRF:

    # Test if server can reach internal ports
    for port in 22 80 443 3306 5432 6379 8080; do
      curl "https://api.example.com/fetch?url=http://localhost:$port"
    done
    

Remediation:

import ipaddress
import re
from urllib.parse import urlparse

def is_internal_ip(ip_str):
    """Check if IP is internal/private"""
    try:
        ip = ipaddress.ip_address(ip_str)
        return ip.is_private or ip.is_loopback or ip.is_reserved
    except ValueError:
        return False

def validate_url(url):
    """Validate URL for SSRF prevention"""
    parsed = urlparse(url)
    
    # Allowlist approach (recommended)
    allowed_hosts = ['api.trusted.com', 'cdn.trusted.com']
    if parsed.hostname not in allowed_hosts:
        raise ValidationError("URL not in allowlist")
    
    # Blocklist approach (supplementary)
    blocked_schemes = ['file', 'ftp', 'gopher', 'dict']
    if parsed.scheme in blocked_schemes:
        raise ValidationError("Scheme not allowed")
    
    # Resolve and check IP
    import socket
    try:
        ip = socket.gethostbyname(parsed.hostname)
        if is_internal_ip(ip):
            raise ValidationError("Internal IPs not allowed")
    except socket.gaierror:
        raise ValidationError("Could not resolve hostname")
    
    return url

# Alternative: Use a dedicated SSRF protection library
# pip install ssrf-protect
from ssrf_protect import SSRFProtect

@SSRFProtect()
def fetch_url(request):
    url = request.GET.get('url')
    # Safe to fetch
    return requests.get(url)

API8: Security Misconfiguration

Risk: Insecure default configurations expose APIs to attacks

Real-World Impact:

  • 2018: Tesla Kubernetes API exposed (cryptomining)
  • 2020: Multiple Elasticsearch instances exposed without auth

Attack Scenarios:

  1. CORS Misconfiguration

    Access-Control-Allow-Origin: *  # Too permissive
    Access-Control-Allow-Credentials: true  # Dangerous combination
    
  2. Verbose Error Messages

    {
      "error": "SQL syntax error near 'user_id = 123'",
      "stack_trace": "...",
      "query": "SELECT * FROM users WHERE user_id = 123"
    }
    
  3. Missing Security Headers

    # Missing headers:
    X-Content-Type-Options: nosniff
    X-Frame-Options: DENY
    Content-Security-Policy: ...
    Strict-Transport-Security: max-age=31536000
    
  4. Debug Mode Enabled

    /api/debug/vars  # Exposes Go runtime variables
    /api/swagger-ui/  # API documentation with try-it feature
    /api/actuator/health  # Spring Boot endpoints
    

Testing Approach:

  1. Security Headers Check:

    curl -I https://api.example.com/endpoint | grep -i "access-control\|x-"
    
  2. Error Handling Test:

    # Trigger errors and check responses
    curl "https://api.example.com/endpoint?param=' OR 1=1--"
    curl "https://api.example.com/endpoint" -X INVALID_METHOD
    
  3. Debug Endpoint Discovery:

    # Common debug paths
    /debug
    /actuator
    /swagger-ui
    /api-docs
    /health
    /metrics
    

Remediation:

# Django middleware for security headers
class SecurityHeadersMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
    
    def __call__(self, request):
        response = self.get_response(request)
        
        # Security headers
        response['X-Content-Type-Options'] = 'nosniff'
        response['X-Frame-Options'] = 'DENY'
        response['X-XSS-Protection'] = '1; mode=block'
        response['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
        response['Content-Security-Policy'] = "default-src 'self'"
        response['Referrer-Policy'] = 'strict-origin-when-cross-origin'
        
        # CORS (restrictive)
        response['Access-Control-Allow-Origin'] = 'https://trusted-domain.com'
        response['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE'
        response['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
        
        return response

# Disable debug mode in production
DEBUG = False

# Custom error handler (don't expose internals)
from django.http import JsonResponse

def custom_error_handler(request, exception=None):
    return JsonResponse(
        {"error": "An error occurred"},
        status=500
    )

API9: Improper Inventory Management

Risk: Shadow APIs and undocumented endpoints create attack surface

Real-World Impact:

  • 2021: Facebook API exposed user data via undocumented endpoint
  • 2022: Twitter API v1.1 endpoints still accessible after v2 launch

Attack Scenarios:

  1. Shadow API Discovery

    Compare OpenAPI spec with actual code
    Look for endpoints not in documentation
    
  2. Deprecated API Usage

    GET /api/v1/users  # Old version still active
    GET /api/v2/users  # New version
    # v1 may have known vulnerabilities
    
  3. Environment Confusion

    Production API: api.company.com
    Staging API: api-staging.company.com  # Exposed?
    Dev API: api-dev.company.com  # Exposed with debug enabled?
    

Testing Approach:

  1. API Inventory:

    # Extract all routes from code
    grep -r "@app.route\|@GetMapping\|path(" --include="*.py" --include="*.java"
    
    # Compare with OpenAPI spec
    # Identify undocumented endpoints
    
  2. Version Testing:

    # Test old API versions
    curl https://api.example.com/v1/users
    curl https://api.example.com/v2/users
    
  3. Environment Discovery:

    # Check for exposed environments
    curl https://api-staging.example.com/health
    curl https://api-dev.example.com/debug
    

Remediation:

# API Gateway configuration (Kong example)
plugins:
  - name: request-termination
    config:
      status_code: 410
      message: "API version deprecated"
    route: api-v1-routes

# Automated API discovery
# Use tools like:
# - OWASP ZAP API scanner
# - Postman collection generator
# - Swagger Codegen

# Regular audits
# Monthly: Compare deployed endpoints with documentation
# Quarterly: Review and deprecate old API versions

API10: Unsafe Consumption of APIs

Risk: Trusting third-party API responses leads to injection attacks

Real-World Impact:

  • 2020: Multiple XXE attacks via external API responses
  • 2021: Deserialization attacks from webhook payloads

Attack Scenarios:

  1. XXE via External API

    <!-- Malicious response from third-party API -->
    <?xml version="1.0"?>
    <!DOCTYPE foo [
      <!ENTITY xxe SYSTEM "file:///etc/passwd">
    ]>
    <data>&xxe;</data>
    
  2. Deserialization Attack

    # Attacker controls serialized data
    import pickle
    malicious = b'...crafted pickle payload...'
    data = pickle.loads(malicious)  # RCE!
    
  3. Webhook Replay

    # Attacker captures webhook and replays it
    POST /api/webhooks/payment
    X-Webhook-Signature: valid_signature
    {"event": "payment.success", "amount": 1000}
    
    # Replay same request to double credits
    
  4. JSON Injection

    // Malicious JSON from external API
    {
      "username": "admin",
      "bio": "<script>alert('XSS')</script>"
    }
    

Testing Approach:

  1. XXE Testing:

    # If API accepts XML from third parties
    # Send malicious XML to test for XXE
    
  2. Deserialization Testing:

    # Check for unsafe deserialization
    # Look for pickle, ObjectInputStream, unserialize
    
  3. Webhook Security:

    # Test webhook replay
    # Check signature validation
    # Verify timestamp/nonce checks
    

Remediation:

# XXE Prevention
import xml.etree.ElementTree as ET
from defusedxml import ElementTree as DefusedET

def parse_xml_safely(xml_string):
    # Use defusedxml to prevent XXE
    return DefusedET.fromstring(xml_string)

# Deserialization Safety
import json

def safe_deserialize(data):
    # Use JSON instead of pickle for untrusted data
    return json.loads(data)
    
    # If you must use pickle, sign the data
    import hmac
    import hashlib
    
    signature = hmac.new(
        SECRET_KEY,
        data,
        hashlib.sha256
    ).hexdigest()
    
    if not hmac.compare_digest(signature, expected_signature):
        raise ValueError("Invalid signature")
    
    return pickle.loads(data)

# Webhook Security
import hmac
import hashlib
import time

def verify_webhook(request):
    # Verify signature
    signature = request.headers.get('X-Webhook-Signature')
    expected = hmac.new(
        WEBHOOK_SECRET,
        request.body,
        hashlib.sha256
    ).hexdigest()
    
    if not hmac.compare_digest(signature, expected):
        raise ValueError("Invalid signature")
    
    # Check timestamp (prevent replay)
    timestamp = int(request.headers.get('X-Webhook-Timestamp'))
    if abs(time.time() - timestamp) > 300:  # 5 minutes
        raise ValueError("Request too old")
    
    # Check nonce (prevent replay)
    nonce = request.headers.get('X-Webhook-Nonce')
    if nonce in used_nonces:
        raise ValueError("Nonce already used")
    used_nonces.add(nonce)
    
    return True

Phase 4: Automated Security Testing

4.1 API Security Scanning

OWASP ZAP:

# Automated API scan
zap-api-scan.py -t https://api.example.com/openapi.json -f openapi

# With authentication
zap-api-scan.py -t https://api.example.com/openapi.json \
  -f openapi \
  -config "replacer.full_list(0).description=auth" \
  -config "replacer.full_list(0).enabled=true" \
  -config "replacer.full_list(0).matchtype=REQ_HEADER" \
  -config "replacer.full_list(0).matchstr=Authorization" \
  -config "replacer.full_list(0).replacement=Bearer $TOKEN"

Burp Suite:

  • Import OpenAPI spec
  • Run active scan on all endpoints
  • Test for BOLA, authentication bypasses

Postman Security Tests:

// Test for sensitive data in response
pm.test("Response does not contain sensitive data", function () {
    pm.expect(pm.response.text()).to.not.include("password");
    pm.expect(pm.response.text()).to.not.include("secret");
});

// Test for security headers
pm.test("Security headers present", function () {
    pm.response.to.have.header("X-Content-Type-Options");
    pm.response.to.have.header("X-Frame-Options");
});

4.2 Custom Security Tests

BOLA Testing Script:

import requests

def test_bola(base_url, token, user_a_id, user_b_id):
    """Test for Broken Object Level Authorization"""
    
    # Get User A's token
    headers = {"Authorization": f"Bearer {token}"}
    
    # Try to access User B's resources
    endpoints = [
        f"/api/users/{user_b_id}",
        f"/api/users/{user_b_id}/orders",
        f"/api/orders?user_id={user_b_id}",
    ]
    
    for endpoint in endpoints:
        response = requests.get(f"{base_url}{endpoint}", headers=headers)
        if response.status_code == 200:
            print(f"BOLA VULNERABILITY: {endpoint}")
            print(f"User A can access User B's data!")

Rate Limit Testing:

import requests
import time

def test_rate_limiting(url, headers, requests_count=100):
    """Test if rate limiting is enforced"""
    
    success_count = 0
    start_time = time.time()
    
    for i in range(requests_count):
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            success_count += 1
        elif response.status_code == 429:
            print(f"Rate limit hit after {i} requests")
            break
    
    elapsed = time.time() - start_time
    print(f"Made {success_count} requests in {elapsed:.2f} seconds")
    
    if success_count == requests_count:
        print("WARNING: No rate limiting detected!")

Phase 5: API Gateway & Infrastructure Review

5.1 Kong Gateway Configuration

# kong.yml
plugins:
  # Rate limiting
  - name: rate-limiting
    config:
      minute: 100
      hour: 1000
      policy: redis
      redis_host: redis
  
  # CORS (restrictive)
  - name: cors
    config:
      origins:
        - "https://app.example.com"
      methods:
        - GET
        - POST
        - PUT
        - DELETE
      headers:
        - Authorization
        - Content-Type
      credentials: true
      max_age: 3600
  
  # Request size limiting
  - name: request-size-limiting
    config:
      allowed_payload_size: 10  # MB
  
  # Bot detection
  - name: bot-detection
    config:
      deny:
        - "Mozilla/5.0 (compatible; Googlebot/2.1)"
  
  # IP restriction
  - name: ip-restriction
    config:
      allow:
        - "10.0.0.0/8"
        - "172.16.0.0/12"

5.2 nginx Configuration

server {
    listen 443 ssl http2;
    server_name api.example.com;
    
    # SSL configuration
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
    
    # Security headers
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    
    # Rate limiting
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    limit_req zone=api burst=20 nodelay;
    
    # Request size limit
    client_max_body_size 10M;
    
    # Timeout settings
    proxy_connect_timeout 30s;
    proxy_send_timeout 30s;
    proxy_read_timeout 30s;
    
    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

5.3 AWS API Gateway

# serverless.yml or CloudFormation
Resources:
  ApiGateway:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: SecureAPI
      
  # Throttling
  ApiGatewayStage:
    Type: AWS::ApiGateway::Stage
    Properties:
      RestApiId: !Ref ApiGateway
      StageName: prod
      MethodSettings:
        - ResourcePath: /*
          HttpMethod: *
          ThrottlingBurstLimit: 100
          ThrottlingRateLimit: 50
          
  # WAF
  WebACL:
    Type: AWS::WAFv2::WebACL
    Properties:
      Name: APIWAF
      Rules:
        - Name: RateLimit
          Priority: 1
          Statement:
            RateBasedStatement:
              Limit: 2000
              AggregateKeyType: IP
          Action:
            Block: {}
          VisibilityConfig:
            SampledRequestsEnabled: true

Output Format

## API Security Assessment: [API Name]

### Executive Summary
- **API Type**: REST | GraphQL | gRPC | SOAP
- **Total Endpoints**: [count]
- **Authentication**: [JWT | OAuth 2.0 | API Keys | Session]
- **Risk Rating**: [Critical | High | Medium | Low]
- **Overall Security Posture**: [Brief assessment]

### API Surface Inventory

#### Authentication Mechanisms
| Mechanism | Implementation | Security Rating |
|-----------|---------------|-----------------|
| JWT | HS256, 15min expiry | ✅ Secure |
| OAuth 2.0 | PKCE enabled | ✅ Secure |

#### Endpoint Breakdown
| Category | Count | Auth Required |
|----------|-------|---------------|
| Public | 5 | No |
| Authenticated | 45 | Yes |
| Admin | 8 | Yes + Admin role |

### Risk Matrix

| OWASP API Ref | Risk | Severity | Status | Evidence |
|--------------|------|----------|--------|----------|
| API1 | BOLA | HIGH | Finding | /api/orders/{id} lacks ownership check |
| API2 | Broken Auth | MEDIUM | Finding | JWT alg=none accepted |
| API3 | BOPA | HIGH | Finding | Mass assignment on /api/users |
| API4 | Resource Consumption | LOW | Finding | No rate limiting on search |
| API5 | BFLA | MEDIUM | Finding | Admin endpoints accessible to users |
| API6 | Business Flows | N/A | N/A | Not applicable |
| API7 | SSRF | CRITICAL | Finding | Cloud metadata accessible |
| API8 | Misconfiguration | MEDIUM | Finding | Debug mode enabled |
| API9 | Inventory | LOW | Finding | Undocumented /api/internal endpoints |
| API10 | Unsafe Consumption | HIGH | Finding | Webhooks lack signature validation |

### Critical Findings

#### [CRITICAL] API7: SSRF - Cloud Metadata Exposure
- **Location**: `/api/fetch?url=` parameter
- **Impact**: AWS credentials can be extracted, leading to full cloud compromise
- **Evidence**: 

GET /api/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ Response: 200 OK with IAM role credentials

- **Remediation**: Implement URL allowlist, block internal IPs

### High Findings
[Additional findings...]

### Positive Security Controls
- ✅ JWT tokens use strong signing (RS256)
- ✅ HTTPS enforced with TLS 1.2+
- ✅ Security headers present
- ✅ Input validation on all endpoints

### Remediation Roadmap

#### Immediate (Critical/High - 1 week)
1. Fix SSRF vulnerability (API7)
2. Implement BOLA checks on all object endpoints (API1)
3. Disable debug mode (API8)

#### Short-term (High/Medium - 1 month)
4. Implement comprehensive rate limiting (API4)
5. Add field-level authorization (API3)
6. Secure admin endpoints (API5)

#### Long-term (Medium/Low - 3 months)
7. Implement API inventory management (API9)
8. Add webhook signature validation (API10)
9. Conduct regular security testing

### Testing Artifacts
- [ ] OWASP ZAP scan results
- [ ] Burp Suite scan results
- [ ] Custom test scripts output
- [ ] API gateway configuration review

### References
- OWASP API Security Top 10 (2023)
- OWASP ASVS v5.0 - V13: API and Web Service
- OWASP Testing Guide: WSTG-APIT

OWASP References

  • OWASP API Security Top 10 (2023) — owasp.org/API-Security
  • OWASP ASVS v5.0 — V13: API and Web Service Verification
  • OWASP Testing Guide — WSTG-APIT: API Testing
  • OWASP Cheat Sheet Series:
    • REST Security
    • GraphQL Security
    • JWT Security
    • OAuth 2.0
    • SAML Security
  • OWASP ZAP — API scanning capabilities