Die Abfrage in aktion=10 stammte noch aus der Zeit der user-Tabelle und
selektierte p.mail, p.jahrgang sowie a.timeid. Keine dieser Spalten
existiert: persons fuehrt email und geburtstag, anfragen hat kein timeid.
Die Abfrage warf damit eine PDOException, und da der Block kein try/catch
hat, brach das Skript vor dem UPDATE ab - "Anforderung loeschen (ohne
Mail)" war komplett wirkungslos.
Spaltennamen korrigiert und die uebrig gebliebene Debug-Ausgabe von
timeid entfernt, die als einzige Stelle den Wert genutzt hat. Ein Scan
aller persons-Aliasse gegen das Schema zeigt keine weiteren Fundstellen.
Der Fehlertext beim fehlgeschlagenen Mailversand in aktion=3 behauptete,
es sei nichts gespeichert worden. Tatsaechlich laeuft das UPDATE vorher.
Die Meldung benennt jetzt den echten Zustand und bittet darum, den
Vorgang erneut auszufuehren.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Button "Telefonisch beantwortet" (aktion=20) hat bisher die Mailvorlage
42 an die private Mailadresse des Patienten geschickt. Telefonisch erledigte
Anfragen duerfen die Praxis nie per Mail verlassen, der Versand entfaellt
daher ersatzlos.
Die Bestaetigungstexte benennen jetzt den tatsaechlichen Vorgang statt
faelschlich von einer Loeschung mit schriftlicher Mailbestaetigung zu
sprechen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>