Files
adminandClaude Opus 5 7dd6580bcc Sicherheit: Erkennung des lokalen Netzes ueber konfigurierte Netze
isLocal() hielt jede Adresse aus 192.168.147.* fuer lokal. Das ist das
Docker-Netz auf dem NAS: Anfragen ueber den Docker- oder einen Reverse
Proxy tragen dessen Adresse und waeren damit als lokal durchgegangen
(Passwort-Login und Passkey-Freigabe von aussen). Der IPv6-Vergleich per
explode(":") scheiterte ausserdem an komprimierten Adressen.

- lokale Netze stehen als CIDR-Liste $lokale_netze in der externen Config
- ipImNetz() vergleicht IPv4/IPv6 korrekt per inet_pton, inkl. ::ffff:-Form
- Anfragen mit Proxy-Headern gelten nie als lokal
- ohne Konfiguration nur noch dasselbe IPv6-/64 wie der Server, kein IPv4

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

126 lines
4.3 KiB
PHP

<?php
require_once("settings.php");
$settings=readsettings();
if($settings != "valid")
$action = "setup";
if($settings == "valid")
{
if(!$mysql = @mysqli_connect($mysql_server, $mysql_user, $mysql_pw))
$err = error("Verbindung zur Datenbank fehlgeschlagen!!");
if(!@mysqli_select_db($mysql,$mysql_db))
$err = error("Datenbank nicht gefunden!! ($mysql_db)");
}
$_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;
}
function checkLogin(){
global $mysql;
//$mysql = new mysqli($GLOBALS["mysql_server"],$GLOBALS["mysql_user"],$GLOBALS["mysql_pass"],$GLOBALS["mysql_db"]);
if(isLocal()){
$_SESSION["local"] =true;
return true;
}
if(isset($_SESSION["authKey"])){
$res = mysqli_query($mysql,"SELECT id FROM users WHERE lastAuth > DATE_SUB(NOW(), INTERVAL 2 DAY) AND authKey = '".mysqli_real_escape_string($mysql,$_SESSION["authKey"])."' AND name = '".mysqli_real_escape_string($mysql,$_SESSION["user"])."';");
if(!$res){
echo mysqli_error($mysql);
return false;
}
if(mysqli_num_rows($res) == 1){
return isset($_SESSION["Logged"]);
}
}else{
return false;
}
}
function checkAdduser(){
global $mysql;
//$mysql = new mysqli($GLOBALS["mysql_server"],$GLOBALS["mysql_user"],$GLOBALS["mysql_pass"],$GLOBALS["mysql_db"]);
if(!mysqli_query($mysql,"DELETE FROM addKey WHERE datetime < DATE_SUB(NOW(), INTERVAL 1 MINUTE);")){
echo mysqli_error($mysql);
}
$result = mysqli_query($mysql,"SELECT * FROM addKey WHERE accesskey='".mysqli_real_escape_string($mysql,$_GET["addKey"])."';");
if(!$result){
return false;
}
if ($result->num_rows > 0) {
return true;
}
return false;
}
?>