CSRF Protection
Cross-Site Request Forgery (CSRF) er et angreb hvor en ondsindet website tvinger en brugers browser til at udføre uønskede handlinger på en website hvor brugeren er autentificeret.
Om Truslen
Cross-Site Request Forgery (CSRF) er en sofistikeret angrebsvektor der udnytter det faktum at browsere automatisk sender credentials (cookies, HTTP authentication) med hver request til et givent domæne. Når en bruger er logget ind på en legitim website og samtidig besøger en ondsindet website, kan angriberen få brugerens browser til at sende forfalskede requests til den legitime website. Dette kan resultere i uautoriserede handlinger som pengeoverførsler, password-ændringer, email-ændringer eller sletning af data - alt sammen udført i brugerens navn uden deres vidende. CSRF er særligt farligt fordi det er usynligt for offeret og kan ramme selv sikkerhedsbevidste brugere. Undersøgelser viser at mange websites stadig mangler tilstrækkelig CSRF-beskyttelse, særligt på kritiske funktioner. Moderne browsere implementerer SameSite cookie-attributten som et forsvar, men mange websites understøtter stadig ældre browsere eller har ikke konfigureret dette korrekt.
Key Points
- ✓Implementer CSRF tokens for alle state-changing operations (POST, PUT, DELETE)
- ✓Brug SameSite cookie-attribut til at begrænse cross-site requests
- ✓Generér kryptografisk sikre CSRF tokens med tilstrækkelig entropi
- ✓Validér CSRF tokens på server-side for hver request
- ✓Brug dobbelt submit cookie pattern som alternativ til session-baserede tokens
- ✓Implementer token-rotation efter login og andre kritiske handlinger
- ✓Brug kun GET requests til read-only operations - aldrig state changes
- ✓Implementer Origin og Referer header-validering som ekstra lag
- ✓Brug custom request headers for AJAX requests som CSRF-beskyttelse
- ✓Implementer reauthentication for særligt kritiske handlinger
- ✓Sæt korte token-levetider for sensitive operationer
- ✓Brug automatisk token-injektion i alle formularer via middleware
- ✓Implementer rate limiting på kritiske endpoints
- ✓Kombiner CSRF-beskyttelse med andre sikkerhedsmekanismer som Content Security Policy
Sårbar Kode (UNDGÅ)
Brug ALDRIG denne kode i produktion!
<?php
// SÅRBAR KODE - Brug ALDRIG dette i produktion!
// 1. Ingen CSRF-beskyttelse på kritisk handling
session_start();
if (!isset($_SESSION['user_id'])) {
die("Not authenticated");
}
// Sårbar password change endpoint
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$new_password = $_POST['new_password'];
$user_id = $_SESSION['user_id'];
// INGEN CSRF TOKEN VALIDERING!
$hashed = password_hash($new_password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("UPDATE users SET password = ? WHERE id = ?");
$stmt->execute([$hashed, $user_id]);
echo "Password updated successfully!";
}
// En attacker kan lave en skjult form på evil.com:
// <form action="https://victim-site.com/change-password.php" method="POST">
// <input name="new_password" value="hacked123">
// </form>
// <script>document.forms[0].submit();</script>
// 2. Sårbar penge-overførsel
if (isset($_POST['transfer'])) {
$to_account = $_POST['to_account'];
$amount = $_POST['amount'];
$from_account = $_SESSION['account_id'];
// Ingen validering af request-oprindelse!
$stmt = $pdo->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?");
$stmt->execute([$amount, $from_account]);
$stmt = $pdo->prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?");
$stmt->execute([$amount, $to_account]);
echo "Transfer completed: $" . $amount;
}
// 3. Sårbar sletning af konto
if (isset($_GET['delete_account'])) {
// MEGET FARLIG: GET request til state-changing operation
$user_id = $_SESSION['user_id'];
$stmt = $pdo->prepare("DELETE FROM users WHERE id = ?");
$stmt->execute([$user_id]);
echo "Account deleted";
}
// Attacker kan bare inkludere: <img src="https://victim-site.com/account.php?delete_account=1">
// 4. Sårbar email-ændring
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['new_email'])) {
$new_email = $_POST['new_email'];
$user_id = $_SESSION['user_id'];
// Ingen token validation
$stmt = $pdo->prepare("UPDATE users SET email = ? WHERE id = ?");
$stmt->execute([$new_email, $user_id]);
echo "Email updated to: " . $new_email;
}
// 5. Sårbar admin-funktionalitet
if (isset($_POST['make_admin'])) {
$target_user = $_POST['user_id'];
// Antager at session-check er nok
if ($_SESSION['is_admin']) {
$stmt = $pdo->prepare("UPDATE users SET role = 'admin' WHERE id = ?");
$stmt->execute([$target_user]);
echo "User promoted to admin";
}
}
// Hvis admin besøger ondsindet site, kan attacker få admin-rettigheder
?>Sikker Kode (ANBEFALET)
Best practices for sikker implementation
<?php
// SIKKER KODE - Best practices for CSRF prevention
session_start();
// 1. CSRF Token Generation
function generateCSRFToken() {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
// 2. CSRF Token Validation
function validateCSRFToken($token) {
if (!isset($_SESSION['csrf_token']) || !isset($token)) {
return false;
}
// Brug hash_equals() for at forhindre timing attacks
return hash_equals($_SESSION['csrf_token'], $token);
}
// 3. Sikker password change med CSRF-beskyttelse
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Validér CSRF token FØRST
if (!validateCSRFToken($_POST['csrf_token'] ?? '')) {
http_response_code(403);
die("Invalid CSRF token");
}
if (!isset($_SESSION['user_id'])) {
http_response_code(401);
die("Not authenticated");
}
$new_password = $_POST['new_password'] ?? '';
$current_password = $_POST['current_password'] ?? '';
$user_id = $_SESSION['user_id'];
// Re-authentication for kritisk handling
$stmt = $pdo->prepare("SELECT password FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$user = $stmt->fetch();
if (!password_verify($current_password, $user['password'])) {
die("Current password incorrect");
}
// Validér password strength
if (strlen($new_password) < 12) {
die("Password too weak");
}
$hashed = password_hash($new_password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("UPDATE users SET password = ? WHERE id = ?");
$stmt->execute([$hashed, $user_id]);
// Regenerér session ID efter kritisk ændring
session_regenerate_id(true);
// Regenerér CSRF token
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
echo "Password updated successfully!";
}
// 4. Form med CSRF token
$csrf_token = generateCSRFToken();
?>
<!DOCTYPE html>
<html>
<head>
<title>Change Password</title>
</head>
<body>
<form method="POST" action="change-password.php">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token); ?>">
<input type="password" name="current_password" required>
<input type="password" name="new_password" required>
<button type="submit">Change Password</button>
</form>
</body>
</html>
<?php
// 5. AJAX med CSRF token
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$json = file_get_contents('php://input');
$data = json_decode($json, true);
if (!validateCSRFToken($data['csrf_token'] ?? '')) {
http_response_code(403);
echo json_encode(['error' => 'Invalid CSRF token']);
exit;
}
// Process request...
echo json_encode(['success' => true]);
}
// 6. Double Submit Cookie Pattern (alternativ)
function setCSRFCookie() {
$token = bin2hex(random_bytes(32));
setcookie('csrf_token', $token, [
'expires' => time() + 3600,
'path' => '/',
'domain' => '',
'secure' => true,
'httponly' => false, // Skal være tilgængelig for JavaScript
'samesite' => 'Strict'
]);
return $token;
}
// 7. SameSite Cookie Configuration
ini_set('session.cookie_samesite', 'Strict');
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
// 8. Origin Header Validation (ekstra lag)
function validateOrigin() {
$allowed_origins = ['https://mysite.com'];
$origin = $_SERVER['HTTP_ORIGIN'] ?? $_SERVER['HTTP_REFERER'] ?? '';
foreach ($allowed_origins as $allowed) {
if (strpos($origin, $allowed) === 0) {
return true;
}
}
return false;
}
?>Almindelige Fejl
- ⚠At kun implementere CSRF-beskyttelse på login, men glemme andre kritiske endpoints som password reset og account deletion
- ⚠At bruge GET requests til state-changing operations - dette kan ikke beskyttes med CSRF tokens
- ⚠At generere CSRF tokens med utilstrækkelig entropi (f.eks. md5(time()) i stedet for random_bytes())
- ⚠At sammenligne CSRF tokens med == i stedet for hash_equals(), hvilket gør dem sårbare over for timing attacks
- ⚠At glemme at validere CSRF tokens på server-side og kun stole på client-side JavaScript
- ⚠At ikke regenerere CSRF tokens efter login eller andre kritiske state changes
Forebyggelsesteknikker
- 🛡️Implementer Synchronizer Token Pattern med session-baserede CSRF tokens
- 🛡️Brug Double Submit Cookie Pattern for stateless applikationer
- 🛡️Konfigurer SameSite=Strict eller SameSite=Lax på alle cookies
- 🛡️Implementer Origin og Referer header validation som ekstra beskyttelse
- 🛡️Brug custom request headers (X-Requested-With) for AJAX requests
- 🛡️Implementer re-authentication for særligt kritiske handlinger
- 🛡️Brug automatisk CSRF token injection via middleware eller framework features
- 🛡️Kombiner CSRF-beskyttelse med rate limiting på kritiske endpoints
Testing Metoder
- 🔍Manuel testing ved at fjerne eller ændre CSRF tokens og se om requests stadig accepteres
- 🔍Brug værktøjer som Burp Suite til at automatisk fjerne CSRF tokens fra requests
- 🔍Test om GET requests kan bruges til state-changing operations
- 🔍Verificer at tokens har tilstrækkelig entropi og ikke kan gættes
- 🔍Test token-validering på tværs af forskellige user sessions
Quick Info
- ⚠Session Hijacking - Tyveri af session cookies
- ⚠Clickjacking - Tricking users into clicking hidden elements
- ⚠XSS - Cross-Site Scripting som kan bruges til at stjæle CSRF tokens
⚠️Sikkerhedsadvarsel
Sikkerhedssårbarheder kan have alvorlige konsekvenser. Test altid grundigt i et sikkert miljø og implementer alle anbefalede forebyggelsesteknikker.