Ratenbegrenzung

Die Ratenbegrenzung ist ein grundlegender Schutzmechanismus, um Ihr System vor API-Missbrauch, DoS-Angriffen und Scraping zu schützen.

Algorithmus-Vergleich

AlgorithmusMerkmaleProfisNachteile
Fixed WindowZählungen innerhalb eines festen ZeitfenstersEinfach zu implementieren, speichereffizientBerstungen treten an Fenstergrenzen auf
Sliding Window LogZeitstempel für DatensatzanfragenPräzise KontrolleHoher Speicherbedarf
Sliding Window CounterGewichteter Durchschnitt des vorherigen und des aktuellen FenstersGutes GleichgewichtZiemlich komplex
Token BucketVerbraucht Token für den ZugangErlaubt Bursts bei gleichzeitiger Einhaltung von GrenzwertenErfordert Parameterabstimmung
Leaky BucketBearbeitet Anfragen mit einer konstanten RateStabile AusgaberateSchlechtes Handling bei Bursts

Implementierung der Ratenbegrenzung in Express.js

JavaScript (Express)Mehrschichtige Ratenbegrenzung
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);

Antwort-Kopfzeilen

HTTP Response HeadersRFC 6585 / draft-ietf-httpapi-ratelimit-headers
RateLimit-Limit: 100
RateLimit-Remaining: 42
RateLimit-Reset: 1672531200
Retry-After: 30
Ratenbegrenzungstasten

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.

🔍 Eingabeüberprüfung

Alle API-Eingaben können bösartige Daten enthalten. Validieren Sie immer auf der Serverseite.

Grundsätze der Validierung

Allowlist-Ansatz

Definieren Sie eine Liste der zulässigen Werte. Sicherer als eine Denyliste (Blockliste). Kann neue Angriffsmuster verarbeiten.

Typ, Länge und Reichweite

Strenge Prüfung von Datentypen, maximaler Zeichenzahl und numerischen Ober- und Untergrenzen. Ein grundlegender Schutz gegen Pufferüberläufe.

Desinfizieren vs. Verwerfen

Die Zurückweisung ungültiger Eingaben ist sicherer als ihre "Korrektur". Sanitisierung kann zu unerwarteten Transformationen führen.

Validierung mit JSON-Schema

JavaScript (Express + Ajv)Schema-Validierung
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);

Häufige Angriffe und Validierungsgegenmaßnahmen

AngriffGegenmaßnahmeBeispiel
SQL-EinschleusungParametrisierte Abfragen, Verwendung eines ORMdb.query('SELECT * FROM users WHERE id = ?', [id])
NoSQL-EinschleusungTypenprüfung, Bereinigung von $-OperatorenSicherstellen, dass die Eingabe eine Zeichenkette ist (Objekte zurückweisen)
XSS (über API-Antwort)Content-Type angeben, Escape-AusgabeContent-Type: application/json
PfadüberquerungPfadseparatoren aus der Eingabe entfernenNormalisieren mit pfad.basename()
XXE (Externe XML-Entität)Auflösung externer Entitäten deaktivierenDeaktivieren in den XML-Parser-Einstellungen

🌐 CORS (Cross-Origin Resource Sharing)

CORS ist ein Mechanismus, der die Same-Origin-Politik des Browsers steuert. Eine Fehlkonfiguration kann zu ernsthaften Sicherheitsrisiken führen.

Gefährliche Konfiguration

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.

Sichere CORS-Konfiguration

JavaScript (Express)CORS-Konfiguration
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
}));

CORS-Checkliste

CORS-Header-Referenz

KopfzeileZweckEmpfohlener Wert
Access-Control-Allow-OriginErlaubte UrsprüngeExplizite Spezifikation des Bereichs
Access-Control-Allow-MethodsErlaubte HTTP-MethodenNur das erforderliche Minimum
Access-Control-Allow-HeadersErlaubte Anfrage-HeaderNur das erforderliche Minimum
Access-Control-Allow-CredentialsÜbertragung von Cookies zulassentrue (nur wenn Authentifizierung erforderlich ist)
Access-Control-Max-AgeDauer des Preflight-Caches in Sekunden86400 (24 Stunden)
Access-Control-Expose-HeadersVon JavaScript lesbare Antwort-HeaderNur notwendige Kopfzeilen wie RateLimit-*

🤖 AI / LLM API Rate Limiting

LLM APIs introduce new dimensions to rate limiting: token consumption, cost per request, and compute-intensive inference.

Traditional API vs. LLM API Rate Limiting

DimensionTraditional APILLM API
Cost per requestLow, predictableVariable, can be 100x+ (based on tokens)
Rate limit unitRequests per time windowTokens per minute (TPM) + Requests per minute (RPM)
Abuse patternScraping, brute force, DDoSPrompt injection, resource exhaustion, denial-of-wallet
Algorithm fitFixed/Sliding windowToken bucket (weighted by token count)

Token-Aware Rate Limiting (Python)

PythonToken-Based Rate Limiter
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}
OWASP References

Related: LLM10: Model Denial of Service, ASI04: Cascading Hallucination Attacks

🛡 Prompt Injection & AI Input Validation

Prompt injection is the #1 risk for LLM applications. Apply defense-in-depth with input validation, structural separation, and output verification.

Prompt Sanitization

Strip or escape special tokens, instruction-like patterns, and control characters from user input before including in prompts.

Structural Input Separation

Use delimiters, XML tags, or separate message roles to clearly isolate system instructions from user-provided content.

Output Verification

Validate LLM responses against expected schemas. Check for data leakage, instruction following, and malicious content before rendering.

Prompt Injection Attack Vectors & Mitigations

AngriffGegenmaßnahmeBeispiel
Direct Prompt InjectionInput sanitization + instruction/data separation"Ignore previous instructions and..."
Indirect Prompt InjectionSanitize RAG results + canary tokensMalicious instructions hidden in retrieved documents
Context StuffingToken limits + input truncationOverloading context window to push out system instructions
Parameter TamperingSchema validation + typed parametersManipulating temperature, max_tokens, or model parameters

Structured Input Validation (Python / Pydantic)

PythonPydantic Validation for LLM Requests
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)
OWASP References

Related: LLM01: Prompt Injection, LLM02: Insecure Output Handling, ASI02: Prompt Injection via Tool Results

🔒 Andere empfohlene Sicherheits-Header

HTTP Response HeadersEmpfohlene Einstellungen
# 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=()