Sudo Befehle mit PHP absetzen

  • Hi!


    ich stehe gerade auf dem Schlauch.
    Ich bastele gerade an einer kleinen Weboberfläche, womit ich Shell-Befehle ausführen kann. Hier speziell Befehle zur Ansteuerung einer Steckdosenleiste.


    Ich habe also meine html-Datei mit den Form-Elementen, die dann eine php-Datei mit dem entsprechenden php-Befehlen ausführt.


    Der php-Befehl ist: sudo sispmctl -o 1 und würde dann per shell_exec() ausgeführt werden.
    Allerdings darf anscheinend der apache-user nicht den sudo-Befehl ausführen.


    Ich würde den Befehl sonst in eine Datei mit folgendem Inhalt schreiben:
    #!/bin/sh
    sudo sispmctl -o 1



    Was muss ich noch mit dieser Datei machen, damit sie von der php-Datei ausgeführt werden kann?


    Danke und Gruß

  • Damit der www-data Benutzer vom apache sudo Befehle ausführen darf, musst du ihm das über /etc/sudoers ( Befehl: visudo ) erlauben... Allerdings musst du dann jeden Befehl wie zB " ls " in eine Liste mit dem absoluten Pfad eintragen oder stumpf alles erlauben was ich persönlich aber nicht so gut finde da unsicher..


    Guck dir mal das im Spoiler von diesem Beitrag an, da findest du eine eigentlich recht einfache Lösung ;)

  • Danke.
    Also muss ich über visudo nur ganz unten folgendes eintragen?:
    www-data ALL=NOPASSWD:/pfad/zum/script/meinscript.sh


    Muss die meinscript.sh noch irgendwie mit chmod berechtigt werden?

  • Dann kannst du ohne Passwort nur das gezeigte Script ausführen, was jedoch noch nichts über die darin enthaltenen Befehle aussagt. Die müsstest du auch explizit erlauben.

  • Sobald du ein Script über sudo ausführst werden die dadrin enthaltenen Befehle auch mit root rechten ausgeführt. Alle Befehle die sich im sudowebscript.sh befinden werden mit root-Rechten ausgeführt, da der Aufruf von sudowebscript.sh ja über sudo stattfindet. Im sudowebscript.sh Script brauch man also nicht nochmals sudo verwenden.


    [hr]


    Wichtige Anmerkungen vorab:

    • Es hat gute Gründe wieso nicht jeder Benutzer einfach so sudo verwenden darf. Ihr solltet euch also wirklich gut überlegen ob das so klug ist. Insbesondere da das ein riesiges Sicherheitsrisiko darstellen kann sofern der Pi übers Internet ansprechbar ist - nicht nur für den Pi sondern für alle in LAN befindlichen Geräte!
    • Tragt nicht Befehle direkt in visudo ein sondern nur das sudowebscript und sprecht dieses dann über selbst definierte Parameter an, die dann im Script entsprechend behandelt werden. Das macht es für euch leichter und für Angreifer schwieriger.
    • Es ist ebenfalls wichtig das Script, welches über sudo ausgeführt wird, nicht direkt im DocumentRoot abzulegen, denn dann könnte jemand diese Datei direkt auslesen und weiß sofort welche Befehle er für seinen Angriff nutzen kann.
    • Wenn ihr an dem sudowebscript.sh nichts mehr verändern wollt bzw damit fertig seid, ist es wichtig das ihr die Dateirechte so verändert dass kein Benutzer (auch nicht der Besitzer) die Datei verändern kann! Dh ihr entfernt die Schreibrechte (w) und sorgt damit für noch ein bisschen mehr Sicherheit, denn dann kann kein Angreifer das Script manipulieren.


      Code
      chmod -w /var/sudowebscript.sh



    [hr]


    Wie gesagt, das einfachste und zugleich sicherste ist ein Script /var/sudowebscript.sh in /etc/sudoers einzutragen - verwende dafür auf jeden Fall den Befehl visudo damit die Syntax verifiziert wird, bevor die original Datei überschrieben wird. Wenn du für visudo anstatt vi lieber nano verwenden willst, führst du ein malig vorher folgendes aus: export EDITOR=nano

    Code
    www-data ALL=NOPASSWD:/var/sudowebscript.sh


    Dann das Script ausführbar machen: chmod +x /var/sudowebscript.sh


    In dem Script durch ein case verschiedene Befehle definieren, zB für dein obiges Beispiel:

    Bash
    #!/bin/bash
    #
    # sudo web script allowing user www-data to run commands with root privilegs
    case "$1" in
    sispmctl)
    sispmctl -o 1
    ;;
    *) echo "ERROR: invalid parameter: $1 (for $0)"; exit 1 ;;
    esac
    exit 0

    (ein case ist vergleichbar mit einer if Anweisung/Schleife)


    Und in deinem PHP Script rufst du dann folgendes auf:

    PHP
    exec('sudo /var/sudowebscript.sh sispmctl', $output, $return_var);

    Wenn der Befehl eine Ausgabe hat wird das ins array $output hinterlegt.
    Gab es einen anderen Exitcode als 0 wird das in $return_var hinterlegt (ansonsten ist das leer oder enthält 0 für Erfolgreich)
    Siehe dazu auch http://php.net/manual/de/function.exec.php
    Wenn du dann später mehr Befehle verwenden möchtest brauchst du das nur im sudowebscript.sh ergänzen... /etc/sudoers kann so bleiben wie es ist ;)
    Also zB:


    Oder das sudowebscript.sh kann auch so aussehen:



    exec(); Vs. shell_exec();
    Führt man einen Konsolen Befehl aus wird immer ein sog. Exitcode generiert. Erfolgreich und ohne Fehler ist immer ein Exitcode von 0. Fehler sind immer mind. 1 oder höher, je nachdem was für ein Fehler oder Problem.
    exec(); bietet hierfür die Möglichkeit diesen Exitcode gesondert zu behandeln, shell_exec(); bietet dafür aber keinerlei Möglichkeit!
    Würde man die Ausgabe von shell_exec(); direkt in eine Variable schreiben lassen, wäre der einzig mögliche Hinweis das der Befehl evtl. nicht erfolgreich war, dass false zurückgegeben wurde. Anders lässt sich bei shell_exec(); kein Fehler feststellen.
    Bei exec(); gibt es wie gesagt eine optionale Möglichkeit nicht nur die mögliche Ausgabe des Befehls weiter zu verarbeiten, sondern auch diesen Exitcode.
    Wenn man also über PHP einen Konsolen Befehl ausführt, welcher aber nicht funktioniert - lässt man sich den Exitcode ausgeben anhand dessen man feststellen könnte Wieso der Befehl nicht funktioniert - Details hierzu folgen in diesem Beitrag weiter unten.
    Außerdem wird bei exec(); jede ausgegebene Zeile des Befehls in ein Array hinterlegt, bei shell_exec(); nur die letzte Zeile...


    PHP
    exec('sudo /var/sudowebscript.sh reb', $output, $return_var);
    if (isset($return_var) AND $return_var == "126") { echo "<b>ERROR: Command found but not executable (Permission problem)!</b><br/>\n"; }
    if (isset($return_var) AND $return_var == "127") { echo "<b>ERROR: Command not found (Possible problem with PATH or a typo)</b><br/>\n"; }
    if (isset($output) AND !empty($output)) {
    foreach ($output AS $line) {
    echo $line."<br/>\n";
    }
    }

    [an=ErrorHandling][/an]Oder ein etwas besseres Error Handling für PHP:


    Wer stattdessen in Echtzeit die Ausgabe eines Befehls haben möchte, sollte popen(); anstatt exec(); verwenden. Ich habe mir zu diesem Zweck eine eigene function geschrieben die ich dann nur noch (und auch mehrmals) aufrufen brauche:


    Der Aufruf erfolgt dann so:

    PHP
    _exec("df -h", "OK");
    _exec("sudo /var/sudowebscript.sh sispmctl");
    _exec("cd /usr/src/ffmpeg && make");


    //EDIT: Hier eine bessere exitcode(); Anweisung:


    Oder noch umfangreicher:


    [hr]
    Bezüglich GPIO: Anleitung zum schalten von GPIO
    Der Benutzer 'pi' ist Standardmäßig Mitglied in der Gruppe 'gpio' und hat daher Zugriff auf die virtuellen Dateien /sys/class/gpio/ ... Der Webserver läuft aber als Benutzer 'www-data' und ist nicht Mitglied in dieser speziellen Gruppe. Um das zu ändern muss man also den 'www-data' Benutzer der Gruppe 'gpio' hinzufügen und den Webserver neu starten:

    Code
    sudo usermod -G gpio -a www-data
    sudo service apache2 restart


    Siehe dazu auch meine GPIO.php oder GPIO_Status.php



    //EDIT: Noch weitere evtl. hilfreiche Erklärungen zu dieser Thematik:
    http://www.forum-raspberrypi.d…llen?pid=224855#pid224855
    http://www.forum-raspberrypi.d…gpio?pid=247409#pid247409 => sofortige Ausgaben des Puffers

  • Toll, Danke für Deine ausführliche Antwort.
    Das probiere ich morgen gleich einmal aus, jetzt bin ich reif für das Bett...

  • Ich habe nach langem Googlen und probieren immer noch Probleme ich habe Lightshowpi installiert. Dieses läuft soweit auch ich kann mit dem Befehl "/home/pi/lightshowpi/bin/start_music_and_lights" das ganze starten. Jetzt möchte ich das über PHP starten.


    Dazu habe ich zuerst in der /etc/sudoers folgenden Eintrag ergänzt:
    www-data ALL=NOPASSWD:/home/pi/lightshowpi/bin/start_music_and_lights


    Anschließend eine PHP Datei erstellt (/var/www/turnon.php) mit folgendem Inhalt:
    <?php
    shell_exec("/home/pi/lightshowpi/bin/start_music_and_lights", $output, $return_var);
    ?>


    Es passiert jedoch nichts. Langsam verzweifle ich an der Problematik :(

  • Bevor du einfach mit deinem individuellen Problem in irgendeinem Thread postest, wäre es ratsam diesen Thread erst mal zu lesen!


    ansonsten googelst du bitte nach: php shell_exec
    und stellst hoffentlich fest das es dort nur einen parameter gibt!
    ergo: Lese diesen Thread und verwende die passende Anweisung mit den jeweiligen Parametern!

  • Die Idee ist im großen und ganzen gut, man sollte aber nur Zahlen als Parameter für vom Webserver ausführbare Scripte nutzen.
    Diese kann man im dann aufgerufenen Script besser parsen.
    So sollte man als ersten den Parameter mit dem "bc" in eine echte Zahl umwandeln,. Indem man die Zahl 0 addiert.
    Wurde eine Zahl übergeben, bleibt dieser erhalten, wurde ein String übergeben, der nicht als Zahl umgewandelt werden konnte, hat man als weiter zu verarbeitende Zahl die Ziffer '0'. Und das kann man gleich als 'illegaler Parameter' auswerten.
    BC kann nur Integer, aber das reicht als Übergabeparameter.


    Worte, so auch im Beispiel, kann man nicht sauber parsen. Und wenn der Webserver das Aufruft, könnte, durch einen Fehler oder einen boshaften Benutzer, dort auch etwas als Parameter übergeben worden sein, das eine Lücke in einem Programm ausnutzt und den Server in einen 'unerwünschten' Zustand bring.


    (ALLE Werte, die von außen an einem Dienst übergeben werden, sollten geprüft werden. solange man die Sicherheit und Einhaltung der übergebenen Werte nicht garantieren kann.)

    Selber denken,
    wie kann man nur?

  • Das ist Ansichtssache.


    Man kann sehr wohl problemlos "Worte" parsen.


    Durch die Methode aus Beitrag#6 besteht auch keinerlei Gefahr, da der Übergabeparameter in keinster Weise weiter verwendet wird.


    Und dein letzter Satz ist selbstverständlich immer der Fall, egal wie man es letztlich macht. Sofern man es aber so macht wie von mir beschrieben besteht keine Gefahr.


    Allgemeine Diskussion was alles zu beachten wäre oder welche Sicherheitskonzepte es gibt usw, ist hier aber IMHO fehl am Platz, dazu gibts auch schon andere Threads wie zum Beispiel apache2 Webserver absichern