- 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>
100 lines
3.5 KiB
PHP
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;
|
|
}
|
|
|
|
?>
|