Peter: Sicherheit in der Abfrage

Hallo,
habe mal eine kurze Frage zur DB-Abfrage und Sicherheit. Ich weiß das ist ein leidiges Thema und wurde schon immer wieder behandelt aber ich denke dass es nie ausführlich genug behandelt ist...

Also vorweg ich habe MySql 5.0.91
über eine URL wird eine id Nummer übergeben
bla.php?id=1
daraus wird dann eine MySql Abfrage:
select viele Felder von Tabelle where id='" . (int)$HTTP_GET_VARS['id'] ."'

Was muß/ kann ich machen, damit die Abfrage sicher ist?
Man könnte doch selber eine Abfrage eingeben ala
bla.php?id=1+Union+Select+1,group_concat(lese andere Tabelle aus)

Wie lässt sich sowas verhindern?

Grüße,
Peter

  1. Hallo,

    das ist ein einfacher Fall:

    Du prüfst ganz am Anfang if (is_numeric($_GET['id'])) - nur wenn das erfolgreich ist, lässt du das Skript weitermachen. Denn wenn ID eine Zahl ist, kann sie nichts anrichten - auch dann nicht, wenn es eine falsch ID ist, denn das prüfst du dann über andere Abgfragen oder z.B. direkt im Query.

    Im Query würde es dann so aussehen: SELECT feld FROM table WHERE id = $id AND eintrag_besitzer = $current_user_id

    Gruß
    Alex

    1. Du prüfst ganz am Anfang if (is_numeric($_GET['id'])) - nur wenn das erfolgreich ist, lässt du das Skript weitermachen. Denn wenn ID eine Zahl ist, kann sie nichts anrichten - auch dann nicht, wenn es eine falsch ID ist, denn das prüfst du dann über andere Abgfragen oder z.B. direkt im Query.

      0xFF ist numerisch und kann trotzdem ggf. die Abfrage kaputt machen - is_numeric() ist nicht geeignet.

  2. Hallo,

    habe mal eine kurze Frage zur DB-Abfrage und Sicherheit. Ich weiß das ist ein leidiges Thema und wurde schon immer wieder behandelt aber ich denke dass es nie ausführlich genug behandelt ist...

    ich denke schon. dedlfix hat dafür den Wiki-Artikel Kontextwechsel verfasst.

    Wie lässt sich sowas verhindern?

    Beachte stets den Kontextwechsel. Validiere Eingabedaten.

    Freundliche Grüße

    Vinzenz

  3. Hello,

    Hallo,
    habe mal eine kurze Frage zur DB-Abfrage und Sicherheit. Ich weiß das ist ein leidiges Thema und wurde schon immer wieder behandelt aber ich denke dass es nie ausführlich genug behandelt ist...

    Also vorweg ich habe MySql 5.0.91
    über eine URL wird eine id Nummer übergeben
    bla.php?id=1
    daraus wird dann eine MySql Abfrage:
    select viele Felder von Tabelle where id='" . (int)$HTTP_GET_VARS['id'] ."'

    Was muß/ kann ich machen, damit die Abfrage sicher ist?
    Man könnte doch selber eine Abfrage eingeben ala
    bla.php?id=1+Union+Select+1,group_concat(lese andere Tabelle aus)

    Wie lässt sich sowas verhindern?

    Du hast es mMn schon richtig gemacht, nur leider renovierungsbedürftig.

    $HTTP_GET_VARS lässt darauf schließen, dass Du eine uralte PHP-Version verwendest als API.

    $id = 0;
    if (isset($_GET['id']))
    {
       $id = intval($_GET['id']);
       $sql = "select $viele_Felder from $tabelle where id = $id";
       $res = mysqli_query($con, $sql);

    #....

    }

    Das müsste eigentlich reichen, PHP und MySQL mal als Beispiel benutzt.

    Das Quoten der ID sollte überflüssig sein, da sie als Integer benutzt wird. Unter der ID==0 (als Primärschlüssel) sollte in einer Tabelle kein Eintrag möglich sein. Das ist aber eine Designsache im Model, die man tunlichst beachten sollte.

    Liebe Grüße aus dem schönen Oberharz

    Tom vom Berg

    --
     ☻_
    /▌
    / \ Nur selber lernen macht schlau
    http://bergpost.annerschbarrich.de
    1. Moin Moin!

      Wenn wir schon über alte APIs reden: PHP hat DB-Interfaces, die mit Prepared Statements umgehen können. Damit ist SQL Injection UNMÖGLICH.

      intval() ist nicht der sauberste Ansatz, das hast Du selbst schon teilweise erklärt: intval() liefert keinen brauchbaren Fehlerstatus, deswegen mußt Du in der DB ausbaden, was Du in PHP verbockt hast. Erschwerend kommt hinzu, dass intval() gelegentlich auch 1 als FEHLERSTATUS liefert. (Ich will gar nicht wissen, welche Drogen man sich einwerfen muß, um auf eine derart abgedrehte API zu kommen.)

      Da PHP reguläre Ausdrücke beherrscht, sollte vor der Übergabe des Parameters an die DB-Schnittstelle überprüft werden, ob der Parameter dem erwarteten Muster entspricht. In diesem Fall wohl /[1]+$/ oder meinetwegen /[2][0-9]*$/

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".

      1. 0-9 ↩︎

      2. 1-9 ↩︎

      1. Hello Alexander,

        Wenn wir schon über alte APIs reden: PHP hat DB-Interfaces, die mit Prepared Statements umgehen können. Damit ist SQL Injection UNMÖGLICH.

        Prepared Statements sind ungefähr das, was ich 1989 schon mit Pascal und bTrieve gebaut habe, also back to the roots. Arbeiten mit Blockbuffer. Es ist eben nicht alles schlecht gewesen. Und (Turbo-)Pascal war mit seiner rigiden Typenprüfung sowieso am besten ;-D

        intval() ist nicht der sauberste Ansatz, das hast Du selbst schon teilweise erklärt: intval() liefert keinen brauchbaren Fehlerstatus, deswegen mußt Du in der DB ausbaden, was Du in PHP verbockt hast.

        PHP hat auch noch die Filterfunktionen:
        http://de2.php.net/manual/en/book.filter.php
        zur Überprüfung und Einhaltung der Formatforschriften und zweitens sollte eine gewisse Logik durchgängig sein. Der Fehler steckt ja in HTTP, und nicht in PHP, denn HTTP überträgt nur Strings.
        Allerdings macht PHP daraus ggf. auch wieder ein Array...

        Erschwerend kommt hinzu, dass intval() gelegentlich auch 1 als FEHLERSTATUS liefert. (Ich will gar nicht wissen, welche Drogen man sich einwerfen muß, um auf eine derart abgedrehte API zu kommen.)

        Das ist tatsächlich verwerflich. Man muss also erst noch auf den Typ prüfen und sicherstellen, dass man einen String erhalten hat, also doch besser gleich die Filterfunktionen benutzten. Das will ich dann doch lieber sofort nochmal ausprobieren, ob man das Problem damit lösen kann.

        Da PHP reguläre Ausdrücke beherrscht, sollte vor der Übergabe des Parameters an die DB-Schnittstelle überprüft werden, ob der Parameter dem erwarteten Muster entspricht.

        Das ist zu spät. Das sollte man schon tun, wenn man die Werte im Script "übernimmt", also z.B. das $_POST-Array mit dem Array mit den Vorgaben vergleicht.

        Das sollten wir auf jeden Fall nebst negativen Beispielen in den Artikel "Umgang mit Formulardaten" aufnehmen, der Bestandteil des Mail-Artikels von Martin werden könnte...

        Liebe Grüße aus dem schönen Oberharz

        Tom vom Berg

        --
         ☻_
        /▌
        / \ Nur selber lernen macht schlau
        http://bergpost.annerschbarrich.de
        1. Nochmal hello Alexander,

          ich habe gerade mal in meine riesige ToDo-Liste geschaut und diesen Thread darin gefunden:
          http://forum.de.selfhtml.org/archiv/2009/5/t187309/#m1244852

          Der sagt eigentlich schon eine ganze Menge aus über die Paranoia bei der Datenübernahme.

          Liebe Grüße aus dem schönen Oberharz

          Tom vom Berg

          --
           ☻_
          /▌
          / \ Nur selber lernen macht schlau
          http://bergpost.annerschbarrich.de
          1. Und nochmal hello Alexander,

            liege ich richtig mit der Annahme, dass in $_POST, $_GET und $_COOKIE nur die Datentypen 'string' und 'array' ankommen können?

            Alle anderen Typen aus
            http://de2.php.net/manual/en/function.gettype.php
            können mMn nur innerhalb des Scriptes erzeugt werden, oder?

            Liebe Grüße aus dem schönen Oberharz

            Tom vom Berg

            --
             ☻_
            /▌
            / \ Nur selber lernen macht schlau
            http://bergpost.annerschbarrich.de
            1. Moin Moin!

              Und nochmal hello Alexander,

              liege ich richtig mit der Annahme, dass in $_POST, $_GET und $_COOKIE nur die Datentypen 'string' und 'array' ankommen können?

              Alle anderen Typen aus
              http://de2.php.net/manual/en/function.gettype.php
              können mMn nur innerhalb des Scriptes erzeugt werden, oder?

              Was steht den in der Doku? *gröhl*

              Ich denke schon, der Webserver liefert Bytes an, via CGI oder API, und PHP verwurstet die dann zu Strings bzw. String-Arrays. Das man PHP mit Zaun samt Weide und Nachbargrundstück winken muß, damit es aus Mehrfach-Werten (z.B. aus <select> oder Checkboxen) auch wirklich ein Array macht, ist eine andere Murkskonstruktion in PHP, aber daran könnte man sich vielleicht ansatzweise noch gewöhnen. Schade nur, dass der Zaunpfahl invalides (X)HTML erzeugt.

              Woher kommt eigentlich die panische Angst vor oder Abneigung gegen Prepared Statements? Nichts könnte schneller oder sauberer sein als Prepared Statements. Ich muß mich nicht um das Quoting von Parametern kümmern, ich kann die Statements beliebig oft wiederverwenden (sofern die DB-API das mitmacht), und die DB kann sich großflächig sparen, Strings aus SQL-Statements herauszuparsen. Und wenn man die DB-Schnittstelle einmal sauber designed hat, funktionieren Prepared Statements auch mit Datenbank-Engines, die das von sich aus gar nicht können - ohne eine Zeile Code ändern zu müssen.

              Alexander

              --
              Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
              1. Hi!

                Woher kommt eigentlich die panische Angst vor oder Abneigung gegen Prepared Statements?

                Ist es wirklich panische Angst und Abneigung oder einfach nur Unwissen? Ich denke, es wird einfach zu wenig gelehrt und die Tutorials beschränken sich auf den herkömmlichen Weg, weil deren Autoren den selbst kennen.

                Nichts könnte schneller oder sauberer sein als Prepared Statements. Ich muß mich nicht um das Quoting von Parametern kümmern, ich kann die Statements beliebig oft wiederverwenden (sofern die DB-API das mitmacht), und die DB kann sich großflächig sparen, Strings aus SQL-Statements herauszuparsen.

                Einspruch! Sauberer sicher, aber schneller keinesfalls. Bei der herkömmlichen Methode hast du einen Funktionsaufruf plus n Maskierungen, sowie noch einen in der Fetch-Schleife. Bei den P.S. braucht es prepare(), bind_param(), bind_result(), execute() sowie das Fetchen. Das sind schonmal 5 statt 3 Funktionen, die man benötigt. Erschwerend kommt hinzu, dass es nur vorgesehen ist, einzelne Variablen zu binden. Arrays gehen nur recht umständlich anzuflanschen. In der Hinsicht wurde bei PDO ein deutlich besseres Design implementiert. Da kann man auch erst beim Execute die benötigten Parameter hinzufügen und ist auch nicht gezwungen bei der Verwendung von Prepared Statements das Ergebnis an Variablen zu binden. Zudem wird der Vorteil des bereits geparsten Statements im PHP-Webumfeld nicht besonders häufig genutzt, weil jede Script-Instanz erneut präparieren muss. Damit ergibt sich auch immer ein zusätzlicher Roundtrip zum DBMS gegenüber der herkömmlichen Variante.

                Ich kann sehr gut verstehen, wenn jemand dieses umständliche Handling der Prepared Statements in PHPs mysqli-Extension nicht mag.

                Lo!

                1. Moin Moin!

                  Woher kommt eigentlich die panische Angst vor oder Abneigung gegen Prepared Statements?

                  Ist es wirklich panische Angst und Abneigung oder einfach nur Unwissen? Ich denke, es wird einfach zu wenig gelehrt und die Tutorials beschränken sich auf den herkömmlichen Weg, weil deren Autoren den selbst kennen.

                  Also Cargo Cult.

                  Nichts könnte schneller oder sauberer sein als Prepared Statements. Ich muß mich nicht um das Quoting von Parametern kümmern, ich kann die Statements beliebig oft wiederverwenden (sofern die DB-API das mitmacht), und die DB kann sich großflächig sparen, Strings aus SQL-Statements herauszuparsen.

                  Einspruch! Sauberer sicher, aber schneller keinesfalls. Bei der herkömmlichen Methode hast du einen Funktionsaufruf plus n Maskierungen, sowie noch einen in der Fetch-Schleife. Bei den P.S. braucht es prepare(), bind_param(), bind_result(), execute() sowie das Fetchen. Das sind schonmal 5 statt 3 Funktionen, die man benötigt.

                  Das sagt nichts über Geschwindigkeit, sondern nur über Schreibaufwand. Und der wird mit ein paar Abenden in einem 10-Finger-Kurs schnell harmlos.

                  Wenn Du über Geschwindigkeit reden willst, fang mit Benchmarks an, statt Funktionsaufrufe zu zählen.

                  Erschwerend kommt hinzu, dass es nur vorgesehen ist, einzelne Variablen zu binden. Arrays gehen nur recht umständlich anzuflanschen.

                  Exakt das Problem hast Du auch, wenn Du Werte direkt ins SQL reinschreibst.

                  In der Hinsicht wurde bei PDO ein deutlich besseres Design implementiert.

                  Richtig, gut von DBI abgeschrieben.

                  Da kann man auch erst beim Execute die benötigten Parameter hinzufügen und ist auch nicht gezwungen bei der Verwendung von Prepared Statements das Ergebnis an Variablen zu binden.

                  Muß ich beim DBI auch nicht:

                    
                  use DBI;  
                    
                  my $dbh=DBI->connect("dbi:$treiber:$datenbank",$user,$password,{ RaiseError => 1, AutoCommit => 1 });  
                    
                  # Hash Ref, Ergebnisspalten-Namen sind Keys  
                  my $sth=$dbh->prepare_cached('select laber,fasel from huhu where foo=? or bar=?');  
                  $sth->execute($fooWert,$barWert);  
                  while (my $hr=$sth->fetchrow_hashref('NAME_lc')) {  
                    print "laber=",$hr->{'laber'}," fasel=",$hr->{'fasel'},"\n";  
                  }  
                  $sth->finish();  
                    
                  # Array Ref, Ergebnisspalten in der Reihenfolge aus dem Select  
                  $sth=$dbh->prepare_cached('select laber,fasel from huhu where foo=? or bar=?');  
                  $sth->execute($fooWert,$barWert);  
                  while (my $ar=$sth->fetchrow_arrayref()) {  
                    print "laber=",$ar->[0]," fasel=",$ar->[1],"\n";  
                  }  
                  $sth->finish();  
                  
                  

                  Ich *KANN* natürlich auch binden, das ist der schnellste Weg, wenn ich um Mikrosekunden feilschen muß. Spart auch Schreiberei, wenn man mit den Ergebnissen aus der DB sehr viel arbeiten muß.

                    
                  # bind_col  
                  $sth=$dbh->prepare_cached('select laber,fasel from huhu where foo=? or bar=?');  
                  $sth->execute($fooWert,$barWert);  
                  my ($laber,$fasel);  
                  $sth->bind_columns(\$laber,\$fasel);  
                  while ($sth->fetch()) {  
                    print "laber=$laber fasel=$fasel\n";  
                  }  
                  $sth->finish();  
                  
                  

                  Auch die Parameter für die Platzhalter kann man binden, per bind_param().

                  Wer möchte, kann auch per bind_param_array() Arrays als Parameter binden und entsprechend execute_array() aufrufen.

                  bind_param_in_out() ist auch möglich, wenn die DB die Parameter aktualisieren soll.

                  Zudem wird der Vorteil des bereits geparsten Statements im PHP-Webumfeld nicht besonders häufig genutzt, weil jede Script-Instanz erneut präparieren muss.

                  Das ist extrem ungeschickt. Wenn ich ein Perl-Script per mod_perl an den Webserver anbinde (analog zu mod_php), dann reicht eine einzige Zeile in httpd.conf bzw. startup.pl, um die DB-Connection und damit auch die Prepared Statements *immer* greifbar zu haben. Am Script selbst muß ich nichts ändern.

                  Nutze ich FastCGI, dann läuft mein Perl-Interpreter ohnehin ständig und hält die Connection und die Statements im Speicher (wenn ich das will).

                  Damit ergibt sich auch immer ein zusätzlicher Roundtrip zum DBMS gegenüber der herkömmlichen Variante.

                  Woher soll der kommen?

                  Wenn Du für jeden Request die Script-Instanz von Null neu startest, hast Du natürlich recht, aber dann läuft PHP im CGI-Modus und der Aufwand für das Prepare dürfte gegenüber dem Start des PHP-Interpreters ziemlich zu vernachlässigen sein.

                  Ich kann sehr gut verstehen, wenn jemand dieses umständliche Handling der Prepared Statements in PHPs mysqli-Extension nicht mag.

                  Tja, schlechtes API-Design. Schon angefangen damit, dass ich viel Code anfassen muß, wenn ich mit einer anderen DB arbeiten will. Bei DBI ist das exakt eine Stelle, dort wo ich den DB-Treiber im connect()-Aufruf festlege. Oft steht der Treiber aber nicht einmal fest im Code, sondern kommt aus einer Konfigurationsdatei. Dann ist der Wechsel auf eine DB eine reine Konfigurationsgeschichte -- vorausgesetzt, der geneigte Coder hat nicht angefangen, abgedrehte DB-Spezialitäten in den Code einzubauen.

                  Alexander

                  --
                  Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
                  1. Hi!

                    Wenn Du über Geschwindigkeit reden willst, fang mit Benchmarks an, statt Funktionsaufrufe zu zählen.

                    Ich meinte nicht direkt die Performance, die hängt ja meist von der Query selbst und der dabei entstehenden Datenmenge ab. Mir ging es vorwiegend um den Schreibaufwand. Es sind ja nicht nur die Funktionen sondern auch das Drumherum, wie Fehlerbehandlung. Die Funktionsaufrufe wirken sich nur dann erwähnenswert auf die Performance aus, wenn man einen langsam angebundenen MySQL-Server ansprechen will oder muss. Da ist jeder Rounddtrip einer zu viel. Doch dieses Szenario trifft auch nur die wenigsten.

                    Erschwerend kommt hinzu, dass es nur vorgesehen ist, einzelne Variablen zu binden. Arrays gehen nur recht umständlich anzuflanschen.
                    Exakt das Problem hast Du auch, wenn Du Werte direkt ins SQL reinschreibst.

                    Mit vsprintf() und array_map('mysql_real_escape_string', $values) bekommt man das schnell gelöst, aber die Referenzgeschichte beim Binden verhindert solche einfachen Lösungen.

                    Muß ich beim DBI auch nicht:

                    Das nützt nur dem PHP-Anwender gar nichts. PDO hilft schon ein Stück weit, ist jedoch nicht in jedem Fall eine Lösung, weil er dann auf die auf MySQL zugeschnittenen Funktionen verzichten muss. Das fängt beim Einstellen der Verbindungskodierung an [*] und beinhaltet beispielsweise auch Multi-Querys.

                    [*] SET NAMES geht natürlich, doch davon bekommt der Escape-Mechanismus nichts mit, und ist dann für einige asiatische Kodierungen nicht mehr verwendbar.

                    Ich *KANN* natürlich auch binden,
                    Auch die Parameter für die Platzhalter kann man binden, per bind_param().
                    Wer möchte, kann auch per bind_param_array() Arrays als Parameter binden und entsprechend execute_array() aufrufen.

                    Und diese Wahlmöglichkeit hat man bei P.S. mit der mysqli-Extension nicht. Entweder P.S. mit Bindung an dedizierte Variablen vor _und_ nach dem Execute oder auf P.S. verzichten.

                    Damit ergibt sich auch immer ein zusätzlicher Roundtrip zum DBMS gegenüber der herkömmlichen Variante.
                    Woher soll der kommen?

                    Der Satz bezog sich auf das Prepare. Herkömmlich gibt es genau einen Roundtrip bei mysql(i)_query(). Bei P.S. hast du mit Prepare und Execute zwei Roundtrips.

                    Wenn Du für jeden Request die Script-Instanz von Null neu startest, hast Du natürlich recht, aber dann läuft PHP im CGI-Modus und der Aufwand für das Prepare dürfte gegenüber dem Start des PHP-Interpreters ziemlich zu vernachlässigen sein.

                    Ja (wenn man keinen langsam angebundenen MySQL_Server hat - beispielsweise einen, der aus organisatorischen Gründen am anderen Ende der Welt steht. Da hilft selbst eine schnelle Leitung nicht, die Laufzeit ist physikalisch bedingt. Aber der Fall trifft wohl für die meisten Wald-und-Wiesen-Anwender ebensowenig zu wie mod_php nutzen zu können.)

                    Ich kann sehr gut verstehen, wenn jemand dieses umständliche Handling der Prepared Statements in PHPs mysqli-Extension nicht mag.
                    Tja, schlechtes API-Design. Schon angefangen damit, dass ich viel Code anfassen muß, wenn ich mit einer anderen DB arbeiten will.

                    Auf ein anderes DBMS umsatteln zu müssen ist vermutlich auch ein selten in Frage kommendes Szenario für die P.S.-Nicht-Kenner.

                    Bei DBI ist das exakt eine Stelle, dort wo ich den DB-Treiber im connect()-Aufruf festlege.

                    Und dann kommen noch die Unterschiede der SQL-Dialekte hinzu. Man wechselt vermutlich eher selten, weil bei gleichen SQL-Statements ein anderes DBMS Vorteile verspricht. Die könnten dann höchstens auf administrativer oder finanzieller Seite liegen. Ich sehe eher Wechsel-Szenarien aufgrund von Features des anderen DBMS - und das erfordert doch etwas mehr Aufwand als den Connection-String anzupassen.

                    vorausgesetzt, der geneigte Coder hat nicht angefangen, abgedrehte DB-Spezialitäten in den Code einzubauen.

                    Da braucht man nicht auf "abgedrehte" Sachen zu schauen. Es fängt ja schon bei auto_increment vs. Sequenzen an und hört bei teilweise grundlegenden Funktionen noch lange nicht auf.

                    Lo!

              2. Moin!

                Schade nur, dass der Zaunpfahl invalides (X)HTML erzeugt.

                Immer nett, wenn sich der Kritiker durch seine Links selbst widerlegt. Das Ticket einfach mal bis zum Ende lesen... :)

                Woher kommt eigentlich die panische Angst vor oder Abneigung gegen Prepared Statements? Nichts könnte schneller oder sauberer sein als Prepared Statements.

                Zur Geschwindigkeit hat dedlfix schon argumentiert. Das deutlich aufwendigere Handling kommt hinzu.

                Meine Kritik: Prepared Statements helfen ausschließlich beim Einfügen von Daten, lassen einen aber im Stich, wenn es darum geht, Statements anderweitig zu dynamisieren:

                • Dynamisch eingefügte Spaltennamen
                • Dynamische WHERE-Bedingungen
                • Dynamische JOINs.
                • ... etc...

                Ich muß mich nicht um das Quoting von Parametern kümmern

                Was macht ein Prepared Statement eigentlich, wenn man sowas haben will:

                SELECT * FROM tab WHERE col LIKE "$var%"

                Klar, Escaping allein bringt hier nix, weil die eventuell enthaltenen LIKE-Metazeichen dadurch nicht beeinflusst werden, weshalb man sich leider selbständig eine eigene LIKE-Escape-Funktion bauen muss.

                Wie kriegt man das Suffix-Prozentzeichen in die Prepared Statements rein? Und wie kriegt man die in der Variablen eventuell enthaltenen Prozentzeichen dazu, nicht als Such-Metazeichen zu wirken? Ich könnte eine Doku wälzen, weil ich es im Moment nicht weiß - aber wenn du als Experte und Befürworter gerade da bist... :)

                ich kann die Statements beliebig oft wiederverwenden (sofern die DB-API das mitmacht), und die DB kann sich großflächig sparen, Strings aus SQL-Statements herauszuparsen.

                Das sind alles Vorteile, die im PHP-Umfeld so eher selten zum Tragen kommen.

                Und wenn man die DB-Schnittstelle einmal sauber designed hat, funktionieren Prepared Statements auch mit Datenbank-Engines, die das von sich aus gar nicht können - ohne eine Zeile Code ändern zu müssen.

                Normale SQL-Statements funktionieren auch einfach so, ohne dass die DB das können muss. Ebenfalls, ohne eine Zeile ändern zu müssen.

                - Sven Rautenberg

                1. Tach auch.

                  Was macht ein Prepared Statement eigentlich, wenn man sowas haben will:

                  SELECT * FROM tab WHERE col LIKE "$var%"

                  Ganz tief aus der Trickkiste. Ich habe mal folgendes benutzt:
                  SELECT * FROM tab WHERE col LIKE CONCAT(?, '%')

                  wobei "?" der PS-Platzhalter ist. War für eine Oracle oder eine Firebird-DB, bin mir da aber nicht mehr vollkommen sicher und habe den Code momentan nicht zur Hand.

                  Bis die Tage,
                  Matti

  4. Moin!

    Also vorweg ich habe MySql 5.0.91
    über eine URL wird eine id Nummer übergeben
    bla.php?id=1
    daraus wird dann eine MySql Abfrage:
    select viele Felder von Tabelle where id='" . (int)$HTTP_GET_VARS['id'] ."'

    Was muß/ kann ich machen, damit die Abfrage sicher ist?

    Die Abfrage ist, so wie sie da steht, sicher.

    Keine Ahnung, warum alle anderen, die bisher geantwortet haben, und es hätten wissen müssen, dein Cast auf Integer übersehen haben. Aber genau dieses (int) vor der Variable sorgt in diesem Fall für ausreichende Sicherheit - das Resultat können nur Integer-Zahlen sein, die dort in den SQL-String gelangen.

    Man könnte doch selber eine Abfrage eingeben ala
    bla.php?id=1+Union+Select+1,group_concat(lese andere Tabelle aus)

    Versuchs selbst mal, und lass dir den erzeugten SQL-String mal anzeigen. Du wirst sehen, dass von deinen Buchstaben nix ankommt.

    Aber im Grundprinzip hast du genau erfasst, was das Problem ist - und die Antwort ist, dass du Kontextwechsel beachten musst, indem du das passende Escaping durchführst - siehe Antwort von Vinzenz Mai.

    - Sven Rautenberg

    1. Hello,

      Keine Ahnung, warum alle anderen, die bisher geantwortet haben, und es hätten wissen müssen, dein Cast auf Integer übersehen haben.

      *hä* ?

      Wo habe ich das übersehen.
      Ich habe es nur im PHP-Kontext gegen intval() ausgetauscht, was dann aber zum Glück nochmal zu einer Diskussion geführt hat.

      Übrigens ergibt ein Cast auf Integer keinesfalls dasselbe, wie eine Transformation zu Integer. Und das hättest Du jetzt erwähnen müssen. Aus einem 'A' wird dann eben eine 65 und keine 0.

      Das Cast ist also noch genauso fehlerträchtig, wie das idiotische intval() ohne vorherige Prüfung mit is_string()...

      Liebe Grüße aus dem schönen Oberharz

      Tom vom Berg

      --
       ☻_
      /▌
      / \ Nur selber lernen macht schlau
      http://bergpost.annerschbarrich.de
      1. Hello,

        Übrigens ergibt ein Cast auf Integer keinesfalls dasselbe, wie eine Transformation zu Integer. Und das hättest Du jetzt erwähnen müssen. Aus einem 'A' wird dann eben eine 65 und keine 0.

        Ok, das nehem ich für PHP zurück. Fällt hier wohl auch unter "Absonderlichkeiten"

        Es kommt 0 raus...
        Und beim Array genauso 1.
        Da sind sie dann wenigstens konsequent merkwürdig geblieben.

          
        <?php   ### zuweisung.php ###  
          
        $intval = "1";  
        var_dump ($intval);  
          
        $castval = (int)$intval;  
        var_dump ($castval);  
          
        $stringval = 'A';  
        $castval = (int)$stringval;  
        var_dump ($castval);  
          
        $arrval = array('Sven','Tom',23);  
        $castval = (int)$arrval;  
        var_dump ($castval);  
          
        ?>  
        
        

        Liebe Grüße aus dem schönen Oberharz

        Tom vom Berg

        --
         ☻_
        /▌
        / \ Nur selber lernen macht schlau
        http://bergpost.annerschbarrich.de
        1. Übrigens ergibt ein Cast auf Integer keinesfalls dasselbe, wie eine Transformation zu Integer. Und das hättest Du jetzt erwähnen müssen. Aus einem 'A' wird dann eben eine 65 und keine 0.

          Dafür gibt es chr() ;)

          Ok, das nehem ich für PHP zurück. Fällt hier wohl auch unter "Absonderlichkeiten"

          Es kommt 0 raus...
          Und beim Array genauso 1.

          Kommt drauf an was im Array drin ist ;) ein leeres Array wird nach 0 gecastet, ein gefülltes nach 1.

          Ebenso wird der String '0xA' nach 0 gecastet, die Zahl 0xA aber nach 10.

          1. Moin!

            Ebenso wird der String '0xA' nach 0 gecastet, die Zahl 0xA aber nach 10.

            Welchen Code meinst du?

            <?php  
              
            var_dump((int) '0xA');  
              
            var_dump((int) 0xA);  
              
            ?>  
            
            

            Logisch. Das eine ist ein String, das andere die im Quellcode erlaubte hexadezimale Notation der Zahl 10.

            Somit identisch zu

            var_dump((int) 10)); // keine Überraschung, oder?

            - Sven Rautenberg

            1. Ebenso wird der String '0xA' nach 0 gecastet, die Zahl 0xA aber nach 10.

              Welchen Code meinst du?

              Beide? ;)

              <?php

              var_dump((int) '0xA');

              var_dump((int) 0xA);

              ?>

                
              int(0)  
              int(10)  
                
              Wie ich sagte: 0 und 10  
                
              
              > Logisch. Das eine ist ein String, das andere die im Quellcode erlaubte hexadezimale Notation der Zahl 10.  
                
              
              > Somit identisch zu  
              >   
              > `var_dump((int) 10)); // keine Überraschung, oder?`{:.language-php}  
                
              Oder  
                
              ~~~php
              var_dump(intval('0xA'));  
              var_dump(intval(0xA));
              

              Für mich ist das nicht überraschend - ich hab' es nur angemerkt, denn Tom schien hier etwas zu zweifeln ;)

              1. Hello,

                Für mich ist das nicht überraschend - ich hab' es nur angemerkt, denn Tom schien hier etwas zu zweifeln ;)

                Ich habe nicht gezweifelt, sondern geirrt...
                Ich war mental noch auf C und nicht auf PHP.

                Liebe Grüße aus dem schönen Oberharz

                Tom vom Berg

                --
                 ☻_
                /▌
                / \ Nur selber lernen macht schlau
                http://bergpost.annerschbarrich.de
    2. Die Abfrage ist, so wie sie da steht, sicher.

      Jein ;)

      Keine Ahnung, warum alle anderen, die bisher geantwortet haben, und es hätten wissen müssen, dein Cast auf Integer übersehen haben.

      Ich hab' ich nicht übersehen, bitte nicht verallgemeinern.

      Aber genau dieses (int) vor der Variable sorgt in diesem Fall für ausreichende Sicherheit - das Resultat können nur Integer-Zahlen sein, die dort in den SQL-String gelangen.

      Für Sicherheit ja, für ein vernünftiges ergebnis sorgt es aber nicht.

      Genauso wie für is_numeric() gilt auch für (int) oder ein explites intval() immer, dass man damit den Sinn der Abfrage entstellen kann - es gibt viele Fälle in denen ein String der nach Integer gecastet wird und ein anderes Ergebnis aufweist, als man eigentlich möchte.

      Man muss nur an strings wie '-10', 'foo10' oder '12,3' denken, im ersteren Fall kommt -10 raus, im zweiteren 0 und im dritte Fall 12 - die ersten beiden sind vermutlich in einer herkömmlichen Tabelle nicht als ID vorhanden und würden ein fehlerhaftes (oder kein) Ergebnis liefern.

      Man muss hierbei nur an ein UPDATE-Statement denken wo man dann (un)absichtlich einen falschen Datensatz aktualisiert.

      "Sicherheit" bedeutet nicht nur, dass etwas vor Injections geschützt ist sondern auch, dass man nicht versehentlich etwas kaputt machen kann.

  5. Hallo,
    erstmal vielen Dank für die vielen Antworten und die interessante Diskussion.
    Habe aber gleich noch eine Frage zur Sicherheit.
    Was mache ich wenn ich eine Art Freitext Suche zulasse? Wie verhindere ich hier, das da ein "böses" Statement eingeschleust wird?
    Select Felder von Tabellen wo t1.feld LIKE '%".$_GET["search_string"]."%' etc.etc.
    Da kann ich doch im Vorfeld nicht wissen nach was gesucht wird also was zulässig ist und was nicht?

    Grüße,
    Peter

    1. Hi!

      Was mache ich wenn ich eine Art Freitext Suche zulasse? Wie verhindere ich hier, das da ein "böses" Statement eingeschleust wird?

      Hast du den von Vinzenz bereits verlinkten Artikel zum Kontextwechsel schon gelesen? Wenn du das Prinzip des Quotierens und und wie man daraus ausbrechen kann - beziehungsweise wie man es mittels Maskierungen verhindert - verstanden hast, solltest du diese Frage selbst klären können. Wenn nicht, versuch dein Verständnisproblem darzulegen.

      Lo!

    2. Hallo,

      Was mache ich wenn ich eine Art Freitext Suche zulasse? Wie verhindere ich hier, das da ein "böses" Statement eingeschleust wird?
      Select Felder von Tabellen wo t1.feld LIKE '%".$_GET["search_string"]."%' etc.etc.

      Du hast im von mir verlinkten Artikel von dedlfix den Abschnitt "Besonderheit: der LIKE-Operator" übersehen.

      Freundliche Grüße

      Vinzenz

      1. Ooops,
        das hatte ich tatsächlich übersehen - Asche auf mein Haupt.
        Wobei so ganz verstehen tu ich das nicht - werde ich mich mal genauer mit beschäftigen....
        Danke und Grüße,
        Peter