- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
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:
- Generiert automatisch CSRF-Token für alle Formulare
- Validiert Token für alle Anfragen, die den Zustand ändern
- 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
Double Submit Cookie Pattern
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
- Verwenden Sie HTTPS: CSRF-Token sollten immer über eine verschlüsselte Verbindung übertragen werden.
- Kurze Token-Lebensdauer: Setzen Sie die Token-Lebensdauer auf einen angemessenen Wert (z.B. 1-2 Stunden).
- Strikte SameSite-Cookie-Einstellungen: Verwenden Sie
SameSite=StrictoderSameSite=Laxfür Session-Cookies. - Token-Rotation: Rotieren Sie Token nach jeder Anfrage, die den Zustand ändert.
- 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.