Compare commits

..

3 Commits

Author SHA1 Message Date
niklas.ortmann ad9a00fd49 csrf.php 2026-07-19 14:57:52 +02:00
niklas.ortmann 42faab17e8 Create csrf.php 2026-07-19 14:56:26 +02:00
niklas.ortmann 1da4842847 Update dataSources.local.xml 2026-07-19 14:56:25 +02:00
4 changed files with 105 additions and 11 deletions
+7 -1
View File
@@ -89,6 +89,12 @@
Es ist ein Datenbankfehler aufgetreten. Bitte versuche es erneut.
</p>
<?php endif; ?>
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_csrf_token"): ?>
<p class="alert-message is-error">
Deine Sitzung ist abgelaufen oder die Anfrage konnte nicht überprüft werden.
Bitte lade die Seite neu und versuche es erneut.
</p>
<?php endif; ?>
<?php
unset($_SESSION["message"]);
?>
?>
+88
View File
@@ -0,0 +1,88 @@
<?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();
}
}
+1
View File
@@ -2,6 +2,7 @@
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
include_once "includes/csrf.php";
include_once "php/controller/index-controller.php";
?>
<!DOCTYPE html>
+9 -10
View File
@@ -310,20 +310,21 @@ class DatabaseArticleManager implements ArticleManagerDAO {
try {
$db = $this->getConnection();
$sql = "SELECT id, title, content, author, category, tags, created
FROM articles
WHERE title LIKE :keyword
OR content LIKE :keyword
OR tags LIKE :keyword;";
FROM articles
WHERE title LIKE :keyword
OR content LIKE :keyword
OR tags LIKE :keyword";
$command = $db->prepare($sql);
if (!$command) {
throw new InternalServerErrorException("internal_error");
}
// Wildcards für die SQL-Suche hinzufügen
// Wildcards für die Suche hinzufügen
$searchParam = '%' . $cleankeyword . '%';
$success = $command->execute([
":keyword" => $searchParam
]);
@@ -336,11 +337,10 @@ class DatabaseArticleManager implements ArticleManagerDAO {
$filteredArticles = [];
foreach ($rows as $row) {
$articleId = intval($row['id']);
$likes = $this->getLikesForArticle($articleId);
$likes = $this->getLikesForArticle(intval($row['id']));
$filteredArticles[] = new Article(
$articleId,
intval($row['id']),
$row['title'] ?? '',
$row['content'] ?? '',
$row['author'] ?? '',
@@ -358,7 +358,6 @@ class DatabaseArticleManager implements ArticleManagerDAO {
}
}
/**
* Holt alle User-IDs, die einen bestimmten Beitrag geliked haben.
*