'; } /** * Prueft das mitgeschickte Token. Akzeptiert das Formularfeld und den * Header X-CSRF-Token, damit auch AJAX-Aufrufe funktionieren. */ function csrf_valid(): bool { if (empty($_SESSION['csrf_token'])) { return false; } $token = $_POST['csrf_token'] ?? ($_SERVER['HTTP_X_CSRF_TOKEN'] ?? ''); if (!is_string($token) || $token === '') { return false; } return hash_equals($_SESSION['csrf_token'], $token); } /** * Bricht jeden POST ohne gueltiges Token ab. Fuer GET-Anfragen wirkungslos. */ function csrf_require(): void { if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') { return; } if (csrf_valid()) { return; } http_response_code(403); $istAjax = ($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '') === 'XMLHttpRequest'; if ($istAjax) { header('Content-Type: application/json'); echo json_encode(['error' => 'Sicherheitstoken ungueltig. Bitte neu anmelden.']); exit; } header('Content-Type: text/html; charset=utf-8'); echo '

Die Anfrage wurde abgelehnt.

'; echo 'Das Sicherheitstoken war ungültig oder die Sitzung ist abgelaufen.
'; echo 'Bitte die Seite neu laden und den Vorgang wiederholen.

'; echo 'Zurück zum Adminbereich'; exit; } /** * Haengt das Token an jedes POST-Formular der Ausgabe. Wird als Callback von * ob_start() benutzt, damit die rund 60 bestehenden Formulare nicht einzeln * angefasst werden muessen. */ function csrf_inject_output(string $html): string { $ersetzt = preg_replace_callback( '/]*>/i', static function (array $treffer): string { $tag = $treffer[0]; // Nur POST-Formulare brauchen ein Token. if (!preg_match('/method\s*=\s*["\']?\s*post/i', $tag)) { return $tag; } return $tag . csrf_field(); }, $html ); // preg_replace_callback liefert bei einem Fehler null - dann lieber die // unveraenderte Seite ausliefern als eine leere. return $ersetzt ?? $html; } /** * Aktiviert das automatische Einfuegen. Muss vor jeder Ausgabe laufen. */ function csrf_autoinject(): void { ob_start('csrf_inject_output'); } /** * Sicheres Ziel fuer Formulare und Links auf die eigene Seite. * * Ersetzt $_SERVER['PHP_SELF']: das enthaelt bei Aufrufen wie * /admin/anfragen.php/">