Files
adminandClaude Opus 5 6306bfba17 Login und Passkeys auf Prepared Statements umgebaut
- sites/start.php: ein Query statt zwei, Passwortvergleich per hash_equals
  in PHP statt im SQL, Updates per id. Die Eingaben gehen nicht mehr durch
  htmlspecialchars(): Passwoerter werden beim Anlegen roh gehasht, mit
  & < > " ' konnte man sich deshalb bisher nie anmelden.
- getUserSetting/updateUserSetting mit Platzhaltern; behoben: INSERT-Zweig
  rief mysqli_real_escape_string ohne Verbindung auf (Fatal Error),
  $mysqli_error als Variable, ungewolltes echo "update". Aufrufer escapen
  nicht mehr vor (sonst doppelt), settings.php: Tippfehler
  mysqi_real_escape_string beim Speichern von show_sks behoben, style nur
  als Dateiname ohne Pfad.
- authServer.php, addKey.php, userkeys.php, checkAdduser(): Platzhalter.
  Passkey-INSERT setzt lastAuth/authKey (Pflichtfelder im strikten Modus).
  addKey nur als reine Ziffern, sonst passte "12345678xyz" per Typumwandlung.
- checkLogin() entfernt: nirgends aufgerufen, fragte die nicht existierende
  Tabelle users ab.
- userkeys.tpl: Passkey-Name escaped.

Getestet gegen die migrierte Testdatenbank im strikten Modus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 10:24:17 +02:00

100 lines
3.5 KiB
PHP

<?php
require_once("settings.php");
$settings=readsettings();
if($settings != "valid")
$action = "setup";
if($settings == "valid")
{
if(!$mysql = db_verbinden($mysql_server, $mysql_user, $mysql_pw, $mysql_db))
$err = error("Verbindung zur Datenbank fehlgeschlagen: ".htmlspecialchars(mysqli_connect_error()));
}
$_SESSION["local"] =false;
/*
* Liegt $ip im Netz $netz (CIDR-Schreibweise, IPv4 oder IPv6)? Eine Adresse
* ohne /Präfix gilt als einzelner Host.
*/
function ipImNetz($ip, $netz)
{
if(!str_contains($netz, "/"))
$netz .= str_contains($netz, ":") ? "/128" : "/32";
list($adresse, $praefix) = explode("/", $netz, 2);
$ipBin = @inet_pton($ip);
$netzBin = @inet_pton($adresse);
if($ipBin === false || $netzBin === false || !ctype_digit($praefix))
return false;
// IPv4 in IPv6-Schreibweise (::ffff:192.168.1.2) wie IPv4 behandeln
if(strlen($ipBin) == 16 && strlen($netzBin) == 4 && substr($ipBin, 0, 12) === str_repeat("\0", 10)."\xff\xff")
$ipBin = substr($ipBin, 12);
$praefix = (int)$praefix;
if(strlen($ipBin) != strlen($netzBin) || $praefix > strlen($ipBin) * 8)
return false;
$bytes = intdiv($praefix, 8);
if(substr($ipBin, 0, $bytes) !== substr($netzBin, 0, $bytes))
return false;
$bits = $praefix % 8;
if($bits == 0)
return true;
$maske = (0xFF << (8 - $bits)) & 0xFF;
return (ord($ipBin[$bytes]) & $maske) == (ord($netzBin[$bytes]) & $maske);
}
/*
* Kommt die Anfrage aus dem lokalen Netz? Davon hängen Passwort-Login und das
* Vergeben von Passkeys ab.
*
* Maßgeblich ist $lokale_netze aus der externen Config, z.B.
* $lokale_netze = array("192.168.179.0/24", "2001:db8:1:2::/64");
* Fehlt die Liste, gelten nur Geräte im selben IPv6-/64-Netz wie der Server.
*
* Das Docker-Netz darf dort nicht stehen: Anfragen, die über den Docker-
* Proxy oder einen Reverse Proxy kommen, tragen dessen Adresse aus diesem
* Netz - dann wäre jeder Zugriff aus dem Internet "lokal".
*/
function isLocal(){
global $lokale_netze;
$_SESSION["local"] = false;
// Über einen Proxy weitergereicht: REMOTE_ADDR ist dann die Adresse des
// Proxys, nicht die des Browsers. Solche Anfragen sind nie lokal.
foreach(array("HTTP_X_FORWARDED_FOR", "HTTP_X_REAL_IP", "HTTP_FORWARDED", "HTTP_X_FORWARDED_HOST", "HTTP_CLIENT_IP") as $kopf){
if(!empty($_SERVER[$kopf]))
return false;
}
$remote = $_SERVER["REMOTE_ADDR"] ?? "";
$lokal = false;
if(is_array($lokale_netze)){
foreach($lokale_netze as $netz){
if(ipImNetz($remote, $netz)){
$lokal = true;
break;
}
}
}else{
$server = $_SERVER["SERVER_ADDR"] ?? "";
$lokal = str_contains($server, ":") && !str_starts_with(strtolower($server), "fe80") && ipImNetz($remote, $server."/64");
}
$_SESSION["local"] = $lokal;
return $lokal;
}
/* Ist $_GET["addKey"] eine gültige, höchstens eine Minute alte Freigabe? */
function checkAdduser(){
global $mysql;
db_abfrage($mysql, "DELETE FROM addKey WHERE datetime < DATE_SUB(NOW(), INTERVAL 1 MINUTE)") or print(mysqli_error($mysql));
// Nur reine Ziffern: accesskey ist eine Zahlenspalte, MariaDB würde
// "12345678xyz" beim Vergleich stillschweigend zu 12345678 machen.
$key = $_GET["addKey"] ?? "";
if(!is_string($key) || !ctype_digit($key))
return false;
$anzahl = db_wert($mysql, "SELECT COUNT(*) FROM addKey WHERE accesskey = ?", array((int)$key));
return $anzahl > 0;
}
?>