Some checks failed
Deploy Application / deploy (push) Has been cancelled
366 lines
8.9 KiB
Markdown
366 lines
8.9 KiB
Markdown
# CSRF Protection System
|
|
|
|
## Übersicht
|
|
|
|
Das CSRF (Cross-Site Request Forgery) Protection System bietet robusten Schutz gegen CSRF-Angriffe durch:
|
|
|
|
- **Atomare Session-Updates**: Verhindert Race Conditions bei parallelen Requests
|
|
- **Vereinfachte Token-Generierung**: Immer neue Token, keine Wiederverwendung
|
|
- **Robuste HTML-Verarbeitung**: DOM-basierte Token-Ersetzung mit Regex-Fallback
|
|
- **Einheitliche API**: Konsistente Endpunkte für PHP und JavaScript
|
|
|
|
## Architektur
|
|
|
|
### Komponenten
|
|
|
|
1. **CsrfProtection**: Verwaltet Token-Generierung und Validierung
|
|
2. **SessionManager**: Bietet atomare Session-Updates mit Locking
|
|
3. **FormDataResponseProcessor**: Ersetzt Token-Platzhalter in HTML
|
|
4. **CsrfMiddleware**: Validiert Token bei state-changing Requests
|
|
5. **API Controllers**: Bereitstellen Token-Endpunkte für JavaScript
|
|
|
|
### Session-Locking
|
|
|
|
Das System verwendet Session-Locking, um Race Conditions zu verhindern:
|
|
|
|
- **FileSessionStorage**: Datei-basiertes Locking mit `flock()`
|
|
- **RedisSessionStorage**: Redis `SET NX EX` für atomares Locking
|
|
- **InMemorySessionStorage**: In-Memory-Locking für Tests
|
|
|
|
### Optimistic Locking
|
|
|
|
SessionData verwendet Versionsnummern für Optimistic Locking:
|
|
|
|
- Jede Änderung inkrementiert die Version
|
|
- Bei Konflikten wird automatisch retried
|
|
- Verhindert Datenverlust bei parallelen Updates
|
|
|
|
## Token-Generierung
|
|
|
|
### Vereinfachte Strategie
|
|
|
|
**Wichtig**: Das System generiert **immer neue Token** - keine Wiederverwendung.
|
|
|
|
```php
|
|
// Generiert immer einen neuen Token
|
|
$token = $session->csrf->generateToken($formId);
|
|
```
|
|
|
|
### Token-Verwaltung
|
|
|
|
- **Maximal 3 Token pro Form-ID**: Älteste werden automatisch entfernt
|
|
- **Token-Lifetime**: 2 Stunden (7200 Sekunden)
|
|
- **Re-Submit Window**: 30 Sekunden für erneute Submissions
|
|
- **Automatisches Cleanup**: Abgelaufene Token werden entfernt
|
|
|
|
### Token-Format
|
|
|
|
- **Länge**: 64 Zeichen
|
|
- **Format**: Hexadezimal (0-9, a-f)
|
|
- **Beispiel**: `f677a410facd19e4e004e41e24fa1c8abbe2379a91abcf8642de23a6988ba8b`
|
|
|
|
## HTML-Verarbeitung
|
|
|
|
### DOM-basierte Ersetzung
|
|
|
|
Der `FormDataResponseProcessor` verwendet DOM-basierte Verarbeitung für robuste Token-Ersetzung:
|
|
|
|
```php
|
|
// Automatische Ersetzung von Platzhaltern
|
|
$html = $processor->process($html, $session);
|
|
```
|
|
|
|
### Platzhalter-Format
|
|
|
|
```html
|
|
<input type="hidden" name="_token" value="___TOKEN_FORMID___">
|
|
```
|
|
|
|
### Fallback-Mechanismus
|
|
|
|
Bei DOM-Parsing-Fehlern fällt das System automatisch auf Regex-basierte Ersetzung zurück.
|
|
|
|
## API-Endpunkte
|
|
|
|
### Token-Generierung
|
|
|
|
**GET/POST `/api/csrf/token`**
|
|
|
|
Generiert einen neuen CSRF-Token für eine Form.
|
|
|
|
**Parameter:**
|
|
- `action` (optional): Form-Action URL (Standard: `/`)
|
|
- `method` (optional): HTTP-Methode (Standard: `post`)
|
|
- `form_id` (optional): Explizite Form-ID
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"form_id": "form_abc123def456",
|
|
"token": "64-character-hex-token",
|
|
"expires_in": 7200,
|
|
"headers": {
|
|
"X-CSRF-Form-ID": "form_abc123def456",
|
|
"X-CSRF-Token": "64-character-hex-token"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Token-Refresh
|
|
|
|
**GET `/api/csrf/refresh`**
|
|
|
|
Generiert einen neuen Token für eine bestehende Form-ID.
|
|
|
|
**Parameter:**
|
|
- `form_id` (erforderlich): Form-ID
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"form_id": "form_abc123def456",
|
|
"token": "64-character-hex-token",
|
|
"expires_in": 7200,
|
|
"headers": {
|
|
"X-CSRF-Form-ID": "form_abc123def456",
|
|
"X-CSRF-Token": "64-character-hex-token"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Token-Informationen
|
|
|
|
**GET `/api/csrf/info`**
|
|
|
|
Gibt Informationen über aktive Token zurück.
|
|
|
|
**Parameter:**
|
|
- `form_id` (erforderlich): Form-ID
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"form_id": "form_abc123def456",
|
|
"active_tokens": 2,
|
|
"max_tokens_per_form": 3,
|
|
"token_lifetime_seconds": 7200,
|
|
"resubmit_window_seconds": 30
|
|
}
|
|
```
|
|
|
|
## JavaScript-Integration
|
|
|
|
### CsrfManager
|
|
|
|
Der `CsrfManager` verwaltet Token automatisch:
|
|
|
|
```javascript
|
|
import { CsrfManager } from './modules/security/CsrfManager.js';
|
|
|
|
const csrfManager = CsrfManager.create({
|
|
endpoint: '/api/csrf/token',
|
|
autoRefresh: true,
|
|
refreshInterval: 30 * 60 * 1000 // 30 Minuten
|
|
});
|
|
|
|
// Token abrufen
|
|
const token = csrfManager.getToken();
|
|
|
|
// Token-Header für Requests
|
|
const headers = csrfManager.getTokenHeader();
|
|
```
|
|
|
|
### Manuelle Token-Anfrage
|
|
|
|
```javascript
|
|
// Token für spezifische Form generieren
|
|
const response = await fetch('/api/csrf/token?action=/submit&method=post');
|
|
const data = await response.json();
|
|
|
|
const token = data.token;
|
|
const formId = data.form_id;
|
|
```
|
|
|
|
### AJAX-Requests
|
|
|
|
```javascript
|
|
// Token in Request-Body
|
|
fetch('/api/submit', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-Form-ID': formId,
|
|
'X-CSRF-Token': token
|
|
},
|
|
body: JSON.stringify({
|
|
_form_id: formId,
|
|
_token: token,
|
|
// ... weitere Daten
|
|
})
|
|
});
|
|
```
|
|
|
|
## Validierung
|
|
|
|
### Middleware
|
|
|
|
Die `CsrfMiddleware` validiert automatisch Token für:
|
|
|
|
- POST Requests
|
|
- PUT Requests
|
|
- DELETE Requests
|
|
- PATCH Requests
|
|
|
|
GET Requests werden nicht validiert.
|
|
|
|
### Validierungsprozess
|
|
|
|
1. **Token-Extraktion**: Aus Request-Body oder Headers
|
|
2. **Form-ID-Extraktion**: Aus Request-Body oder Headers
|
|
3. **Session-Lookup**: Token in Session suchen
|
|
4. **Validierung**: Token-Match und Expiration prüfen
|
|
5. **Markierung**: Token als verwendet markieren
|
|
|
|
### Fehlerbehandlung
|
|
|
|
Bei Validierungsfehlern wird eine `CsrfValidationFailedException` geworfen mit:
|
|
|
|
- Fehlgrund (missing token, invalid token, expired, etc.)
|
|
- Session-ID
|
|
- Anzahl verfügbarer Token
|
|
- Liste verfügbarer Form-IDs
|
|
|
|
## Best Practices
|
|
|
|
### Form-IDs
|
|
|
|
- Verwende konsistente Form-IDs für dieselben Formulare
|
|
- Form-IDs werden automatisch aus Action und Method generiert
|
|
- Explizite Form-IDs können über `form_id` Parameter gesetzt werden
|
|
|
|
### Token-Refresh
|
|
|
|
- Token sollten vor Ablauf erneuert werden
|
|
- Automatischer Refresh alle 30 Minuten empfohlen
|
|
- Bei langen Formularen manuell vor Submit refreshen
|
|
|
|
### Fehlerbehandlung
|
|
|
|
```php
|
|
try {
|
|
// Form-Verarbeitung
|
|
} catch (CsrfValidationFailedException $e) {
|
|
// Token ungültig - Benutzer sollte Seite neu laden
|
|
return redirect()->back()->with('error', 'Session expired. Please try again.');
|
|
}
|
|
```
|
|
|
|
### Testing
|
|
|
|
```php
|
|
// Token generieren
|
|
$token = $session->csrf->generateToken('test-form');
|
|
|
|
// Token validieren
|
|
$isValid = $session->csrf->validateToken('test-form', $token);
|
|
|
|
// Mit Debug-Informationen
|
|
$result = $session->csrf->validateTokenWithDebug('test-form', $token);
|
|
if (!$result['valid']) {
|
|
$reason = $result['debug']['reason'];
|
|
// ...
|
|
}
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Token wird nicht akzeptiert
|
|
|
|
1. **Prüfe Token-Länge**: Muss genau 64 Zeichen sein
|
|
2. **Prüfe Form-ID**: Muss mit generierter Form-ID übereinstimmen
|
|
3. **Prüfe Session**: Token muss in Session vorhanden sein
|
|
4. **Prüfe Expiration**: Token darf nicht abgelaufen sein
|
|
|
|
### Race Conditions
|
|
|
|
Das System verwendet atomare Session-Updates, um Race Conditions zu verhindern. Bei Problemen:
|
|
|
|
1. Prüfe Session-Storage-Konfiguration
|
|
2. Prüfe Locking-Mechanismus (File/Redis)
|
|
3. Prüfe Logs für Version-Conflicts
|
|
|
|
### HTML-Verarbeitung
|
|
|
|
Bei Problemen mit Token-Ersetzung:
|
|
|
|
1. Prüfe Platzhalter-Format: `___TOKEN_FORMID___`
|
|
2. Prüfe HTML-Struktur (DOM-Parsing kann bei malformed HTML fehlschlagen)
|
|
3. Prüfe Logs für Fallback auf Regex
|
|
|
|
## Migration
|
|
|
|
### Von altem System
|
|
|
|
Das neue System ist vollständig kompatibel mit dem alten System:
|
|
|
|
- Alte Token werden weiterhin akzeptiert
|
|
- Alte API-Endpunkte funktionieren weiterhin
|
|
- Keine Breaking Changes
|
|
|
|
### Neue Features nutzen
|
|
|
|
1. Verwende `/api/csrf/token` statt `/api/csrf-token`
|
|
2. Nutze atomare Session-Updates für kritische Operationen
|
|
3. Verwende DOM-basierte HTML-Verarbeitung (automatisch)
|
|
|
|
## Performance
|
|
|
|
### Optimierungen
|
|
|
|
- **Token-Cleanup**: Automatisch nach 5 Minuten
|
|
- **Maximal 3 Token**: Begrenzt Session-Größe
|
|
- **Locking-Timeout**: 5 Sekunden Standard-Timeout
|
|
- **Optimistic Locking**: Reduziert Lock-Contention
|
|
|
|
### Monitoring
|
|
|
|
- Token-Anzahl pro Form-ID überwachen
|
|
- Session-Lock-Contention überwachen
|
|
- Token-Validierungs-Fehlerrate überwachen
|
|
|
|
## Sicherheit
|
|
|
|
### Schutz-Mechanismen
|
|
|
|
1. **Token-Rotation**: Neue Token nach jeder Generierung
|
|
2. **Expiration**: Token laufen nach 2 Stunden ab
|
|
3. **Single-Use**: Token können innerhalb von 30 Sekunden wiederverwendet werden
|
|
4. **Session-Binding**: Token sind an Session gebunden
|
|
|
|
### Angriffs-Vektoren
|
|
|
|
Das System schützt gegen:
|
|
|
|
- **CSRF-Angriffe**: Token-Validierung verhindert unautorisierte Requests
|
|
- **Token-Replay**: Expiration und Single-Use verhindern Replay
|
|
- **Session-Hijacking**: Session-Binding verhindert Token-Diebstahl
|
|
|
|
## Referenz
|
|
|
|
### Klassen
|
|
|
|
- `App\Framework\Http\Session\CsrfProtection`
|
|
- `App\Framework\Http\Session\SessionManager`
|
|
- `App\Framework\View\Response\FormDataResponseProcessor`
|
|
- `App\Framework\Http\Middlewares\CsrfMiddleware`
|
|
- `App\Application\Api\CsrfController`
|
|
- `App\Application\Controller\CsrfController`
|
|
|
|
### Konstanten
|
|
|
|
- `CsrfProtection::TOKEN_LIFETIME` = 7200 (2 Stunden)
|
|
- `CsrfProtection::MAX_TOKENS_PER_FORM` = 3
|
|
- `CsrfProtection::RE_SUBMIT_WINDOW` = 30 (Sekunden)
|
|
|
|
|