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:
- Welche Risiken sind für eure API realistisch? Public vs. Internal?
- Wie prüft ihr Ressourcen-Zugriff? Nur Auth oder auch Ownership?
- Welche Felder dürfen Clients setzen? Allowlist oder Denylist?
- Wie begrenzt ihr Requests? Size Limits? Rate Limits?
- 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
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