TS
Thomas Schmitz

Freiberuflicher IT-Berater, Softwareentwickler & DevOps. Praxisnahe Artikel zu API-Design, Architektur und Cloud-Workflows.

Serie: API-Design · Teil 12 von 22

API-Design Teil 12: OWASP API Security

Die OWASP API Security Top 10 als Design-Checkliste: Von BOLA über Mass Assignment bis SSRF – und wie du sie verhinderst.

Praxisnah Checkliste

OWASP (Open Web Application Security Project) pflegt eine Liste der häufigsten API-Sicherheitsrisiken. Diese Top 10 sind keine theoretischen Probleme – sie sind die Angriffsvektoren, die in der Praxis am häufigsten zu Breaches führen.

Dieser Artikel geht jeden Punkt durch und zeigt, wie du diese Risiken schon im API-Design verhinderst – nicht erst in der Implementierung.

Zielbild

Nach diesem Artikel kannst du:

  • Die OWASP API Security Top 10 erklären und erkennen
  • Für jedes Risiko konkrete Gegenmaßnahmen benennen
  • Ein einfaches Threat Model für deine API erstellen
  • Abuse Cases identifizieren und dokumentieren
  • Input-Validierung und Size Limits richtig einsetzen

Kernfragen

Bevor du weiterliest, versuche diese Fragen für dein Projekt zu beantworten:

  1. Welche Risiken sind für eure API realistisch? Public vs. Internal?
  2. Wie prüft ihr Ressourcen-Zugriff? Nur Auth oder auch Ownership?
  3. Welche Felder dürfen Clients setzen? Allowlist oder Denylist?
  4. Wie begrenzt ihr Requests? Size Limits? Rate Limits?
  5. Verarbeitet ihr externe URLs? Webhooks? Imports?

OWASP API Security Top 10 (2023)

Die aktuelle Liste konzentriert sich auf API-spezifische Risiken:

# Risiko Kurzform
API1 Broken Object Level Authorization BOLA
API2 Broken Authentication Auth-Fehler
API3 Broken Object Property Level Authorization Property-Level AuthZ
API4 Unrestricted Resource Consumption Resource Exhaustion
API5 Broken Function Level Authorization Function-Level AuthZ
API6 Unrestricted Access to Sensitive Business Flows Business Logic Abuse
API7 Server Side Request Forgery SSRF
API8 Security Misconfiguration Config-Fehler
API9 Improper Inventory Management Shadow APIs
API10 Unsafe Consumption of APIs Third-Party Risk

API1: Broken Object Level Authorization (BOLA)

Das häufigste API-Sicherheitsproblem: Zugriff auf fremde Ressourcen durch ID-Manipulation.

Das Problem (API1: BOLA)

GET /v1/orders/order_12345
Authorization: Bearer token_of_user_a

Wenn order_12345 zu User B gehört, aber User A sie abrufen kann → BOLA.

Warum passiert das?

  • API prüft nur Hat User die Rolle?, nicht Gehört die Ressource dem User?
  • IDs sind vorhersagbar (sequentiell: 1, 2, 3...)
  • Keine Ownership-Prüfung implementiert

Gegenmaßnahmen (API1: BOLA)

# FALSCH
def get_order(order_id, user):
    return Order.find(order_id)  # Keine Prüfung!

# RICHTIG
def get_order(order_id, user):
    order = Order.find(order_id)
    if order.tenant_id != user.tenant_id:
        raise ForbiddenError()
    return order

Design-Regeln:

  • Jeder Ressourcen-Zugriff prüft Ownership
  • UUIDs statt sequentieller IDs (erschwert Enumeration)
  • 403 bei fehlender Berechtigung, 404 nur bei nicht existenter Ressource
  • Query-Level Filtering für List-Endpoints

API2: Broken Authentication

Schwächen in der Authentifizierung ermöglichen Identitätsdiebstahl.

Typische Probleme (API2: Broken Authentication)

Problem Beispiel
Schwache Passwörter Keine Komplexitätsanforderungen
Credential Stuffing Keine Rate Limits bei Login
Token Leakage Tokens in URLs, Logs
Fehlende Token-Validierung Signatur nicht geprüft
Lange Token-Lifetimes Access Tokens > 1 Stunde

Gegenmaßnahmen (API2: Broken Authentication)

Token-Validierung:

def validate_token(token):
    # 1. Signatur prüfen
    payload = jwt.decode(token, public_key, algorithms=["RS256"])

    # 2. Issuer prüfen
    if payload["iss"] != "https://auth.example.com":
        raise InvalidTokenError()

    # 3. Audience prüfen
    if payload["aud"] != "https://api.example.com":
        raise InvalidTokenError()

    # 4. Expiry prüfen (automatisch durch jwt.decode)

    return payload

Design-Regeln:

  • Kurze Token-Lifetimes (15 min für Browser)
  • Rate Limiting für Auth-Endpoints
  • Tokens nie in URLs oder Logs
  • Multi-Factor für sensitive Aktionen

API3: Broken Object Property Level Authorization

User kann Felder lesen oder schreiben, die er nicht sollte.

Das Problem (API3: Broken Object Property Level Authorization)

Excessive Data Exposure (Lesen):

API gibt mehr zurück als nötig:

{
  "id": "user_123",
  "email": "user@example.com",
  "password_hash": "REDACTED",
  "internal_notes": "VIP customer",
  "credit_card": "4111-XXXX-XXXX-1111"
}

Mass Assignment (Schreiben):

PATCH /v1/users/me
Content-Type: application/json

{
  "name": "Alice",
  "role": "admin",
  "is_verified": true
}

→ User setzt sich selbst zum Admin und verifiziert sich selbst!

Gegenmaßnahmen (API3: Broken Object Property Level Authorization)

Für Responses (Data Exposure):

# Explizite Serializer pro Kontext
class UserPublicSerializer:
    fields = ["id", "name", "avatar_url"]

class UserPrivateSerializer:
    fields = ["id", "name", "email", "created_at"]

class UserAdminSerializer:
    fields = ["id", "name", "email", "role", "internal_notes"]

Für Requests (Mass Assignment):

# Allowlist erlaubter Felder
ALLOWED_USER_UPDATES = ["name", "email", "avatar_url"]

def update_user(user_id, data, current_user):
    # Nur erlaubte Felder übernehmen
    safe_data = {k: v for k, v in data.items() if k in ALLOWED_USER_UPDATES}
    User.update(user_id, safe_data)

Design-Regeln:

  • Explizite Field-Allowlists für Input und Output
  • Verschiedene Serializer für verschiedene Rollen
  • Nie das DB-Model direkt als Response senden
  • Sensitive Felder nie im Response (auch nicht null)

API4: Unrestricted Resource Consumption

API verbraucht unbegrenzt Ressourcen und wird zum DoS-Vektor.

Typische Probleme (API4: Unrestricted Resource Consumption)

Problem Auswirkung
Keine Request Size Limits Riesige Payloads crashen Server
Keine Pagination Limits ?limit=1000000
Teure Queries ohne Limit Regex-Suche, Deep Joins
Unbegrenzte Batch-Operationen 10.000 Items in einem Request
Keine Rate Limits Brute Force, Scraping

Gegenmaßnahmen (API4: Unrestricted Resource Consumption)

Request Size Limits:

# NGINX
client_max_body_size 1m;
client_body_buffer_size 16k;
large_client_header_buffers 4 8k;

Pagination Limits:

MAX_LIMIT = 100
DEFAULT_LIMIT = 20

def list_orders(limit=DEFAULT_LIMIT):
    limit = min(limit, MAX_LIMIT)
    return Order.limit(limit).all()

Query Timeouts:

# Datenbank-Query mit Timeout
with db.timeout(5000):  # 5 Sekunden
    results = db.execute(query)

Design-Regeln:

  • Max Body Size: 1 MB (oder weniger)
  • Max Pagination Limit: 100
  • Query Timeouts: 5-30 Sekunden
  • Rate Limits pro User/IP/Tenant
  • Batch-Operationen begrenzen (max 100 Items)

API5: Broken Function Level Authorization

User kann Funktionen aufrufen, die für seine Rolle nicht vorgesehen sind.

Das Problem (API5: Broken Function Level Authorization)

# Normaler User findet Admin-Endpoint
DELETE /v1/admin/users/user_456
Authorization: Bearer normal_user_token

Wenn die API nur prüft Ist User authentifiziert? statt Hat User Admin-Rolle? → Problem.

Gegenmaßnahmen (API5: Broken Function Level Authorization)

# Decorator für Rollen-Prüfung
@require_role("admin")
def delete_user(user_id, current_user):
    User.delete(user_id)

# Oder Scope-basiert
@require_scope("users:admin")
def delete_user(user_id, current_user):
    User.delete(user_id)

Design-Regeln:

  • Jeder Endpoint hat explizite Rollen/Scopes
  • Admin-Endpoints in separatem Pfad (/admin/...)
  • Default: Deny (kein Zugriff ohne explizite Erlaubnis)
  • Regelmäßige Audits: Welche Endpoints sind ohne Auth erreichbar?

API6: Unrestricted Access to Sensitive Business Flows

Angreifer missbrauchen legitime Business-Funktionen im großen Stil.

Beispiele

Flow Missbrauch
Registrierung Massen-Account-Erstellung
Passwort-Reset E-Mail-Bombing
Gutschein-Einlösung Automatisiertes Ausprobieren
Ticket-Kauf Bot kauft alle Tickets
Kommentare Spam-Bots

Gegenmaßnahmen (API6: Sensitive Business Flows)

Rate Limiting pro Business-Flow:

# Striktere Limits für sensitive Flows
@rate_limit("registration", limit=5, period="1h", key="ip")
def register(email, password):
    ...

@rate_limit("password_reset", limit=3, period="1h", key="email")
def request_password_reset(email):
    ...

Bot-Schutz:

Maßnahme Use Case
CAPTCHA Registrierung, Login nach Fehlversuchen
Device Fingerprinting Erkennung von Automation
Behavioral Analysis Unnatürliche Muster erkennen
Honeypots Versteckte Felder, die Bots ausfüllen

Design-Regeln:

  • Sensitive Flows identifizieren
  • Strikte Rate Limits pro Flow
  • CAPTCHA/Challenge bei Verdacht
  • Monitoring auf Anomalien

API7: Server Side Request Forgery (SSRF)

API macht Requests zu URLs, die der Angreifer kontrolliert.

Das Problem (API7: SSRF)

POST /v1/webhooks
Content-Type: application/json

{
  "callback_url": "http://169.254.169.254/latest/meta-data/"
}

Server macht Request zu AWS Metadata Service → Credentials geleakt.

Gefährliche Patterns

Pattern Risiko
Webhook-URLs Angreifer kontrolliert Ziel
URL-Previews Link-Vorschau macht Request
PDF/Image-Generierung URL in Template
Import von URL CSV/JSON von externer URL

Gegenmaßnahmen (API7: SSRF)

URL-Validierung:

import ipaddress
import socket
from urllib.parse import urlparse

BLOCKED_HOSTS = {"localhost", "metadata.google"}
BLOCKED_NETWORKS = [
    ipaddress.ip_network("127.0.0.0/8"),
    ipaddress.ip_network("10.0.0.0/8"),
    ipaddress.ip_network("172.16.0.0/12"),
    ipaddress.ip_network("192.168.0.0/16"),
    ipaddress.ip_network("169.254.0.0/16"),  # Link-local, AWS Metadata
    ipaddress.ip_network("::1/128"),
    ipaddress.ip_network("fc00::/7"),        # Unique local addresses
    ipaddress.ip_network("fe80::/10"),       # IPv6 link-local
]

def resolve_ips(hostname):
    infos = socket.getaddrinfo(hostname, None, proto=socket.IPPROTO_TCP)
    return {ipaddress.ip_address(info[4][0]) for info in infos}

def is_blocked_ip(ip):
    if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved or ip.is_multicast:
        return True
    return any(ip in network for network in BLOCKED_NETWORKS)

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

    # Nur HTTPS erlauben
    if parsed.scheme != "https":
        raise ValueError("Only HTTPS allowed")

    if not parsed.hostname:
        raise ValueError("Missing hostname")

    # Blocked Hosts
    if parsed.hostname in BLOCKED_HOSTS:
        raise ValueError("Blocked host")

    # Alle IPs auflösen (IPv4 + IPv6) und prüfen
    ips_first = resolve_ips(parsed.hostname)
    if not ips_first:
        raise ValueError("Could not resolve hostname")
    if any(is_blocked_ip(ip) for ip in ips_first):
        raise ValueError("Blocked network")

    # DNS-Rebinding-Mitigierung: erneut auflösen und vergleichen
    ips_second = resolve_ips(parsed.hostname)
    if ips_first != ips_second:
        raise ValueError("DNS rebinding detected")

    return url

Design-Regeln:

  • URLs validieren (Schema, Host, IP-Bereich)
  • Nur HTTPS erlauben
  • Private IP-Bereiche blocken
  • DNS-Rebinding verhindern (IP nach Auflösung nochmal prüfen)
  • Allowlist für bekannte Domains (wenn möglich)

API8: Security Misconfiguration

Falsche Konfiguration öffnet Angriffsvektoren.

Typische Probleme (API8: Security Misconfiguration)

Problem Beispiel
Debug-Modus in Prod Stack Traces sichtbar
Default-Credentials admin/admin
Unnötige Features aktiv GraphQL Introspection
Fehlende Security Headers Kein HSTS, CSP
Veraltete Dependencies Bekannte CVEs
Offene CORS Access-Control-Allow-Origin: *

Gegenmaßnahmen (API8: Security Misconfiguration)

Checkliste:

  • [ ] DEBUG=false in Production
  • [ ] Keine Default-Credentials
  • [ ] Stack Traces nicht an Clients
  • [ ] GraphQL Introspection deaktiviert (Prod)
  • [ ] Security Headers gesetzt
  • [ ] CORS restriktiv konfiguriert
  • [ ] Dependencies aktuell (Dependabot)
  • [ ] Unnecessary Endpoints deaktiviert

Security Headers:

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: default-src 'self'

API9: Improper Inventory Management

Unbekannte oder veraltete API-Versionen werden vergessen und angreifbar.

Typische Probleme (API9: Improper Inventory Management)

Problem Risiko
Shadow APIs Unbekannte Endpoints ohne Security
Alte API-Versionen v1 noch aktiv, ungepatch
Test-Endpoints in Prod /debug, /test, /internal
Undokumentierte Endpoints Kein Review, kein Monitoring

Gegenmaßnahmen (API9: Improper Inventory Management)

API-Inventar führen:

Endpoint Version Status Owner Last Review
/v1/orders v1 Active Team A 2026-01
/v2/orders v2 Active Team A 2026-01
/v1/legacy v1 Deprecated Team B 2025-06

Design-Regeln:

  • Alle APIs dokumentieren (OpenAPI)
  • Alte Versionen deprecaten mit Sunset-Header
  • Test-Endpoints nur in Dev-Umgebungen
  • Regelmäßige Audits: Was läuft in Prod?
  • API-Gateway als Single Point of Entry

API10: Unsafe Consumption of APIs

Deine API vertraut Third-Party-APIs zu sehr.

Das Problem (API10: Unsafe Consumption)

# FALSCH: Third-Party-Response ungeprüft übernehmen
def get_user_data(user_id):
    response = third_party_api.get(f"/users/{user_id}")
    return response.json()  # Was wenn das Malware-JSON ist?

Risiken

Risiko Beispiel
Injection Third-Party liefert SQL/XSS
Data Exposure Mehr Daten als erwartet
Availability Third-Party down → API down
Schema Changes Breaking Changes ohne Warning

Gegenmaßnahmen (API10: Unsafe Consumption)

# RICHTIG: Validieren und transformieren
def get_user_data(user_id):
    response = third_party_api.get(f"/users/{user_id}")

    # 1. Schema validieren
    data = UserSchema.parse(response.json())

    # 2. Nur benötigte Felder extrahieren
    return {
        "id": data.id,
        "name": sanitize(data.name),
        "email": validate_email(data.email)
    }

Design-Regeln:

  • Third-Party-Responses validieren
  • Timeouts und Circuit Breaker
  • Nur benötigte Daten extrahieren
  • Input sanitizen (auch von vertrauenswürdigen APIs)
  • Fallback bei Third-Party-Ausfall

Input-Validierung: Die Grundlage

Viele OWASP-Risiken werden durch gute Input-Validierung verhindert.

Validation Layers

Size Limits

Verhindert Resource Exhaustion

Schema Validation

Verhindert Typ-Fehler, Injection

Business Validation

Verhindert Logic-Fehler

Authorization

Verhindert BOLA, Function-Level AuthZ

Input Size Limits

Input Empfohlenes Limit
Request Body 1 MB
URL Length 2048 Zeichen
Header Size 8 KB
Query String 2048 Zeichen
Array Items 100-1000
String Length Feldspezifisch
File Upload 10-100 MB

Schema-Validierung

from pydantic import BaseModel, Field, EmailStr

class CreateUserRequest(BaseModel):
    name: str = Field(min_length=1, max_length=100)
    email: EmailStr
    age: int = Field(ge=0, le=150)
    role: Literal["user", "admin"] = "user"

# Automatische Validierung
@app.post("/users")
def create_user(request: CreateUserRequest):
    # request ist bereits validiert
    ...

Artefakt: Threat Model + Abuse Cases

# API Threat Model

## Assets

| Asset         | Sensitivity | Impact if Compromised      |
|---------------|-------------|----------------------------|
| User Data     | High        | Privacy breach, GDPR fines |
| Payment Info  | Critical    | Financial loss, fraud      |
| API Keys      | High        | Unauthorized access        |
| Business Data | Medium      | Competitive disadvantage   |

## Threat Actors

| Actor         | Motivation            | Capability        |
|---------------|-----------------------|-------------------|
| Script Kiddie | Fun, chaos            | Low               |
| Competitor    | Business intelligence | Medium            |
| Criminal      | Financial gain        | High              |
| Insider       | Revenge, money        | High (has access) |

## Attack Vectors (OWASP API Top 10)

| Risk                       | Likelihood | Impact   | Mitigation               |
|----------------------------|------------|----------|--------------------------|
| API1: BOLA                 | High       | High     | Ownership checks, UUIDs  |
| API2: Broken Auth          | Medium     | Critical | Short tokens, MFA        |
| API3: Property AuthZ       | High       | Medium   | Field whitelists         |
| API4: Resource Consumption | High       | Medium   | Rate limits, size limits |
| API5: Function AuthZ       | Medium     | High     | Role checks, scopes      |
| API6: Business Flow Abuse  | Medium     | Medium   | Bot protection, CAPTCHA  |
| API7: SSRF                 | Low        | Critical | URL validation           |
| API8: Misconfiguration     | Medium     | High     | Security checklist       |
| API9: Inventory            | Medium     | Medium   | API registry             |
| API10: Unsafe Consumption  | Low        | Medium   | Input validation         |

## Abuse Cases

### AC-01: Account Takeover via BOLA

**Scenario:** Angreifer ändert Order-ID in URL und sieht fremde Orders.

**Steps:**

1. Angreifer hat eigene Order: `/orders/order_100`
2. Angreifer probiert: `/orders/order_99`, `/orders/order_101`
3. API gibt fremde Order-Daten zurück

**Mitigation:**

- Ownership-Check bei jedem Ressourcen-Zugriff
- UUIDs statt sequentieller IDs

### AC-02: Privilege Escalation via Mass Assignment

**Scenario:** User setzt sich selbst zum Admin.

**Steps:**

1. User schickt PATCH `/users/me` mit `{"role": "admin"}`
2. API übernimmt alle Felder ohne Prüfung
3. User ist jetzt Admin

**Mitigation:**

- Whitelist erlaubter Felder pro Endpoint
- `role` nicht in User-Update erlauben

### AC-03: DoS via Large Payload

**Scenario:** Angreifer schickt 1 GB JSON-Body.

**Steps:**

1. POST `/orders` mit 1 GB Body
2. Server versucht zu parsen
3. Out of Memory, Server crasht

**Mitigation:**

- Request Body Limit: 1 MB
- Streaming-Parser für große Payloads

### AC-04: SSRF via Webhook

**Scenario:** Angreifer liest AWS Credentials.

**Steps:**

1. POST `/webhooks` mit URL `http://169.254.169.254/...`
2. Server macht Request zu AWS Metadata
3. Angreifer bekommt Credentials

**Mitigation:**

- URL-Validierung (keine private IPs)
- Nur HTTPS erlauben
- Allowlist für Domains

### AC-05: Credential Stuffing

**Scenario:** Angreifer testet geleakte Passwörter.

**Steps:**

1. Angreifer hat Liste von E-Mail/Passwort-Paaren
2. Automatisierte Login-Versuche
3. Erfolgreiche Logins werden übernommen

**Mitigation:**

- Rate Limiting: 5 Versuche / 15 min / IP
- CAPTCHA nach 3 Fehlversuchen
- Breach-Detection (haveibeenpwned)

## Security Controls Summary

| Control           | Prevents                    |
|-------------------|-----------------------------|
| Ownership Checks  | BOLA (API1)                 |
| Token Validation  | Broken Auth (API2)          |
| Field Whitelists  | Property AuthZ (API3)       |
| Rate Limits       | Resource Consumption (API4) |
| Role/Scope Checks | Function AuthZ (API5)       |
| Bot Protection    | Business Flow Abuse (API6)  |
| URL Validation    | SSRF (API7)                 |
| Security Headers  | Misconfiguration (API8)     |
| API Registry      | Inventory (API9)            |
| Input Validation  | Unsafe Consumption (API10)  |

Checkliste

Bevor du zum nächsten Artikel gehst, prüfe:

  • [ ] BOLA: Ownership-Check bei jedem Ressourcen-Zugriff
  • [ ] Auth: Token-Validierung vollständig (Signatur, Issuer, Audience, Expiry)
  • [ ] Property AuthZ: Field-Whitelists für Input und Output
  • [ ] Resource Limits: Body Size, Pagination, Query Timeout
  • [ ] Function AuthZ: Explizite Rollen/Scopes pro Endpoint
  • [ ] Business Flows: Rate Limits für sensitive Aktionen
  • [ ] SSRF: URL-Validierung wenn externe URLs verarbeitet werden
  • [ ] Config: Debug aus, Headers gesetzt, Dependencies aktuell
  • [ ] Inventory: Alle APIs dokumentiert, alte Versionen deprecated
  • [ ] Third-Party: Input-Validierung auch für externe APIs
  • [ ] Threat Model existiert
  • [ ] Abuse Cases dokumentiert

Wie es weitergeht

Im nächsten Teil geht es um Rate Limiting und Quotas: Wie du Limits sinnvoll setzt, kommunizierst und durchsetzt – ohne legitime Clients zu blockieren.

Alle Teile der Serie: Serie: API-Design

Mehr Beiträge aus dem Blog.