88 lines
2.3 KiB
PHP
88 lines
2.3 KiB
PHP
<?php
|
|
/**
|
|
* CSRF-Schutz nach dem Synchronizer-Token-Pattern.
|
|
*
|
|
* Pro Session wird ein einziges, zufälliges Token erzeugt,
|
|
* das in jedem Formular als verstecktes Feld mitgeschickt und bei jeder
|
|
* zustandsändernden Anfrage serverseitig mit dem Session-Token verglichen
|
|
* wird.
|
|
*
|
|
* @author Niklas Ortmann
|
|
*/
|
|
|
|
if (session_status() === PHP_SESSION_NONE) {
|
|
session_start();
|
|
}
|
|
|
|
/**
|
|
* Gibt das aktuelle CSRF-Token der Session zurück.
|
|
*
|
|
* Existiert noch kein Token, wird eines erzeugt und in der Session
|
|
* gespeichert.
|
|
*
|
|
* @return string Das CSRF-Token
|
|
*/
|
|
function csrf_token(): string
|
|
{
|
|
if (empty($_SESSION["csrf_token"]) || !is_string($_SESSION["csrf_token"])) {
|
|
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
|
|
}
|
|
|
|
return $_SESSION["csrf_token"];
|
|
}
|
|
|
|
/**
|
|
* Gibt ein verstecktes Formularfeld mit dem aktuellen CSRF-Token aus.
|
|
*
|
|
* Wird in jedem Formular benötigt, das eine zustandsändernde
|
|
* Aktion auslöst.
|
|
*
|
|
* @return void
|
|
*/
|
|
function csrf_field(): void
|
|
{
|
|
echo '<input type="hidden" name="csrf_token" value="'
|
|
. htmlspecialchars(csrf_token())
|
|
. '">';
|
|
}
|
|
|
|
/**
|
|
* Prüft, ob das per POST gesendete CSRF-Token zum Session-Token passt.
|
|
*
|
|
* Der Vergleich erfolgt zeitkonstant über hash_equals(), um
|
|
* Timing-Angriffe auf den Vergleich selbst auszuschließen.
|
|
*
|
|
* @return bool true, wenn das Token gültig ist
|
|
*/
|
|
function csrf_verify(): bool
|
|
{
|
|
$sentToken = $_POST["csrf_token"] ?? "";
|
|
$sessionToken = $_SESSION["csrf_token"] ?? "";
|
|
|
|
if (!is_string($sentToken) || $sentToken === "" || $sessionToken === "") {
|
|
return false;
|
|
}
|
|
|
|
return hash_equals($sessionToken, $sentToken);
|
|
}
|
|
|
|
/**
|
|
* Bricht die Anfrage ab und leitet mit einer Fehlermeldung um,
|
|
* wenn das mitgesendete CSRF-Token ungültig oder nicht vorhanden ist.
|
|
*
|
|
* Muss am Anfang jeder zustandsändernden POST-Aktion aufgerufen werden,
|
|
* bevor irgendeine Änderung an Daten vorgenommen wird.
|
|
*
|
|
* @param string $redirectTo Ziel-URL, zu der bei ungültigem Token
|
|
* weitergeleitet wird
|
|
* @return void
|
|
*/
|
|
function csrf_require_valid(string $redirectTo = "index.php"): void
|
|
{
|
|
if (!csrf_verify()) {
|
|
http_response_code(403);
|
|
$_SESSION["message"] = "invalid_csrf_token";
|
|
header("Location: " . $redirectTo);
|
|
exit();
|
|
}
|
|
} |