Compare commits

...
4 Commits
Author SHA1 Message Date
adminandClaude Opus 5 768520cedd Geraete-Endpunkte: Anmeldung voraussetzen
actorDetails.php, sensorDetails.php, fillActorDD.php und fillSensorDD.php
fragten die homeMesh-Datenbank ab und gaben Geraetenamen samt Steuer-URLs
(command_url, command_parameters.url) aus - ohne jede Zugriffspruefung.

Alle vier pruefen jetzt checkLogin(), bevor sie etwas ausgeben. Aufgerufen
werden sie ausschliesslich aus js/solar/autoActionFuncs.js auf der Home-Seite,
also aus einer angemeldeten Sitzung; der Regeleditor funktioniert unveraendert.

Die SQL war bereits sauber (intval), es fehlte nur die Autorisierung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 15:37:49 +02:00
adminandClaude Opus 5 0075f803d0 tahoma.php: Anmeldung voraussetzen
Die Datei hatte keinerlei Zugriffspruefung. Damit liessen sich von beliebiger
Stelle die Somfy-Geraete auflisten (?action=devlist) sowie Rollladen abfragen
und verfahren (?action=move&device=...&pos=...&angle=...). Das Tahoma-Token
liegt serverseitig, ein Angreifer brauchte es also nicht.

checkLogin() ist jetzt vorgeschaltet, dafuer wird helper.php eingebunden. Im
Frontend gibt es derzeit keinen Aufrufer der Datei, ein bestehender Ablauf
kann also nicht brechen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 15:37:48 +02:00
adminandClaude Opus 5 464c08f414 carEG.php: Befehlseinschleusung ueber das Zeitfeld schliessen
$_POST["ftt"] wurde nur auf "nicht leer" geprueft und dann roh in die
exec()-Kommandozeile gehaengt. Ein Wert wie "12:00 2>&1; befehl #" fuehrte
damit beliebige Shell-Befehle als Web-Benutzer aus.

Das Formularfeld ist ein <input type="time">, liefert also HH:MM. Der Wert
wird jetzt gegen genau dieses Format geprueft; alles andere gilt als "keine
Ladeplanung" und nimmt den vorhandenen else-Zweig. Zusaetzlich gehen ftt und
setTime durch escapeshellarg().

fte und evAmp waren durch die Vergleichs-Klemmung bereits faktisch numerisch,
laufen jetzt aber ueber intval() und eigene Variablen statt ueber $_POST.
Fuer gueltige Eingaben ist die erzeugte Kommandozeile unveraendert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 15:35:59 +02:00
adminandClaude Opus 5 49ff6b5c0b authServer.php: Passkey-Registrierung gegen fremde Aufrufe absichern
getCreateArgs und processCreate waren ohne jede Pruefung erreichbar. Der
Einmalschluessel aus addUser.php steuerte nur, ob index.php die
Registrierungsseite anzeigt - authServer.php liess sich direkt aufrufen und
umging das. Da userId und userName fest hinterlegt sind, konnte damit jeder
mit zwei Requests einen eigenen Passkey fuer das vorhandene Konto anlegen.

checkAdduser() vermerkt eine erfolgreiche Schluesselpruefung jetzt als
$_SESSION["mayRegister"]. authServer.php laesst getCreateArgs, processCreate
und clearRegistrations nur noch durch, wenn dieser Vermerk hoechstens zehn
Minuten alt ist oder der Aufruf aus dem lokalen Netz kommt.

Anmeldung und Metadatenabfrage (getGetArgs, processGet,
queryFidoMetaDataService) bleiben unveraendert offen.

authServer.php bindet dafuer helper.php ein statt nur restricted/mysql.php;
das eigene session_start() entfaellt, helper.php erledigt es.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 15:34:11 +02:00
8 changed files with 87 additions and 17 deletions
+7
View File
@@ -1,5 +1,12 @@
<?php <?php
require_once("../helper.php"); require_once("../helper.php");
// Der Endpunkt gibt Geraetenamen und Steuer-URLs aus der homeMesh-
// Datenbank aus und war bisher ohne Anmeldung erreichbar.
if (!checkLogin()) {
http_response_code(403);
exit;
}
if(isset($_GET["actorID"])){ if(isset($_GET["actorID"])){
$actorID = intval($_GET["actorID"]); $actorID = intval($_GET["actorID"]);
$qry = "SELECT command_name, command_url, parameter_type, min_value, max_value, possible_values, command_parameters.url AS parameter_url, parameter_name FROM actor_commands LEFT JOIN command_parameters ON actor_commands.id = command_id LEFT JOIN state_types ON parameter_type = state_types.id WHERE actor_id=".$actorID; $qry = "SELECT command_name, command_url, parameter_type, min_value, max_value, possible_values, command_parameters.url AS parameter_url, parameter_name FROM actor_commands LEFT JOIN command_parameters ON actor_commands.id = command_id LEFT JOIN state_types ON parameter_type = state_types.id WHERE actor_id=".$actorID;
+28 -15
View File
@@ -16,35 +16,48 @@ if (checkLogin()) {
} }
$close = 1; $close = 1;
} else if (isset($_POST["evMode"])) { } else if (isset($_POST["evMode"])) {
if ($_POST["fte"] > 100) // Eingaben pruefen, bevor sie in die Kommandozeile gehen. ftt kam bisher
$_POST["fte"] = 100; // ungeprueft aus dem Formular und liess sich mit "12:00; befehl" zum
else if ($_POST["fte"] < 1) // Anhaengen beliebiger Shell-Befehle missbrauchen.
$_POST["fte"] = 1; $fte = intval($_POST["fte"]);
if ($fte > 100)
$fte = 100;
else if ($fte < 1)
$fte = 1;
$fte = $fte * 140;
if ($_POST["evAmp"] > 16) $evAmp = intval($_POST["evAmp"]);
$_POST["evAmp"] = 16; if ($evAmp > 16)
if ($_POST["evAmp"] < 6) $evAmp = 16;
$_POST["evAmp"] = 6; if ($evAmp < 6)
$_POST["fte"] = $_POST["fte"] * 140; $evAmp = 6;
// Das Formularfeld ist ein <input type="time">, liefert also HH:MM.
// Alles andere gilt als "keine Ladeplanung" und faellt in den else-Zweig.
$ftt = "";
if (isset($_POST["ftt"]) && preg_match('/^([01][0-9]|2[0-3]):[0-5][0-9]$/', $_POST["ftt"])) {
$ftt = $_POST["ftt"];
}
$setTime = gmdate("Y-m-d\TH:i:s", time()) . ".000";
if ($_POST["evMode"] == "eco") { if ($_POST["evMode"] == "eco") {
$close = 1; $close = 1;
if ($_POST["ftt"] && $_POST["fte"] > 0) { if ($ftt !== "" && $fte > 0) {
exec("python wattpilot.py -eco -time " . $_POST["ftt"] . " -energy " . $_POST["fte"] . " -maxCurr " . $_POST["evAmp"] . " -setTime " . gmdate("Y-m-d\TH:i:s", time()) . ".000 2>&1", $output, $return_var); exec("python wattpilot.py -eco -time " . escapeshellarg($ftt) . " -energy " . $fte . " -maxCurr " . $evAmp . " -setTime " . escapeshellarg($setTime) . " 2>&1", $output, $return_var);
} else { } else {
exec("/bin/python wattpilot.py -e 2>&1", $output, $return_var); exec("/bin/python wattpilot.py -e 2>&1", $output, $return_var);
} }
} else if ($_POST["evMode"] == "default") { } else if ($_POST["evMode"] == "default") {
$close = 1; $close = 1;
if ($_POST["ftt"] && $_POST["fte"] > 0) { if ($ftt !== "" && $fte > 0) {
exec("python wattpilot.py -default -time " . $_POST["ftt"] . " -energy " . $_POST["fte"] . " -maxCurr " . $_POST["evAmp"] . " -setTime " . gmdate("Y-m-d\TH:i:s", time()) . ".000 2>&1", $output, $return_var); exec("python wattpilot.py -default -time " . escapeshellarg($ftt) . " -energy " . $fte . " -maxCurr " . $evAmp . " -setTime " . escapeshellarg($setTime) . " 2>&1", $output, $return_var);
} else { } else {
exec("python wattpilot.py -d 2>&1", $output, $return_var); exec("python wattpilot.py -d 2>&1", $output, $return_var);
} }
} else if ($_POST["evMode"] == "NextTrip") { } else if ($_POST["evMode"] == "NextTrip") {
$close = 1; $close = 1;
if ($_POST["ftt"] && $_POST["fte"] > 0) { if ($ftt !== "" && $fte > 0) {
exec("python wattpilot.py -trip -time " . $_POST["ftt"] . " -energy " . $_POST["fte"] . " -maxCurr " . $_POST["evAmp"] . " -setTime " . gmdate("Y-m-d\TH:i:s", time()) . ".000 2>&1", $output, $return_var); exec("python wattpilot.py -trip -time " . escapeshellarg($ftt) . " -energy " . $fte . " -maxCurr " . $evAmp . " -setTime " . escapeshellarg($setTime) . " 2>&1", $output, $return_var);
} else { } else {
exec("python wattpilot.py -t 2>&1", $output, $return_var); exec("python wattpilot.py -t 2>&1", $output, $return_var);
} }
+7
View File
@@ -1,5 +1,12 @@
<?php <?php
require_once("../helper.php"); require_once("../helper.php");
// Der Endpunkt gibt Geraetenamen und Steuer-URLs aus der homeMesh-
// Datenbank aus und war bisher ohne Anmeldung erreichbar.
if (!checkLogin()) {
http_response_code(403);
exit;
}
$qry = "SELECT name, actors.id FROM actors INNER JOIN actor_commands ON actor_id = actors.id GROUP BY actors.id"; $qry = "SELECT name, actors.id FROM actors INNER JOIN actor_commands ON actor_id = actors.id GROUP BY actors.id";
$mysql = new mysqli($mysql_server, $mysql_MeshUser, $mysql_MeshPass, $mysql_MeshDB); $mysql = new mysqli($mysql_server, $mysql_MeshUser, $mysql_MeshPass, $mysql_MeshDB);
$result = mysqli_query($mysql, $qry); $result = mysqli_query($mysql, $qry);
+7
View File
@@ -1,5 +1,12 @@
<?php <?php
require_once("../helper.php"); require_once("../helper.php");
// Der Endpunkt gibt Geraetenamen und Steuer-URLs aus der homeMesh-
// Datenbank aus und war bisher ohne Anmeldung erreichbar.
if (!checkLogin()) {
http_response_code(403);
exit;
}
$qry = "SELECT name, actors.id FROM actors INNER JOIN actor_states ON actor_id = actors.id GROUP BY actors.id"; $qry = "SELECT name, actors.id FROM actors INNER JOIN actor_states ON actor_id = actors.id GROUP BY actors.id";
$mysql = new mysqli($mysql_server, $mysql_MeshUser, $mysql_MeshPass, $mysql_MeshDB); $mysql = new mysqli($mysql_server, $mysql_MeshUser, $mysql_MeshPass, $mysql_MeshDB);
$result = mysqli_query($mysql, $qry); $result = mysqli_query($mysql, $qry);
+7
View File
@@ -1,5 +1,12 @@
<?php <?php
require_once("../helper.php"); require_once("../helper.php");
// Der Endpunkt gibt Geraetenamen und Steuer-URLs aus der homeMesh-
// Datenbank aus und war bisher ohne Anmeldung erreichbar.
if (!checkLogin()) {
http_response_code(403);
exit;
}
if(isset($_GET["sensorID"])){ if(isset($_GET["sensorID"])){
$sensorID = intval($_GET["sensorID"]); $sensorID = intval($_GET["sensorID"]);
$qry = "SELECT state_name, url, state_types.type, unit, current_value, possible_values FROM actor_states LEFT JOIN state_types ON state_type = state_types.id WHERE actor_id=".$sensorID; $qry = "SELECT state_name, url, state_types.type, unit, current_value, possible_values FROM actor_states LEFT JOIN state_types ON state_type = state_types.id WHERE actor_id=".$sensorID;
+9
View File
@@ -1,7 +1,16 @@
<?php <?php
set_time_limit(60); set_time_limit(60);
ob_start(); ob_start();
require_once("../helper.php");
require_once("../restricted/tahoma_EG.php"); require_once("../restricted/tahoma_EG.php");
// Ohne diese Pruefung liessen sich Rollladen von beliebiger Stelle abfragen
// und verfahren - das Tahoma-Token liegt serverseitig, ein Angreifer braucht
// es also gar nicht.
if (!checkLogin()) {
http_response_code(403);
exit;
}
//67ebd23e3a61763386d9 //67ebd23e3a61763386d9
function getSSLPage($url, $tahoma_token) function getSSLPage($url, $tahoma_token)
{ {
+18 -2
View File
@@ -35,12 +35,28 @@
*/ */
require_once './restricted/WebAuthn/src/WebAuthn.php'; require_once './restricted/WebAuthn/src/WebAuthn.php';
require_once("./restricted/mysql.php"); require_once("./helper.php"); // startet die Session, liefert isLocal() und mysql.php
try { try {
session_start();
// read get argument and post body // read get argument and post body
$fn = filter_input(INPUT_GET, 'fn'); $fn = filter_input(INPUT_GET, 'fn');
// Registrierung und Loeschen von Anmeldedaten nur nach einem gueltigen
// Einmalschluessel aus addUser.php (checkAdduser setzt mayRegister) oder
// aus dem lokalen Netz. Ohne diese Wache genuegen zwei Aufrufe von
// beliebiger Stelle, um einen eigenen Passkey fuer das fest hinterlegte
// Konto anzulegen - der Einmalschluessel schuetzte nur die Anzeige in
// index.php, nicht diesen Endpunkt.
if ($fn === 'getCreateArgs' || $fn === 'processCreate' || $fn === 'clearRegistrations') {
$freigegeben = isset($_SESSION['mayRegister'])
&& (time() - $_SESSION['mayRegister']) < 600;
if (!$freigegeben && !isLocal()) {
http_response_code(403);
header('Content-Type: application/json');
print(json_encode(['success' => false, 'msg' => 'Registrierung nicht freigegeben.']));
exit;
}
}
$requireResidentKey = false; $requireResidentKey = false;
$userVerification = false; $userVerification = false;
$formats = []; $formats = [];
+4
View File
@@ -59,6 +59,10 @@ function checkAdduser(){
return false; return false;
} }
if ($result->num_rows > 0) { 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 true;
} }
return false; return false;