CSRF Schutz Implementieren
CSRF-Schutz: Cross-Site Request Forgery verhindern
CSRF lässt Angreifer Aktionen im Namen Ihrer Nutzer ausführen. Passwort ändern, Geld überweisen, Account löschen – ohne dass der Nutzer es merkt.
Was ist CSRF?
Ein Angreifer bringt einen eingeloggten Nutzer dazu, unbeabsichtigt eine Aktion auszuführen:
<!-- Auf bösartiger Website -->
<img src="https://bank.com/transfer?to=attacker&amount=10000">
<!-- Oder mit verstecktem Formular -->
<form action="https://bank.com/transfer" method="POST" id="evil">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="10000">
</form>
<script>document.getElementById('evil').submit();</script>
Der Browser sendet automatisch die Session-Cookies mit – die Bank sieht eine authentifizierte Anfrage!
Lösung 1: CSRF-Token
Fügen Sie ein geheimes Token hinzu, das Angreifer nicht kennen können.
PHP Implementation
<?php
// Token generieren und in Session speichern
function generateCsrfToken(): string {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
// Token validieren
function validateCsrfToken(string $token): bool {
return isset($_SESSION['csrf_token'])
&& hash_equals($_SESSION['csrf_token'], $token);
}
<!-- Im Formular -->
<form method="POST" action="/transfer">
<input type="hidden" name="csrf_token" value="<?= generateCsrfToken() ?>">
<input type="text" name="amount">
<button type="submit">Überweisen</button>
</form>
<?php
// Bei der Verarbeitung
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!validateCsrfToken($_POST['csrf_token'] ?? '')) {
http_response_code(403);
die('CSRF-Token ungültig');
}
// Request verarbeiten...
}
Node.js mit Express
const csrf = require('csurf');
const cookieParser = require('cookie-parser');
app.use(cookieParser());
app.use(csrf({ cookie: true }));
// Token an Template übergeben
app.get('/form', (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});
// In EJS Template
<form method="POST">
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<button type="submit">Senden</button>
</form>
AJAX-Requests
// Token aus Meta-Tag lesen
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
// Mit fetch senden
fetch('/api/action', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify(data)
});
// Oder mit axios
axios.defaults.headers.common['X-CSRF-Token'] = csrfToken;
Lösung 2: SameSite Cookies
Moderne Browser unterstützen das SameSite-Attribut:
// Cookies nicht bei Cross-Site Requests senden Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly // Oder etwas lockerer (Links erlaubt, Formulare nicht) Set-Cookie: session=abc123; SameSite=Lax; Secure; HttpOnly
| SameSite | Verhalten |
|---|---|
Strict |
Nie bei Cross-Site Requests |
Lax |
Nur bei Top-Level Navigation (Links) |
None |
Immer (erfordert Secure) |
<?php
// PHP: Session-Cookie mit SameSite
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Strict'
]);
session_start();
Lösung 3: Double Submit Cookie
Alternative ohne Server-Session:
// 1. Token in Cookie UND in Request senden // 2. Server vergleicht beide // Cookie setzen (JavaScript-lesbar) document.cookie = "csrf_token=random123; path=/; secure"; // Im Formular <input type="hidden" name="csrf_token" value="random123"> // Server prüft: Cookie == Form-Wert?
Lösung 4: Origin/Referer Header prüfen
<?php
function checkOrigin(): bool {
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
$referer = $_SERVER['HTTP_REFERER'] ?? '';
$allowedOrigins = ['https://example.com'];
// Origin-Header bevorzugen
if ($origin && in_array($origin, $allowedOrigins)) {
return true;
}
// Fallback auf Referer
if ($referer) {
$refererHost = parse_url($referer, PHP_URL_HOST);
return $refererHost === 'example.com';
}
return false;
}
Framework-spezifischer CSRF-Schutz
Laravel
{{-- Blade Template --}}
<form method="POST">
@csrf
...
</form>
// Für AJAX
<meta name="csrf-token" content="{{ csrf_token() }}">
// In JavaScript
$.ajaxSetup({
headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') }
});
Django
{% csrf_token %}
// AJAX
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
Spring Boot
<input type="hidden"
name="${_csrf.parameterName}"
value="${_csrf.token}"/>
Best Practices
- CSRF-Token für alle state-ändernden Requests
- SameSite=Strict oder Lax für Session-Cookies
- Keine GET-Requests für Änderungen
- Token pro Session oder pro Request
- Sichere Zufallsgeneratoren verwenden
CSRF-Checkliste
- ☐ CSRF-Token in allen Formularen
- ☐ Token bei AJAX-Requests senden
- ☐ SameSite-Attribut für Cookies
- ☐ Token serverseitig validieren
- ☐ Keine state-Änderungen via GET