Files
clemensandClaude Opus 5 19f1ac7ec8 CSRF-Schutz, Brute-Force-Bremse und wirksame Session-Cookie-Flags
Neu: inc/security.inc.php, eingebunden von inc/config.inc.php.

CSRF
Der Adminbereich hatte keinerlei Schutz gegen fremde POSTs. Jetzt haengt
csrf_inject_output() als Ausgabefilter das Token an jedes POST-Formular und
csrf_require() weist POSTs ohne gueltiges Token ab. Beides wird nur fuer
Skripte unterhalb von admin/ aktiviert, der oeffentliche Bereich bleibt
unveraendert. Der Umweg ueber den Ausgabefilter erspart es, die rund 60
bestehenden Formulare einzeln anzufassen; der AJAX-Aufruf auf
mailtemplate.php schickt das Token als Feld mit.

Session-Cookie-Flags
config.inc.php setzt secure, httponly und samesite - aber 25 Dateien in
admin/ und intern/ riefen session_start() vor dem Include auf, womit die
Parameter wirkungslos waren. Die vorgezogenen Aufrufe sind entfernt,
config.inc.php startet die Sitzung nur noch, wenn keine laeuft.
admin/logout.php musste umgestellt werden, weil dort session_destroy()
vor dem Include stand; die Cookies werden jetzt mit denselben Parametern
geloescht, mit denen sie gesetzt wurden.

Brute-Force
Nach 5 Fehlversuchen je Konto oder 20 je IP ist die Anmeldung 15 Minuten
gesperrt, gezaehlt in der neuen Tabelle login_attempts. Fehlt die Tabelle,
laeuft der Login wie bisher - gleiche Vorgehensweise wie bei
securitytokensHatAblaufspalte(). Eine erfolgreiche Anmeldung raeumt die
Fehlversuche des Kontos ab.

Passwort vergessen
admin/passwortvergessen.php uebergab $mail und $body an SendMailMessage();
beide Variablen gibt es dort nicht, sie heissen $empfaenger und $text. Die
Mail ging deshalb nie raus, obwohl der Reset-Code gesetzt wurde.

Getestet gegen einen lokalen PHP-Server: Token wird eingesetzt, POST ohne
Token liefert 403, mit Token laeuft der Login normal, der sechste
Fehlversuch wird gesperrt, das Sitzungscookie traegt secure/HttpOnly/
SameSite. Oeffentliche Seiten sind unveraendert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 14:18:59 +02:00

97 lines
2.9 KiB
PHP

<?php
require_once(__DIR__ . "/../inc/config.inc.php");
require_once(__DIR__ . "/../inc/functions.inc.php");
if(!isset($_GET['userid']) || !isset($_GET['code'])) {
error("Leider wurde beim Aufruf dieser Website kein Code übermittelt");
}
$showForm = true;
$userid = $_GET['userid'];
$code = $_GET['code'];
//Abfrage des Nutzers
$statement = $pdo->prepare("SELECT *, (passwortcode_time IS NOT NULL AND passwortcode_time > DATE_SUB(NOW(), INTERVAL 24 HOUR)) AS code_noch_gueltig FROM intern_users WHERE id = :userid");
$result = $statement->execute(array('userid' => $userid));
$user = $statement->fetch();
//Überprüfe dass ein Nutzer gefunden wurde und dieser auch ein Authentifizierungscode angefordert hat
// fetch() liefert false und nicht null, wenn kein Benutzer gefunden wird -
// die alte Bedingung griff daher nie und lief mit false in den Array-Zugriff.
if(!$user || $user['passwortcode'] === null) {
error("Der Benutzer wurde nicht gefunden oder hat kein ein Authentifizierungscode angefordert.");
$showForm = false;
}
// Ablauf in SQL pruefen: passwortcode_time wird mit NOW() gesetzt (Uhr des
// DB-Servers), waehrend PHP auf dem Webserver in einer anderen Zeitzone
// laeuft. strtotime()/time() lag hier um genau diese Differenz daneben.
if(!$user['code_noch_gueltig']) {
error("Dein Code ist leider abgelaufen. Bitte benutze die Authentifizierungsfunktion erneut.");
$showForm = false;
}
//Überprüfe den Passwortcode
if(sha1($code) != $user['passwortcode']) {
error("Der übergebene Code war ungültig. Stell sicher, dass du den genauen Link in der URL aufgerufen hast. Solltest du mehrmals die Authentifizierungsfunktion genutzt haben, so ruf den Link in der neuesten E-Mail auf.");
$showForm = false;
}
include("templates/header.inc.php");
?>
<div class="container small-container-500">
<h1>Authentifizierung</h1>
<?php
if(check_mailreg()){
echo "<p>Ihr Benutzerkonto ist schon authentifiziert.</p>";
}elseif($showForm){
// (hier stand ein password_hash() auf einer nie gesetzten Variablen, dessen
// Ergebnis niemand benutzt hat - es warf nur eine Deprecation-Warnung)
$statement = $pdo->prepare("UPDATE intern_users SET mailreg='1', passwortcode = NULL, passwortcode_time = NULL WHERE id = :userid");
$result = $statement->execute(array('userid'=> $userid ));
if($result) {
$msg = "<p>Die Authentifizierung Ihre Kontos wurde erfolgreich abgeschlossen.</p>";
$showForm = false;
}
}else{
echo "<p>Die Authentifizierungsfunktion steht mit den angegebenen Daten nicht zur Verfügung.</p>";
}
if(isset($msg)) {
echo $msg;
}
echo "<br><br>";
echo "<br><br>";
echo "<form action='index.php' method=POST>";
echo "<input name=aktion type=hidden value=1>";
echo "<input type=submit class='btn btn-primary' value='Zurück'><br>";
echo '</form>';
echo "<br><br>";
echo "<br><br>";
echo "<br><br>";
echo "<br><br>";
?>
</div> <!-- /container -->
<?php
include("templates/footer.inc.php")
?>