Files
michaelschiemer/docs/components/security/csrf-protection.md
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

11 KiB

CSRF-Schutz

Dokumentationshinweis: Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung des CSRF-Schutzes korrekt dar.

Übersicht

Cross-Site Request Forgery (CSRF) ist eine Art von Angriff, bei dem ein Angreifer einen Benutzer dazu bringt, unbeabsichtigt Aktionen auf einer Website auszuführen, bei der er bereits authentifiziert ist. Das Framework bietet einen robusten CSRF-Schutzmechanismus, der solche Angriffe verhindert, indem es einzigartige Token generiert und validiert.

Hauptkomponenten

CsrfToken

Die CsrfToken-Klasse ist ein Value Object, das einen CSRF-Token repräsentiert:

use App\Framework\Security\CsrfToken;

// Token aus einem String erstellen
$token = CsrfToken::fromString($tokenString);

// Token-Wert als String abrufen
$tokenString = $token->toString();
// oder
$tokenString = (string)$token;

Die Klasse stellt sicher, dass CSRF-Token immer gültige 64-Zeichen lange hexadezimale Strings sind und bietet Typsicherheit für CSRF-Token-Operationen.

CsrfTokenGenerator

Die CsrfTokenGenerator-Klasse ist verantwortlich für die Generierung neuer CSRF-Token:

use App\Framework\Security\CsrfTokenGenerator;

// Generator initialisieren
$generator = new CsrfTokenGenerator($randomGenerator);

// Neues Token generieren
$token = $generator->generate();

Die Klasse verwendet einen kryptografisch sicheren Zufallszahlengenerator, um Token zu erzeugen, die nicht vorhersehbar sind.

CsrfTokenData

Die CsrfTokenData-Klasse erweitert das einfache Token um zusätzliche Metadaten:

use App\Framework\Security\CsrfTokenData;

// Token-Daten mit Metadaten erstellen
$tokenData = new CsrfTokenData(
    $token,
    $clientIp,
    $targetPath,
    $expiresAt
);

// Prüfen, ob das Token abgelaufen ist
if ($tokenData->isExpired($clock)) {
    // Token ist abgelaufen
}

// Prüfen, ob das Token bereits verwendet wurde
if ($tokenData->isUsed()) {
    // Token wurde bereits verwendet
}

Diese Klasse ermöglicht erweiterte Sicherheitsfunktionen wie Token-Ablauf, Verwendungsverfolgung und Bindung an bestimmte Pfade oder IP-Adressen.

Verwendung

Token generieren und in Formularen verwenden

// In einem Controller
public function showForm(): Response
{
    // Token generieren
    $csrfToken = $this->csrfTokenGenerator->generate();
    
    // Token in der Session speichern
    $this->session->set('csrf_token', $csrfToken->toString());
    
    // Token im Formular einbinden
    return $this->render('form.twig', [
        'csrf_token' => $csrfToken->toString(),
    ]);
}

In der Formularvorlage:

<form method="post" action="/submit">
    <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
    <!-- Weitere Formularfelder -->
    <button type="submit">Absenden</button>
</form>

Token validieren

// In einem Controller
public function handleForm(Request $request): Response
{
    // Gespeichertes Token abrufen
    $storedToken = CsrfToken::fromString($this->session->get('csrf_token'));
    
    // Übermitteltes Token abrufen
    $submittedToken = $request->getPostParam('csrf_token');
    
    // Token vergleichen (zeitsichere Vergleichsmethode)
    if (!$storedToken->equalsString($submittedToken)) {
        throw new SecurityException('Ungültiges CSRF-Token');
    }
    
    // Token ist gültig, Formular verarbeiten
    // ...
}

Erweiterte Token-Validierung mit Metadaten

// In einem Controller
public function showForm(): Response
{
    // Token mit Metadaten generieren
    $token = $this->csrfTokenGenerator->generate();
    $tokenData = new CsrfTokenData(
        $token,
        $this->request->getClientIp(),
        $this->request->path,
        new \DateTimeImmutable('+1 hour')
    );
    
    // Token-Daten in der Session speichern
    $this->session->set('csrf_token_data', $tokenData->toArray());
    
    // Token im Formular einbinden
    return $this->render('form.twig', [
        'csrf_token' => $token->toString(),
    ]);
}

public function handleForm(Request $request): Response
{
    // Token-Daten aus der Session abrufen
    $tokenDataArray = $this->session->get('csrf_token_data');
    $tokenData = CsrfTokenData::fromArray($tokenDataArray);
    
    // Übermitteltes Token abrufen
    $submittedToken = $request->getPostParam('csrf_token');
    
    // Umfassende Validierung durchführen
    if ($tokenData->isExpired($this->clock)) {
        throw new SecurityException('CSRF-Token ist abgelaufen');
    }
    
    if ($tokenData->isUsed()) {
        throw new SecurityException('CSRF-Token wurde bereits verwendet');
    }
    
    if ($tokenData->clientIp !== $request->getClientIp()) {
        throw new SecurityException('CSRF-Token ist an eine andere IP-Adresse gebunden');
    }
    
    if (!$tokenData->token->equalsString($submittedToken)) {
        throw new SecurityException('Ungültiges CSRF-Token');
    }
    
    // Token als verwendet markieren
    $tokenData->markAsUsed();
    $this->session->set('csrf_token_data', $tokenData->toArray());
    
    // Token ist gültig, Formular verarbeiten
    // ...
}

Integration mit dem Framework

CSRF-Middleware

Das Framework bietet eine CsrfProtectionMiddleware, die automatisch CSRF-Schutz für alle POST, PUT, DELETE und PATCH-Anfragen implementiert:

// In der Bootstrap-Datei oder Router-Konfiguration
$app->addMiddleware(CsrfProtectionMiddleware::class);

Die Middleware:

  1. Generiert automatisch CSRF-Token für alle Formulare
  2. Validiert Token für alle Anfragen, die den Zustand ändern
  3. Wirft eine Exception oder gibt eine Fehlerantwort zurück, wenn die Validierung fehlschlägt

Konfiguration

Die CSRF-Schutzfunktionen können in der Konfigurationsdatei angepasst werden:

// config/security.php
return [
    'csrf' => [
        'enabled' => true,
        'token_lifetime' => 3600, // Sekunden
        'exclude_routes' => [
            '/api/webhook',
            '/api/external/callback',
        ],
        'exclude_patterns' => [
            '#^/api/public/#', // Regulärer Ausdruck für auszuschließende Routen
        ],
        'header_name' => 'X-CSRF-Token', // Name des HTTP-Headers für API-Anfragen
        'parameter_name' => 'csrf_token', // Name des Formularfelds
        'strict_mode' => true, // Strikte Validierung (IP-Bindung, Einmalverwendung)
    ],
];

Template-Integration

Das Framework bietet eine einfache Möglichkeit, CSRF-Token in Templates einzubinden:

// In einem Controller
public function showForm(): Response
{
    return $this->render('form.twig', [
        'csrf_token' => $this->csrfProtection->getToken()->toString(),
    ]);
}

Mit der Template-Funktion:

{# In einer Twig-Vorlage #}
<form method="post" action="/submit">
    {{ csrf_field() }}
    <!-- Weitere Formularfelder -->
    <button type="submit">Absenden</button>
</form>

Die csrf_field()-Funktion generiert automatisch ein verstecktes Formularfeld mit dem aktuellen CSRF-Token.

API-Integration

Für API-Anfragen kann das CSRF-Token über einen HTTP-Header übermittelt werden:

// JavaScript-Beispiel
fetch('/api/data', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
    },
    body: JSON.stringify(data)
});

In der HTML-Vorlage:

<meta name="csrf-token" content="{{ csrf_token }}">

Erweiterte Funktionen

Das Framework unterstützt das Double Submit Cookie Pattern für zusätzliche Sicherheit:

// In einem Controller
public function showForm(): Response
{
    $token = $this->csrfTokenGenerator->generate();
    
    // Token in der Session speichern
    $this->session->set('csrf_token', $token->toString());
    
    // Token auch als Cookie setzen
    $this->response->setCookie('csrf_token', $token->toString(), [
        'httponly' => false, // JavaScript muss darauf zugreifen können
        'samesite' => 'Strict',
        'secure' => true,
        'path' => '/',
    ]);
    
    return $this->render('form.twig', [
        'csrf_token' => $token->toString(),
    ]);
}

Token-Rotation

Für zusätzliche Sicherheit können CSRF-Token nach jeder Anfrage rotiert werden:

// In einem Controller
public function handleForm(Request $request): Response
{
    // Token validieren
    // ...
    
    // Neues Token generieren
    $newToken = $this->csrfTokenGenerator->generate();
    $this->session->set('csrf_token', $newToken->toString());
    
    // Antwort mit neuem Token
    return $this->render('success.twig', [
        'new_csrf_token' => $newToken->toString(),
    ]);
}

Synchronizer Token Pattern

Das Framework implementiert das Synchronizer Token Pattern, bei dem für jede Sitzung ein eindeutiges Token generiert wird:

// In der Session-Initialisierung
public function initializeSession(): void
{
    if (!$this->session->has('csrf_token')) {
        $token = $this->csrfTokenGenerator->generate();
        $this->session->set('csrf_token', $token->toString());
    }
}

Fehlerbehebung

Häufige Probleme

Token-Validierung schlägt fehl

Mögliche Ursachen:

  • Das Token ist abgelaufen
  • Das Token wurde bereits verwendet
  • Das Token wurde nicht korrekt übermittelt
  • Die Session ist abgelaufen oder wurde zurückgesetzt

Lösung:

  • Überprüfen Sie die Session-Konfiguration
  • Stellen Sie sicher, dass das Token korrekt im Formular eingebunden ist
  • Erhöhen Sie die Token-Lebensdauer in der Konfiguration

CSRF-Schutz für bestimmte Routen deaktivieren

Wenn Sie den CSRF-Schutz für bestimmte Routen deaktivieren müssen (z.B. für Webhook-Callbacks):

// config/security.php
return [
    'csrf' => [
        'exclude_routes' => [
            '/api/webhook',
            '/api/external/callback',
        ],
    ],
];

AJAX-Anfragen mit CSRF-Schutz

Für AJAX-Anfragen müssen Sie das CSRF-Token im Header oder als Formularfeld übermitteln:

// JavaScript mit jQuery
$.ajaxSetup({
    headers: {
        'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
    }
});

// Oder mit Fetch API
fetch('/api/data', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
    },
    body: JSON.stringify(data)
});

Sicherheitsüberlegungen

Best Practices

  1. Verwenden Sie HTTPS: CSRF-Token sollten immer über eine verschlüsselte Verbindung übertragen werden.
  2. Kurze Token-Lebensdauer: Setzen Sie die Token-Lebensdauer auf einen angemessenen Wert (z.B. 1-2 Stunden).
  3. Strikte SameSite-Cookie-Einstellungen: Verwenden Sie SameSite=Strict oder SameSite=Lax für Session-Cookies.
  4. Token-Rotation: Rotieren Sie Token nach jeder Anfrage, die den Zustand ändert.
  5. Validieren Sie alle Anfragen: Stellen Sie sicher, dass alle Anfragen, die den Zustand ändern, ein gültiges CSRF-Token erfordern.

Bekannte Einschränkungen

  • CSRF-Schutz funktioniert nicht für Benutzer, die keine Cookies akzeptieren.
  • Bei sehr langen Sitzungen kann die Sicherheit des CSRF-Tokens beeinträchtigt sein.
  • CSRF-Schutz kann mit bestimmten Caching-Strategien in Konflikt geraten.

Weiterführende Informationen