secure-agent-playbook

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:

Inputs

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):

Key Questions:

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:

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:

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:

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:

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:

Attack Scenarios:

  1. Mass Assignment
    POST /api/users
    {
      "username": "newuser",
      "email": "user@example.com",
      "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": "john@example.com",
      "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": "test@test.com",
        "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:

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:

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:

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:

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:

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:

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:

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:

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