HIGH

API Security: Understanding and Preventing the OWASP API Top 10

A comprehensive guide to the most critical API security risks, with real-world examples and practical mitigation strategies.

Why API Security Matters

APIs are the backbone of modern applications. They power mobile apps, microservices, IoT devices, and third-party integrations. But their ubiquity makes them a prime target:

  • 83% of web traffic is now API traffic
  • APIs are 3x more targeted than traditional web applications
  • Average cost of an API breach: $6.1 million

OWASP API Security Top 10 (2023)

API1: Broken Object Level Authorization (BOLA)

The most common and dangerous API vulnerability. Occurs when APIs don’t verify that the user has permission to access a specific object.

Vulnerable Example:

GET /api/v1/users/1234/orders
Authorization: Bearer <token_for_user_5678>

The API returns user 1234’s orders even though the request came from user 5678.

Exploitation:

# Attacker iterates through user IDs
for user_id in range(1, 10000):
    response = requests.get(
        f"https://api.example.com/users/{user_id}/data",
        headers={"Authorization": f"Bearer {stolen_token}"}
    )
    if response.status_code == 200:
        print(f"Leaked data for user {user_id}")

Mitigation:

# Always verify object ownership
def get_user_orders(request, user_id):
    # Check if requesting user owns this resource
    if request.user.id != user_id and not request.user.is_admin:
        raise PermissionDenied("Cannot access other user's orders")

    return Order.objects.filter(user_id=user_id)

API2: Broken Authentication

Weak authentication mechanisms that allow attackers to compromise authentication tokens or exploit implementation flaws.

Common Issues:

  • No rate limiting on login endpoints
  • Weak password requirements
  • Sensitive data in JWT payloads
  • Long-lived or non-expiring tokens
  • Tokens in URLs

Vulnerable JWT:

// Never store sensitive data in JWT payload
{
  "user_id": 123,
  "email": "user@example.com",
  "password_hash": "abc123...",  // NEVER DO THIS
  "credit_card": "4111..."       // NEVER DO THIS
}

Secure Implementation:

# Secure token configuration
JWT_CONFIG = {
    "algorithm": "RS256",        # Use asymmetric encryption
    "access_token_lifetime": timedelta(minutes=15),
    "refresh_token_lifetime": timedelta(days=7),
    "rotate_refresh_tokens": True,
    "blacklist_after_rotation": True,
}

# Rate limiting on auth endpoints
@ratelimit(key='ip', rate='5/m', block=True)
def login(request):
    # Authentication logic
    pass

API3: Broken Object Property Level Authorization

API exposes object properties that users should not be able to see or modify.

Vulnerable Response:

{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com",
  "salary": 150000,        // User shouldn't see this
  "ssn": "123-45-6789",    // User shouldn't see this
  "is_admin": false        // User shouldn't modify this
}

Vulnerable Update:

PATCH /api/users/123
Content-Type: application/json

{
  "name": "John Doe",
  "is_admin": true    // Mass assignment vulnerability
}

Mitigation:

# Define explicit serializers for different contexts
class UserPublicSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'name', 'email']  # Only public fields

class UserPrivateSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'name', 'email', 'salary', 'ssn']
        read_only_fields = ['id', 'is_admin']  # Prevent modification

API4: Unrestricted Resource Consumption

APIs that don’t limit how much resource a user can consume, leading to DoS or cost attacks.

Vulnerable Scenarios:

  • No pagination limits
  • Unlimited file upload sizes
  • Complex queries without timeouts
  • No rate limiting

Exploitation:

GET /api/users?page_size=1000000

Mitigation:

# Enforce limits
MAX_PAGE_SIZE = 100
MAX_UPLOAD_SIZE = 10 * 1024 * 1024  # 10MB
RATE_LIMIT = "100/hour"

@api_view(['GET'])
@throttle_classes([UserRateThrottle])
def list_users(request):
    page_size = min(
        int(request.GET.get('page_size', 20)),
        MAX_PAGE_SIZE
    )
    # Paginated response

API5: Broken Function Level Authorization

Users can access administrative functions they shouldn’t have access to.

Vulnerable:

# Regular user can access admin endpoint
GET /api/admin/users
DELETE /api/admin/users/123

Mitigation:

# Decorator-based authorization
from functools import wraps

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

@admin_required
def delete_user(request, user_id):
    # Admin-only logic
    pass

API6: Unrestricted Access to Sensitive Business Flows

APIs allow automation of business flows that should have human verification or limits.

Examples:

  • Automated ticket scalping
  • Mass account creation
  • Referral bonus abuse
  • Coupon/discount abuse

Mitigation:

# Implement business logic controls
class PurchaseView(APIView):
    def post(self, request):
        user = request.user

        # Check purchase velocity
        recent_purchases = Purchase.objects.filter(
            user=user,
            created_at__gte=timezone.now() - timedelta(hours=1)
        ).count()

        if recent_purchases >= 5:
            return Response(
                {"error": "Purchase limit reached"},
                status=429
            )

        # Check quantity limits
        if request.data.get('quantity', 1) > 4:
            return Response(
                {"error": "Maximum 4 items per purchase"},
                status=400
            )

        # Implement CAPTCHA for suspicious activity
        if user.risk_score > 0.7:
            if not verify_captcha(request.data.get('captcha')):
                return Response(
                    {"error": "CAPTCHA verification required"},
                    status=400
                )

API7: Server-Side Request Forgery (SSRF)

API fetches remote resources without validating user-supplied URLs.

Vulnerable:

# User can make server fetch any URL
@app.route('/api/fetch-image')
def fetch_image():
    url = request.args.get('url')
    response = requests.get(url)  # SSRF vulnerability
    return response.content

Exploitation:

# Access internal metadata service (cloud)
GET /api/fetch-image?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/

# Scan internal network
GET /api/fetch-image?url=http://192.168.1.1:22/

Mitigation:

from urllib.parse import urlparse
import ipaddress

ALLOWED_DOMAINS = ['images.example.com', 'cdn.example.com']

def is_safe_url(url):
    parsed = urlparse(url)

    # Check scheme
    if parsed.scheme not in ['http', 'https']:
        return False

    # Check against allowlist
    if parsed.hostname not in ALLOWED_DOMAINS:
        return False

    # Check for internal IPs
    try:
        ip = ipaddress.ip_address(parsed.hostname)
        if ip.is_private or ip.is_loopback:
            return False
    except ValueError:
        pass  # hostname, not IP

    return True

@app.route('/api/fetch-image')
def fetch_image():
    url = request.args.get('url')
    if not is_safe_url(url):
        return "Invalid URL", 400
    # Safe to fetch

API8: Security Misconfiguration

Insecure default configurations, incomplete configurations, or misconfigured HTTP headers.

Common Issues:

Misconfigurations:
  - Missing security headers (CSP, HSTS, X-Frame-Options)
  - CORS allowing all origins (*)
  - Verbose error messages exposing stack traces
  - Unnecessary HTTP methods enabled
  - Default credentials not changed
  - Debug mode in production
  - Outdated TLS versions

Secure Configuration:

# Django security settings
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
X_FRAME_OPTIONS = 'DENY'

# CORS configuration
CORS_ALLOWED_ORIGINS = [
    "https://app.example.com",
    "https://www.example.com",
]
CORS_ALLOW_CREDENTIALS = True

# Disable debug in production
DEBUG = False

API9: Improper Inventory Management

Organizations don’t know all their APIs, leading to unpatched, unmonitored shadow APIs.

Common Issues:

  • Old API versions still accessible
  • Undocumented endpoints
  • Development/staging APIs exposed
  • Third-party API integrations forgotten

Solution:

API Inventory Checklist:
  Discovery:
    - Regular API discovery scans
    - Traffic analysis for unknown endpoints
    - Code repository scanning

  Documentation:
    - OpenAPI/Swagger specs for all APIs
    - Version deprecation policies
    - Clear ownership assignment

  Lifecycle:
    - Sunset dates for old versions
    - Forced migration paths
    - Monitoring for deprecated endpoint usage

API10: Unsafe Consumption of APIs

Trusting third-party APIs without proper validation.

Vulnerable:

# Blindly trusting third-party response
def get_user_data():
    response = requests.get("https://third-party-api.com/users/123")
    data = response.json()

    # Directly using in SQL query - SQL injection!
    query = f"INSERT INTO users (name) VALUES ('{data['name']}')"

Mitigation:

import bleach
from jsonschema import validate

# Define expected schema
USER_SCHEMA = {
    "type": "object",
    "properties": {
        "name": {"type": "string", "maxLength": 100},
        "email": {"type": "string", "format": "email"}
    },
    "required": ["name", "email"]
}

def get_user_data():
    response = requests.get(
        "https://third-party-api.com/users/123",
        timeout=5  # Always set timeouts
    )

    data = response.json()

    # Validate response structure
    validate(instance=data, schema=USER_SCHEMA)

    # Sanitize data
    sanitized_name = bleach.clean(data['name'])

    # Use parameterized queries
    cursor.execute(
        "INSERT INTO users (name) VALUES (%s)",
        [sanitized_name]
    )

API Security Checklist

## Pre-Production Checklist

### Authentication & Authorization
- [ ] MFA enabled for sensitive operations
- [ ] JWT using RS256, short expiration
- [ ] Object-level authorization on all endpoints
- [ ] Function-level authorization enforced

### Input Validation
- [ ] All input validated against schema
- [ ] File uploads validated and sandboxed
- [ ] SQL parameterized queries only
- [ ] Output encoding implemented

### Rate Limiting
- [ ] Per-user rate limits
- [ ] Per-IP rate limits
- [ ] Endpoint-specific limits for sensitive operations
- [ ] Pagination limits enforced

### Monitoring
- [ ] All API calls logged
- [ ] Anomaly detection enabled
- [ ] Failed auth attempts alerted
- [ ] Response time monitoring

References


APIs are the front door to your data. Make sure you’re checking who’s knocking.