Bobby: SQL-Injection

Moin

mal ne Frage zu SQL-Injection.

Angenommen ich habe eine Seite, die wie folgt aufgebaut ist. Es wird per GET eine Variable ID übergeben, welche für einen MySQL-DB-Aufruf genutzt wird.

Select * FROM Tabelle WHERE ID='$_GET['ID']'

Wie kann ein potentieller Hacker nun mittels SQL_Injection eingreifen? Wie und welche Schutzmechanismen kann man hier einbauen?

PS: derzeit versuche ich mit Select * FROM Tabelle WHERE ID='addslashes(htmlentities(htmlspecialchars($_GET['ID'])))' Codeeinspeisung zu verhindern. Ist dies der richtige Weg?

Gruß Bobby

--
-> Für jedes Problem gibt es eine Lösung, die einfach, sauber und falsch ist! <-
-> Nicht das Problem macht die Schwierigkeiten, sondern unsere Sichtweise! <-
ie:{ br:> fl:{ va:} ls:< fo:) rl:( n4:( de:> ss:) ch:? js:( mo:} sh:) zu:)
  1. Hallo,

    Angenommen ich habe eine Seite, die wie folgt aufgebaut ist. Es wird per GET eine Variable ID übergeben, welche für einen MySQL-DB-Aufruf genutzt wird.

    Select * FROM Tabelle WHERE ID='$_GET['ID']'

    Wie kann ein potentieller Hacker nun mittels SQL_Injection eingreifen?

    überleg doch mal was passiert wenn die ID den Inhalt O'Reily hat.

    Wie und welche Schutzmechanismen kann man hier einbauen?

    Zunächst einmal solltest du wissen, dass es bei einem SELECT Statement nicht ganz so tragisch wie bei einem DELETE oder INSERT ausschaut. Trotzdem müssen Codeeinschleusungen verhindert werden.

    PS: derzeit versuche ich mit Select * FROM Tabelle WHERE ID='addslashes(htmlentities(htmlspecialchars($_GET['ID'])))' Codeeinspeisung zu verhindern. Ist dies der richtige Weg?

    Absolut nicht. Der richtige Weg wäre ID=mysql_real_escape_string($_GET['ID')]. Mehr nicht.

    Grüße, Matze

    Gruß Bobby

    1. Hallo,

      Select * FROM Tabelle WHERE ID='$_GET['ID']'
      Wie kann ein potentieller Hacker nun mittels SQL_Injection eingreifen?
      überleg doch mal was passiert wenn die ID den Inhalt O'Reily hat.

      hmm,
      warum ueberlegen, Versuch macht kluch ...
      mysql = exec-error
      1064 = Fehler in der SQL-Syntax bei 'Reily'' in Zeile 1

      Das System ist also erst mal nicht zusammengebrochen ... ;-)

      Ausserdem:
      Wenn man Daten von "aussen" in ein Script holt, sollte man zumindestens eine
      Minimal-Validation vornehmen, z.B.:

      <?php  
      $pid = (isset($_GET['ID'])) ? intval($_GET['ID']) : 0;  
      ?>
      

      Nun kann ein potentieller Hacker machen was er moechte, es wird ihm nix nutzen.

      Gruss Norbert

    2. Hallo Matze,

      Zunächst einmal solltest du wissen, dass es bei einem SELECT Statement nicht ganz so tragisch wie bei einem DELETE oder INSERT ausschaut.

      Nur unwesentlich. ;-) Beispiel:

      $query = "SELECT feld1, feld2 FROM tabelle WHERE id = '".$_GET['id'].'"';

      Übergebe ich jetzt folgenden String: »0' UNION ALL SELECT username as feld1, password as feld2 FROM users WHERE 1 OR '«.

      Dann wird aus dem Query ein:

      SELECT feld1, feld2 FROM tabelle WHERE id = '0' UNION ALL SELECT username as feld1, password as feld2 FROM users WHERE 1 OR ''

      Und schwupps habe ich Deine komplette User-Datenbank. Und wenn die Passwörter im Klartext gespeichert werden oder ungesaltet gehasht werden, dann hat man als Angreifer eigentlich schon gewonnen.

      Nur so als Beispiel, warum SELECT im Prinzip genauso böse ist.

      PS: derzeit versuche ich mit Select * FROM Tabelle WHERE ID='addslashes(htmlentities(htmlspecialchars($_GET['ID'])))' Codeeinspeisung zu verhindern. Ist dies der richtige Weg?

      Absolut nicht.

      ACK.

      Der richtige Weg wäre ID=mysql_real_escape_string($_GET['ID')]. Mehr nicht.

      ACHTUNG! Hier muss man _höllisch_ aufpassen!

      $query = "SELECT ... WHERE id = ".mysql_real_escape_string ($_GET['id']); -> SICHERHEITSLÜCKE

      Warum? Weil die Funktion mysql_real_escape_STRING heißt, d.h. für STRINGS gedacht ist. Wenn Du unter der Annahme, es handelt sich um eine Zahl, die Zahl einfach in den Query schreibst, dann nützt Dir mysql_real_escape_string nichts, weil in einem String wie »1 UNION ALL ...« nichts drin ist, was escapet werden müsste (keine ' etc.)!

      $query = "SELECT ... WHERE id = '".mysql_real_escape_string ($_GET['id'])."'"; -> hat das Problem nicht.

      Viele Grüße,
      Christian

      1. Hallo,

        ACHTUNG! Hier muss man _höllisch_ aufpassen!

        $query = "SELECT ... WHERE id = ".mysql_real_escape_string ($_GET['id']); -> SICHERHEITSLÜCKE

        Warum? Weil die Funktion mysql_real_escape_STRING heißt, d.h. für STRINGS gedacht ist. Wenn Du unter der Annahme, es handelt sich um eine Zahl, die Zahl einfach in den Query schreibst, dann nützt Dir mysql_real_escape_string nichts, weil in einem String wie »1 UNION ALL ...« nichts drin ist, was escapet werden müsste (keine ' etc.)!

        $query = "SELECT ... WHERE id = '".mysql_real_escape_string ($_GET['id'])."'"; -> hat das Problem nicht.

        hm.. aber id steht doch schon in '' also $_GET['id'].
        Ist es in dem Fall nicht irrelevant weil id schon als String behandelt wird?
        Ich schreib zwar immer im Stil '".mysql..(..)."' aber frag mich halt grad nur.

        Grüße, Matze

        1. echo $begrüßung;

          $query = "SELECT ... WHERE id = '".mysql_real_escape_string ($_GET['id'])."'";

          hm.. aber id steht doch schon in '' also $_GET['id'].
          Ist es in dem Fall nicht irrelevant weil id schon als String behandelt wird?

          Meinst du, es gibt einen Unterschied zwischen $_GET['id'] und $_GET[id]? Ja, gibt es, aber der hat nicht mit den Anführungszeichen zu tun, die in der Ausgabe landen. Diese Anführungszeichen werden nur benötigt, um einen Schlüssel für das $_GET-Array anzugeben. Dieser Schlüssel ist ein Stringwert, weswegen er bei einer direkten Notation in '' oder "" einzufassen ist. Der Wert, der in diesem Array-Element enthalten ist, hat mit der Notation seines Abrufs nichts zu tun.

          Zum Nachvollziehen lass dir bitte den Inhalt von $query ausgeben, wenn du bei obiger Zeile mal $_GET['id'] und mal $_GET[id] verwendest. Setze bitte außerdem das error_reporting auf E_ALL (und display_errors auf on), damit du auch den Unterschied zwischen $_GET['id'] und $_GET[id] siehst, so dir die Erklärung im Handbuch nicht genügt: Why is $foo[bar] wrong?.

          echo "$verabschiedung $name";

          1. Hallo,

            Zum Nachvollziehen lass dir bitte den Inhalt von $query ausgeben, wenn du bei obiger Zeile mal $_GET['id'] und mal $_GET[id] verwendest. Setze bitte außerdem das error_reporting auf E_ALL (und display_errors auf on), damit du auch den Unterschied zwischen $_GET['id'] und $_GET[id] siehst, so dir die Erklärung im Handbuch nicht genügt: Why is $foo[bar wrong?].

            nicht nötig, Danke. Ich hatte nur n bisschen blöd gedacht.
            Du hast mich schon wieder in die richtige Richtung gelenkt.
            Der Link ist sicher eine der am häufigst gelesenen Seiten im Handbuch^^

            Grüße, Matze

  2. echo $begrüßung;

    PS: derzeit versuche ich mit Select * FROM Tabelle WHERE ID='addslashes(htmlentities(htmlspecialchars($_GET['ID'])))' Codeeinspeisung zu verhindern. Ist dies der richtige Weg?

    Im Prinzip wurde deine Frage ja schon beantwortet, aber zu diesem Konstrukt möchte ich noch ein paar Worte verlieren.

    Bei der Ausgabe von Werten ist es wichtig, den jeweiligen Kontext zu beachten, in den die Werte ausgegeben werden. Du hast hier ein SQL-Statement und kein HTML vorliegen, deswegen sind alle Behandlungen für den HTML-Kontext fehl am Platz. Dass du später mal den Datenbankinhalt nach HTML ausgeben willst, interessiert an dieser Stelle überhaupt nicht. Zum einen, weil du nicht wissen kannst, ob du später die Daten nicht mal in einen anderem Kontext ausgeben willst, zum anderen, weil durch die verunstalteten Zeichen Stringfunktionen in der Datenbank nicht mehr richtig arbeiten können.

    In einigen Kontexten (beispielsweise SQL, HTML, XML) werden Daten und Code gemeinsam übertragen. Man nimmt Maskierungen deshalb vor, um zu unterscheiden, ob es sich bei bestimmten Zeichen um Code oder Daten handelt.

    Der Grundsatz lautet deshalb: Daten _immer_ und _nur_ kontextabhängig behandeln.

    htmlentities() berücksichtigt auch die Zeichen, die htmlspecialchars() behandelt. Wenn du beide Funktionen schachtelst, erhältst du eine doppelte Maskierung. Es ist in den meisten Fällen ausreichend, htmlspecialchars() zu verwenden (natürlich nur bei der Ausgabe in einen HTML-Kontext). Die Fälle, in denen htmlentities() benötigt wird, will man nicht haben.

    Neben den unbedingt zu berücksichtigenden HTML-eigenen Zeichen <, >, & und " braucht man keine weiteren Maskierungen, wenn man für das Dokument eine passende Kodierung gewählt hat. (Bitte beachte bei dem Thema "Zeichen" die Begrifflichkeiten. Ein Vermischen stiftet regelmäßig Verwirrung und ist für das Verständnis des Themas nicht hilfreich.) Verwendest du die Zeichenkodierung ISO-8859-1, kannst du zwar deutsche Umlaute damit abbilden und dir erscheint das vielleicht ausreichend. Doch irgendwann gibt mal jemand ein Zeichen außerhalb von ISO-8859-1 ein, und dann machen einige Browser daraus eine Ersatzdarstellung &#xxxx;. Diese Ersatzdarstellung nehmen sie aber nicht für ein normales &, und sie unterscheidet sich auch nicht von einer händischen und beabsichtigten Eingabe solch einer Zeichensequenz. Welche Sequenz soll nun bei der Ausgabe durchgelassen werden und welche nicht? Anders gefragt: Welche Sequenz ist bereits HTML-kodiert und welche muss noch so behandelt werden, damit sie so wie beabsichtigt erscheint und nicht statt ihrer ein Zeichen angezeigt wird?

    Als Ausweg aus diesem Dilemma ist die konsequente Verwendung der Zeichenkodierung UTF-8 eine Lösung. Damit lassen sich praktisch sämtliche bekannte Zeichen kodieren und der Browser braucht keine Ersatzdarstellungen zu verwenden. Leider ist für PHP erst ab Version 6 eine Unterstützung für Unicode und UTF-8 geplant. Die derzeit vorhandene mbstring-Extension steht nicht überall zur Verfügung. Diese Einschränkung kommt nur dann zum Tragen, wenn man mit PHP Strings bearbeiten muss (z.B. kürzen, sortieren). Wenn man sie nur durchreichen lässt, gibt es im Prinzip keine Probleme.

    Zurück zu htmlentities(). Diese Funktion braucht man nur, wenn man Daten vorliegen hat, die sich in der von der HTML-Seite verwendeten Kodierung nicht transportieren lassen. Doch wo sollen diese Zeichen herkommen? Bei der Eingabe werden sie vom Browser verunstaltet. In der Datenbank können sie nur dann stehen, wenn sie anderweitig da hinein gelangt sind. Bleiben noch Inhalte aus anderen/fremden Quellen übrig, die man direkt anzapft. In einem solchen Fall sollten dann in einer Einzelfallentscheidung die konkreten Umstände berücksichtigt werden.

    Zusammenfassung:

    • keine HTML-Kodierung für den SQL-Kontext verwenden, bzw. obigen generellen Grundsatz beherzigen.
    • angemessene Kodierung verwenden, htmlentities() ad acta legen, htmlspecialchars() für den HTML-Kontext verwenden.

    echo "$verabschiedung $name";

    1. Moin

      Ich danke für diese umfassende Erläuterung und werde dies in Zukunft beherzigen.

      Gruß Bobby

      --
      -> Für jedes Problem gibt es eine Lösung, die einfach, sauber und falsch ist! <-
      -> Nicht das Problem macht die Schwierigkeiten, sondern unsere Sichtweise! <-
      ie:{ br:> fl:{ va:} ls:< fo:) rl:( n4:( de:> ss:) ch:? js:( mo:} sh:) zu:)