From f8bcfebc9e7ee09fb2d334018787c374647e35dd Mon Sep 17 00:00:00 2001 From: Clemens Creutzburg Date: Wed, 2 Sep 2026 20:00:42 +0200 Subject: [PATCH] 2FA-Code-Pruefung: Zeitzonen-Fehler und Eingabe-Normalisierung login.php erzeugte expires_at mit PHPs date(), verify_2fa.php verglich gegen MySQLs NOW(). Webserver (secureserver.net) und Datenbankserver (mysql2fda.netcup.net) liegen bei verschiedenen Anbietern und sind unabhaengig voneinander konfiguriert. Laeuft PHP in UTC und MySQL in Europe/Berlin, liegt expires_at (PHP-Zeit + 5 Minuten) zwei Stunden vor NOW() - jeder Code gilt sofort als abgelaufen und wird als "Falscher oder abgelaufener Code" abgewiesen. Die Ablaufzeit wird jetzt beim INSERT per DATE_ADD(NOW(), INTERVAL 5 MINUTE) berechnet. Erzeugung und Pruefung benutzen damit dieselbe Uhr, unabhaengig davon, wie die beiden Server eingestellt sind. Ausserdem: - Der eingegebene Code wird auf Ziffern reduziert. Aus HTML-Mails kopierte Codes schleppen oft Leerzeichen oder geschuetzte Leerzeichen mit, die den Hash-Vergleich scheitern liessen. - Abgelaufener Code und falscher Code werden getrennt gemeldet, damit ein solcher Fall kuenftig ohne Raten eingegrenzt werden kann. Co-Authored-By: Claude Opus 5 --- intern/login.php | 16 +++++++++------- intern/verify_2fa.php | 23 ++++++++++++++++------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/intern/login.php b/intern/login.php index 259d08d..64b3ba1 100644 --- a/intern/login.php +++ b/intern/login.php @@ -32,17 +32,19 @@ if (!empty($_POST['email']) && !empty($_POST['passwort'])) { ->execute(['uid' => $user['id']]); // 2FA-Code - $code = random_int(100000, 999999); - $codeHash = hash('sha256', $code); - $expires = date('Y-m-d H:i:s', time() + 300); + $code = random_int(100000, 999999); + $codeHash = hash('sha256', (string)$code); $pdo->prepare(" + -- Ablaufzeit bewusst per NOW() in SQL berechnen: Webserver und + -- DB-Server stehen bei verschiedenen Anbietern und koennen + -- unterschiedliche Zeitzonen haben. verify_2fa.php prueft + -- ebenfalls gegen NOW(), damit beide dieselbe Uhr benutzen. INSERT INTO intern_2fa_codes (user_id, code, expires_at) - VALUES (:uid, :code, :expires) + VALUES (:uid, :code, DATE_ADD(NOW(), INTERVAL 5 MINUTE)) ")->execute([ - 'uid' => $user['id'], - 'code' => $codeHash, - 'expires' => $expires + 'uid' => $user['id'], + 'code' => $codeHash ]); SendMailMessageSilent( diff --git a/intern/verify_2fa.php b/intern/verify_2fa.php index d8728af..98d34f0 100644 --- a/intern/verify_2fa.php +++ b/intern/verify_2fa.php @@ -15,16 +15,23 @@ $error_msg = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['code'])) { - $userId = (int)$_SESSION['2fa_userid']; - $codeHash = hash('sha256', $_POST['code']); + $userId = (int)$_SESSION['2fa_userid']; - // 2FA-Code prüfen + // Aus HTML-Mails kopierte Codes enthalten haeufig Leerzeichen oder + // geschuetzte Leerzeichen. Der Code besteht immer aus sechs Ziffern. + $eingabe = preg_replace('/\D/', '', (string)$_POST['code']); + $codeHash = hash('sha256', $eingabe); + + // Der Ablauf wird in SQL geprueft (NOW()), nicht in PHP: der Webserver + // und der DB-Server stehen bei verschiedenen Anbietern und koennen in + // unterschiedlichen Zeitzonen laufen. Wuerde die Ablaufzeit in PHP + // erzeugt und gegen NOW() verglichen, waere jeder Code sofort + // "abgelaufen". Ablauf und Tippfehler werden getrennt gemeldet. $stmt = $pdo->prepare(" - SELECT id + SELECT id, (expires_at > NOW()) AS noch_gueltig FROM intern_2fa_codes WHERE user_id = :uid AND code = :code - AND expires_at > NOW() LIMIT 1 "); $stmt->execute([ @@ -34,7 +41,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['code'])) { $row = $stmt->fetch(PDO::FETCH_ASSOC); - if ($row) { + if ($row && $row['noch_gueltig']) { // Code löschen (Einmalverwendung) $pdo->prepare("DELETE FROM intern_2fa_codes WHERE user_id = :uid") @@ -79,7 +86,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['code'])) { exit; } - $error_msg = 'Falscher oder abgelaufener Code.'; + $error_msg = $row + ? 'Der Code ist abgelaufen. Bitte melden Sie sich erneut an, damit wir Ihnen einen neuen Code schicken können.' + : 'Der eingegebene Code war nicht korrekt.'; } include("templates/header.inc.php");