Die Pruefung zerlegte beide Adressen mit explode(":") und verglich die ersten
vier Felder. Bei IPv4 entsteht dabei nur ein Feld, die uebrigen drei sind
undefiniert und null == null ist wahr - die Bedingung schrumpfte damit auf
REMOTE_ADDR == SERVER_ADDR. Kaeme je ein Reverse Proxy auf denselben Host,
haette das jeden Zugriff aus dem Internet als lokal gelten lassen.
Neu:
- LOCAL_NETWORKS als sichtbare Liste, derzeit 192.168.179.0/24.
- ipInNetwork() vergleicht binaer ueber inet_pton, also unabhaengig von der
Schreibweise. "2001:0db8:a:b::9" gilt jetzt zurecht als dasselbe /64 wie
"2001:db8:a:b::9" - vorher fiel dieser Client durch.
- Das IPv6-/64 wird weiterhin aus SERVER_ADDR abgeleitet, da der Provider das
Praefix vergibt.
- REMOTE_ADDR wird mit filter_var geprueft; "192.168.179.44 evil" galt vorher
wegen str_starts_with als lokal.
- Auf IPv6 abgebildete IPv4-Adressen (::ffff:192.168.179.44) werden
normalisiert. Ein Dual-Stack-Socket meldet LAN-Clients so; bisher fielen
sie durch.
- $_SESSION["local"] wird in beiden Zweigen gesetzt, nicht nur im positiven.
Verhaltensaenderung: Anfragen, deren Absender gleich SERVER_ADDR ist - etwa
127.0.0.1 - gelten nicht mehr automatisch als lokal. Im Repo ruft nichts die
Seite ueber HTTP vom Server selbst auf. Wird das doch gebraucht, gehoert das
betreffende Netz in LOCAL_NETWORKS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
135 lines
4.9 KiB
PHP
135 lines
4.9 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
|
|
}
|
|
$bits = isset($teile[1]) ? intval($teile[1]) : 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;
|
|
}
|
|
|
|
?>
|