Files
Smart-Dashboard/helper.php
T
adminandClaude Opus 5 dc85f33e9f ipInNetwork(): Praefixlaenge pruefen statt ungeprueft uebernehmen
Die Laenge wurde mit intval() aus dem CIDR-String uebernommen. Bei einem
Tippfehler in LOCAL_NETWORKS lief die Funktion damit fail-open: "/-5" ergibt
intdiv(-5,8)=0 ganze Bytes und chr((0xFF << 13) & 0xFF)=Nullbyte, womit beide
maskierten Bytes gleich sind und jede Adresse in jedem Netz liegt. isLocal()
haette dann jede Anfrage aus dem Internet als lokal gewertet - und damit als
angemeldet, inklusive Freigabe der Passkey-Registrierung.

Die Laenge muss jetzt aus reinen Ziffern bestehen und darf die Adressbreite
nicht ueberschreiten; sonst liefert die Funktion false. "/-5", "/244", "/abc"
und "/" fallen damit durch, gueltige Angaben bleiben unveraendert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 16:14:25 +02:00

148 lines
5.3 KiB
PHP

<?php
session_start();
require_once("restricted/mysql.php");
$_SESSION["local"] =false;
// Netze, die als lokal gelten und damit ohne Anmeldung Zugriff bekommen.
// Bewusst eine sichtbare Liste statt eines Zeichenkettenvergleichs.
const LOCAL_NETWORKS = ["192.168.179.0/24"];
/**
* Liegt $ip im Netz $cidr? Verglichen wird binaer (inet_pton), also
* unabhaengig von der Schreibweise: "2001:db8::" und "2001:0db8:0000::"
* sind dieselbe Adresse.
*/
function ipInNetwork($ip, $cidr)
{
$teile = explode("/", $cidr, 2);
$adresse = @inet_pton($ip);
$netz = @inet_pton($teile[0]);
if ($adresse === false || $netz === false || strlen($adresse) !== strlen($netz)) {
return false; // ungueltig oder verschiedene Adressfamilien
}
// Praefixlaenge streng pruefen. Ohne das macht ein Tippfehler in
// LOCAL_NETWORKS die Funktion fail-open: "/-5" etwa liefert eine
// Nullmaske, und dann liegt jede Adresse in jedem Netz.
if (isset($teile[1])) {
if (!ctype_digit($teile[1])) {
return false;
}
$bits = intval($teile[1]);
if ($bits > strlen($adresse) * 8) {
return false;
}
} else {
$bits = strlen($adresse) * 8;
}
$ganzeBytes = intdiv($bits, 8);
$restBits = $bits % 8;
if ($ganzeBytes > 0 && strncmp($adresse, $netz, $ganzeBytes) !== 0) {
return false;
}
if ($restBits === 0) {
return true;
}
$maske = chr((0xFF << (8 - $restBits)) & 0xFF);
return ($adresse[$ganzeBytes] & $maske) === ($netz[$ganzeBytes] & $maske);
}
/**
* Kommt die Anfrage aus dem Heimnetz? Nur dann gilt sie ohne Anmeldung als
* berechtigt.
*
* REMOTE_ADDR stammt aus der TCP-Verbindung und ist nicht faelschbar.
* Weiterleitungs-Header wie X-Forwarded-For duerfen hier bewusst NICHT
* herangezogen werden - sie kann jeder Aufrufer frei setzen.
*/
function isLocal()
{
$_SESSION["local"] = false;
$remote = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
$server = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '';
if (filter_var($remote, FILTER_VALIDATE_IP) === false) {
return false;
}
// Auf IPv6 abgebildete IPv4-Adressen (::ffff:192.168.179.44) auf ihre
// IPv4-Form zurueckfuehren. Ein Server mit Dual-Stack-Socket meldet
// LAN-Clients so - ohne diesen Schritt faellt das Heimnetz durch.
if (preg_match('/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/i', $remote, $treffer)) {
$remote = $treffer[1];
}
$lokal = false;
foreach (LOCAL_NETWORKS as $netz) {
if (ipInNetwork($remote, $netz)) {
$lokal = true;
break;
}
}
// Gleiches IPv6-/64 wie der Server. Das Praefix vergibt der Provider und
// es kann sich aendern, deshalb aus SERVER_ADDR abgeleitet statt fest.
if (!$lokal
&& filter_var($remote, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false
&& filter_var($server, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false) {
$lokal = substr(inet_pton($remote), 0, 8) === substr(inet_pton($server), 0, 8);
}
$_SESSION["local"] = $lokal;
return $lokal;
}
/**
* Kleine HTTP-GET-Anfrage im Heimnetz, Antwort als JSON-Array.
* Ersetzt die frueher in carOG.php, heater.php und AutoAction.php je einzeln
* definierte Funktion getSSLPage().
*/
function httpGetJson($url){
$curlSession = curl_init();
curl_setopt($curlSession, CURLOPT_URL, $url);
curl_setopt($curlSession, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlSession, CURLOPT_TIMEOUT, 5);
$response = curl_exec($curlSession);
curl_close($curlSession);
return json_decode($response, true);
}
function checkLogin(){
$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(){
$mysql = new mysqli($GLOBALS["mysql_server"],$GLOBALS["mysql_user"],$GLOBALS["mysql_pass"],$GLOBALS["mysql_db"]);
if(!mysqli_query($mysql,"DELETE FROM addUser WHERE datetime < DATE_SUB(NOW(), INTERVAL 1 MINUTE);")){
echo mysqli_error($mysql);
}
$result = mysqli_query($mysql,"SELECT * FROM addUser WHERE accesskey='".mysqli_real_escape_string($mysql,$_GET["addUser"])."';");
if(!$result){
return false;
}
if ($result->num_rows > 0) {
// Freigabe fuer die Passkey-Registrierung vermerken. authServer.php
// prueft dieses Merkmal; ohne das waeren getCreateArgs/processCreate
// direkt aufrufbar und der Einmalschluessel wirkungslos.
$_SESSION["mayRegister"] = time();
return true;
}
return false;
}
?>