Ergebnis der Durchsicht beider Bereiche (47 Dateien). Patientendaten ohne Anmeldung abrufbar -------------------------------------- admin/mailtemplate.php hatte keine Zugriffspruefung. Der Endpunkt liefert gerenderte Mailvorlagen und damit Vorname, Nachname, Geburtstag, Adresse, Medikamente, Anfragetext und den Anfragen-Link mit dem hash. Ein anonymer POST wurde bis in die Datenbank verarbeitet (geprueft mit ungueltiger templetid, es sind keine Daten geflossen). Jetzt check_admin_user() mit HTTP 401. Ausserdem ging die Exception-Meldung an den Aufrufer zurueck - sie geht jetzt ins Log. intern/meineanfragen.php filterte beim Detailaufruf ausschliesslich auf die anfrageid aus $_POST, ohne Bezug zum angemeldeten Benutzer - und setzte sie unmaskiert ins SQL. Jeder registrierte Patient konnte damit fremde Anfragen samt Geburtstag, Adresse und Telefonnummer lesen. Jetzt Prepared Statement und zusaetzlich an die E-Mail des angemeldeten Benutzers gebunden, wie in der Listenansicht derselben Datei. SQL-Injection ------------- admin/togoadmin.php: 14 Abfragen bauten $_GET/$_POST direkt in das SQL. Ganzzahlige Spalten bekommen einen (int)-Cast, damit die umgebenden mysqli-Schleifen unveraendert bleiben; alle INSERT- und UPDATE-Anweisungen mit Textwerten sind auf Prepared Statements umgestellt. create_time dort jetzt per NOW() statt PHP-date(). Ein abschliessender Scan ueber intern/, admin/ und zeiterfassung/ findet keine verkettete Nutzereingabe in SQL mehr. Zugriffspruefung ohne Wirkung ----------------------------- admin/anrufbeantworter.php und admin/kalender.php riefen check_admin_user() auf, werteten den Rueckgabewert aber nie aus. Die Funktion liefert bei fehlender Anmeldung nur null, sie bricht nicht ab - beide Seiten rendered fuer anonyme Besucher weiter. Daten flossen nicht ab, aber die Oberflaeche war sichtbar. Jetzt gleiches Muster wie admin/index.php. Tokens ohne Ablauf ------------------ Die Tabelle securitytokens hatte keine Ablaufspalte: ein erbeutetes Admin-Cookie galt unbegrenzt. Der Patientenbereich setzt 30 Tage. securitytokensHatAblaufspalte() prueft die Spalte zur Laufzeit, damit der Code vor und nach der Migration laeuft; admin/login.php setzt den Ablauf beim Anlegen, check_admin_user() beruecksichtigt ihn beim Lesen. Migration in admin/sql/. Die Cookie-Laufzeit war auf 365 Tage gesetzt und ist jetzt deckungsgleich mit dem Token. Entfernt -------- admin/phpinfo.php lieferte ohne Anmeldung 102 KB Serverkonfiguration. intern/admin.php, admin/admin.php sowie mailtemplatebody.php und mailtemplatebetreff.php in beiden Verzeichnissen waren tot (falscher relativer require-Pfad, HTTP 500) - die mailtemplate-Vorgaenger enthielten zudem rohe SQL-Injection mit Patientendaten. Keine der sechs Dateien wird irgendwo aufgerufen. admin/sql war als leere Datei statt als Verzeichnis angelegt. Kleinere Korrekturen -------------------- intern/authentifizierung.php und admin/passwortzuruecksetzen.php verglichen das Ergebnis von fetch() mit null statt false und pruefen den Ablauf des Codes jetzt in SQL statt mit strtotime()/time() - dieselbe Zeitzonenfalle wie bei den 2FA-Codes, hier in die harmlose Richtung. Ein toter password_hash()-Aufruf auf einer nie gesetzten Variablen ist entfallen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
98 lines
2.9 KiB
PHP
98 lines
2.9 KiB
PHP
<?php
|
|
session_start();
|
|
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")
|
|
?>
|