Erläutert defensive Implementierungsmuster zum Schutz Ihrer API vor Missbrauch.
Die Ratenbegrenzung ist ein grundlegender Schutzmechanismus, um Ihr System vor API-Missbrauch, DoS-Angriffen und Scraping zu schützen.
| Algorithmus | Merkmale | Profis | Nachteile |
|---|---|---|---|
| Fixed Window | Zählungen innerhalb eines festen Zeitfensters | Einfach zu implementieren, speichereffizient | Berstungen treten an Fenstergrenzen auf |
| Sliding Window Log | Zeitstempel für Datensatzanfragen | Präzise Kontrolle | Hoher Speicherbedarf |
| Sliding Window Counter | Gewichteter Durchschnitt des vorherigen und des aktuellen Fensters | Gutes Gleichgewicht | Ziemlich komplex |
| Token Bucket | Verbraucht Token für den Zugang | Erlaubt Bursts bei gleichzeitiger Einhaltung von Grenzwerten | Erfordert Parameterabstimmung |
| Leaky Bucket | Bearbeitet Anfragen mit einer konstanten Rate | Stabile Ausgaberate | Schlechtes Handling bei Bursts |
const rateLimit = require('express-rate-limit'); const RedisStore = require('rate-limit-redis'); // Globale Grenze: gilt für alle APIs const globalLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 Minuten max: 100, standardHeaders: true, // Gibt RateLimit-*-Kopfzeilen zurück legacyHeaders: false, message: { error: 'Too many requests, please try again later.' }, }); // Für Authentifizierungsendpunkte: strengere Grenzen const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5, // Login-Versuche auf 5 pro 15 Minuten begrenzt skipSuccessfulRequests: true, // Erfolgreiche Anfragen werden nicht gezählt }); // Für verteilte Umgebungen: Redis-Backend const distributedLimiter = rateLimit({ store: new RedisStore({ sendCommand: (...args) => redisClient.sendCommand(args) }), windowMs: 60 * 1000, max: 30, }); app.use(globalLimiter); app.use('/api/auth', authLimiter);
RateLimit-Limit: 100 RateLimit-Remaining: 42 RateLimit-Reset: 1672531200 Retry-After: 30
Durch die Anwendung von Ratenbeschränkungen unter Verwendung zusammengesetzter Schlüssel wie API-Schlüssel, Benutzer-ID oder Endpunkt zusätzlich zur IP-Adresse können Sie die Auswirkungen auf legitime Benutzer minimieren.
Alle API-Eingaben können bösartige Daten enthalten. Validieren Sie immer auf der Serverseite.
Definieren Sie eine Liste der zulässigen Werte. Sicherer als eine Denyliste (Blockliste). Kann neue Angriffsmuster verarbeiten.
Strenge Prüfung von Datentypen, maximaler Zeichenzahl und numerischen Ober- und Untergrenzen. Ein grundlegender Schutz gegen Pufferüberläufe.
Die Zurückweisung ungültiger Eingaben ist sicherer als ihre "Korrektur". Sanitisierung kann zu unerwarteten Transformationen führen.
const Ajv = require('ajv'); const addFormats = require('ajv-formats'); const ajv = new Ajv({ allErrors: true, removeAdditional: true }); addFormats(ajv); // Schema für die Benutzererstellungs-API const createUserSchema = { type: 'object', required: ['name', 'email'], additionalProperties: false, properties: { name: { type: 'string', minLength: 1, maxLength: 100, pattern: '^[a-zA-Z0-9\\s\\-]+$', // Beschränkung auf zulässige Zeichen }, email: { type: 'string', format: 'email', maxLength: 254, }, age: { type: 'integer', minimum: 0, maximum: 150, }, }, }; // Validierungs-Middleware function validateBody(schema) { const validate = ajv.compile(schema); return (req, res, next) => { if (!validate(req.body)) { return res.status(400).json({ error: 'Validation failed', details: validate.errors, }); } next(); }; } app.post('/api/users', validateBody(createUserSchema), createUser);
| Angriff | Gegenmaßnahme | Beispiel |
|---|---|---|
| SQL-Einschleusung | Parametrisierte Abfragen, Verwendung eines ORM | db.query('SELECT * FROM users WHERE id = ?', [id]) |
| NoSQL-Einschleusung | Typenprüfung, Bereinigung von $-Operatoren | Sicherstellen, dass die Eingabe eine Zeichenkette ist (Objekte zurückweisen) |
| XSS (über API-Antwort) | Content-Type angeben, Escape-Ausgabe | Content-Type: application/json |
| Pfadüberquerung | Pfadseparatoren aus der Eingabe entfernen | Normalisieren mit pfad.basename() |
| XXE (Externe XML-Entität) | Auflösung externer Entitäten deaktivieren | Deaktivieren in den XML-Parser-Einstellungen |
CORS ist ein Mechanismus, der die Same-Origin-Politik des Browsers steuert. Eine Fehlkonfiguration kann zu ernsthaften Sicherheitsrisiken führen.
Access-Control-Allow-Origin: * und Access-Control-Allow-Credentials: true können nicht zusammen verwendet werden. Schränken Sie die Verwendung von Platzhaltern nur auf öffentliche APIs ein.
const cors = require('cors'); // Explizite Angabe zulässiger Ursprünge const allowedOrigins = [ 'https://app.example.com', 'https://admin.example.com', ]; app.use(cors({ origin(origin, callback) { // Kommunikation von Server zu Server zulassen (kein Ursprung) if (!origin || allowedOrigins.includes(origin)) { callback(null, true); } else { callback(new Error('Not allowed by CORS')); } }, methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key'], credentials: true, maxAge: 86400, // Preflight-Zwischenspeicher: 24 Stunden }));
| Kopfzeile | Zweck | Empfohlener Wert |
|---|---|---|
Access-Control-Allow-Origin | Erlaubte Ursprünge | Explizite Spezifikation des Bereichs |
Access-Control-Allow-Methods | Erlaubte HTTP-Methoden | Nur das erforderliche Minimum |
Access-Control-Allow-Headers | Erlaubte Anfrage-Header | Nur das erforderliche Minimum |
Access-Control-Allow-Credentials | Übertragung von Cookies zulassen | true (nur wenn Authentifizierung erforderlich ist) |
Access-Control-Max-Age | Dauer des Preflight-Caches in Sekunden | 86400 (24 Stunden) |
Access-Control-Expose-Headers | Von JavaScript lesbare Antwort-Header | Nur notwendige Kopfzeilen wie RateLimit-* |
LLM APIs introduce new dimensions to rate limiting: token consumption, cost per request, and compute-intensive inference.
| Dimension | Traditional API | LLM API |
|---|---|---|
| Cost per request | Low, predictable | Variable, can be 100x+ (based on tokens) |
| Rate limit unit | Requests per time window | Tokens per minute (TPM) + Requests per minute (RPM) |
| Abuse pattern | Scraping, brute force, DDoS | Prompt injection, resource exhaustion, denial-of-wallet |
| Algorithm fit | Fixed/Sliding window | Token bucket (weighted by token count) |
import tiktoken import time from collections import defaultdict class TokenRateLimiter: """Rate limiter that counts tokens, not just requests.""" def __init__(self, tokens_per_minute=100_000, requests_per_minute=60): self.tpm_limit = tokens_per_minute self.rpm_limit = requests_per_minute self.usage = defaultdict(lambda: {"tokens": [], "requests": []}) self.encoder = tiktoken.encoding_for_model("gpt-4") def count_tokens(self, text: str) -> int: return len(self.encoder.encode(text)) def check_limit(self, user_id: str, prompt: str) -> dict: now = time.time() window = now - 60 # 1-minute sliding window user = self.usage[user_id] # Clean up expired entries user["tokens"] = [(t, c) for t, c in user["tokens"] if t > window] user["requests"] = [t for t in user["requests"] if t > window] # Check RPM if len(user["requests"]) >= self.rpm_limit: return {"allowed": False, "reason": "RPM limit exceeded"} # Check TPM token_count = self.count_tokens(prompt) used_tokens = sum(c for _, c in user["tokens"]) if used_tokens + token_count > self.tpm_limit: return {"allowed": False, "reason": "TPM limit exceeded"} # Record usage user["tokens"].append((now, token_count)) user["requests"].append(now) return {"allowed": True, "tokens_used": token_count}
Related: LLM10: Model Denial of Service, ASI04: Cascading Hallucination Attacks
Prompt injection is the #1 risk for LLM applications. Apply defense-in-depth with input validation, structural separation, and output verification.
Strip or escape special tokens, instruction-like patterns, and control characters from user input before including in prompts.
Use delimiters, XML tags, or separate message roles to clearly isolate system instructions from user-provided content.
Validate LLM responses against expected schemas. Check for data leakage, instruction following, and malicious content before rendering.
| Angriff | Gegenmaßnahme | Beispiel |
|---|---|---|
| Direct Prompt Injection | Input sanitization + instruction/data separation | "Ignore previous instructions and..." |
| Indirect Prompt Injection | Sanitize RAG results + canary tokens | Malicious instructions hidden in retrieved documents |
| Context Stuffing | Token limits + input truncation | Overloading context window to push out system instructions |
| Parameter Tampering | Schema validation + typed parameters | Manipulating temperature, max_tokens, or model parameters |
from pydantic import BaseModel, Field, field_validator import re class LLMRequest(BaseModel): """Validated LLM request with prompt injection defenses.""" user_message: str = Field(..., max_length=4000) max_tokens: int = Field(default=1000, ge=1, le=4096) temperature: float = Field(default=0.7, ge=0.0, le=2.0) @field_validator("user_message") @classmethod def sanitize_prompt(cls, v: str) -> str: # Block known injection patterns patterns = [ r"(?i)ignore\s+(previous|above|all)\s+(instructions?|prompts?)", r"(?i)you\s+are\s+now\s+", r"(?i)system\s*:\s*", r"(?i)\[INST\]|\[\/INST\]|<\|im_start\|>", ] for pattern in patterns: if re.search(pattern, v): raise ValueError("Input contains disallowed patterns") return v # Usage with FastAPI @app.post("/api/chat") async def chat(request: LLMRequest): # Separate system instructions from user input messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": f"<user_input>{request.user_message}</user_input>"}, ] return await call_llm(messages, request.max_tokens, request.temperature)
Related: LLM01: Prompt Injection, LLM02: Insecure Output Handling, ASI02: Prompt Injection via Tool Results
# Automatische Erkennung des Inhaltstyps deaktivieren X-Content-Type-Options: nosniff # Iframe-Einbettung verhindern X-Frame-Options: DENY # HTTPS erzwingen Strict-Transport-Security: max-age=31536000; includeSubDomains # CSP: Da APIs nur JSON zurückgeben, blockieren Sie die gesamte Skriptausführung Content-Security-Policy: default-src 'none'; frame-ancestors 'none' # Referrer-Informationen einschränken Referrer-Policy: no-referrer # Browser-Funktionen einschränken Permissions-Policy: geolocation=(), camera=(), microphone=()