Michael: SQL-Injektion Schutz auch bei Prepared Statements?

Hallo Forum,

auf der PHP Buddy Seite zum Thema "Einführung in die Arbeit mit mysqli" wird das Hinzufügen eines Datensatzes zu einer DB gezeigt.

Im verwendeten Skript steht:

// Maskierende Slashes aus POST Array entfernen  
if (get_magic_quotes_gpc()) { $_POST = array_map( 'stripslashes', $_POST ); }

Wofür soll das gut sein? Wenn ich (wie auch in diesem Beispielskript auf der Seite) einen neunen Datensatz mit Prepared Statements, also objektorientiert, übermittle, dann brauche ich mich doch nicht um Magic Quotes oder Ähnliches kümmern.

Meine einzige kontextbezogene "Behandlung" von Usereingaben ist, dass ich bei der Ausgabe - zB. bei einem Gästebuch - die Funktion htmlentities verwende.

Genau das _ist_ doch der Vorteil bei mysqli, wenn ich mit Prepared Statements Datensätze objektorientiert übermittle, dass ich dann _keine_ Angst vor SQL Injektions haben muß bzw. keine Gegenmaßnahmen treffen muß.

Oder sehe ich das falsch?

MfG

Michael

  1. Hi,

    // Maskierende Slashes aus POST Array entfernen

    if (get_magic_quotes_gpc()) { $_POST = array_map( 'stripslashes', $_POST ); }

    
    >   
    > Wofür soll das gut sein?  
      
    Es soll die Verfälschung der Daten, die durch ggf. aktiviertes magic\_quotes\_gpc entstanden ist, wieder rückgängig machen.  
      
    
    > Wenn ich (wie auch in diesem Beispielskript auf der Seite) einen neunen Datensatz mit Prepared Statements, also objektorientiert, übermittle, dann brauche ich mich doch nicht um Magic Quotes oder Ähnliches kümmern.  
      
    Doch, wenn du die richtigen Daten eintragen willst, und keine verfälschten.  
      
    
    > Meine einzige kontextbezogene "Behandlung" von Usereingaben ist, dass ich bei der Ausgabe - zB. bei einem Gästebuch - die Funktion [htmlentities](http://at2.php.net/manual/de/function.htmlentities.php) verwende.  
      
    htmlspecialchars wäre die bessere Alternative.  
      
    
    > Genau das \_ist\_ doch der Vorteil bei mysqli, wenn ich mit Prepared Statements Datensätze objektorientiert übermittle, dass ich dann \_keine\_ Angst vor SQL Injektions haben muß bzw. keine Gegenmaßnahmen treffen muß.  
    >   
    > Oder sehe ich das falsch?  
      
    Du siehst falsch, was aktiviertes magic\_quotes\_gpc bewirkt.  
      
    MfG ChrisB  
      
    
    -- 
    “Whoever best describes the problem is the person most likely to solve the problem.” [Dan Roam]
    
    1. Hallo ChrisB, hallo dedlfix,

      danke für Eure Antwort!

      // Maskierende Slashes aus POST Array entfernen

      if (get_magic_quotes_gpc()) { $_POST = array_map( 'stripslashes', $_POST ); }

      
      > >   
      > > Wofür soll das gut sein?  
      >   
      > Es soll die Verfälschung der Daten, die durch ggf. aktiviertes magic\_quotes\_gpc entstanden ist, wieder rückgängig machen.  
        
      Ich weiß schon, dass eingeschaltetes MAGIC QUOTES zu einem Escapen führt und stripslashes das wieder rückgängig macht.  
        
      
      > Du siehst falsch, was aktiviertes magic\_quotes\_gpc bewirkt.  
        
      Ich meinte vielmehr, dass durch die Verwendung von Prepared Statements - zumindesht habe ich das bisher so verstanden - egal ist, was der User eingibt, weil die Eingabe von der Ausführung (oder was auch immer) getrennt ist. Also das 1:1 die Eingabe aus dem Formular eingetragen wird. Mit allen Slashes, Anführungszeichen, etc.  
        
      Bei meiner Versuchsseite sieht die Verarbeitung der Daten, die aus einem Formular kommen, so aus:  
        
      ~~~php
      include("connect.php");  
      $vorname=$_POST["vorname"];  
      $familienname=$_POST["familienname"];  
      $aktion='INSERT INTO `014_testbank` (`testbank_vorname`, `testbank_familienname`) VALUES (?, ?)';  
      $eintrag=$meine_datenbank->prepare($aktion );  
      $eintrag->bind_param('ss',$vorname,$familienname);  
      $eintrag->execute();  
      if($eintrag->affected_rows==1)  
        {  
          echo"<p>Der Datensatz \" ".htmlentities($vorname)." ".htmlentities($familienname)." \" wurde der Datenbank hinzugefügt.</p>\n";  
        }  
      else  
        {  
          echo"<p>Der Datensatz konnte der Datenbank leider nicht hinzugefügt werden.</p>\n";  
        }  
      include("disconnect.php");
      

      Wenn jetzt MAGIC QUOTES eingeschaltet _wäre_ (das habe ich abgedreht) und ich _nicht_ ein

      if (get_magic_quotes_gpc()) { $_POST = array_map( 'stripslashes', $_POST ); }

      verwende, was kann dann passieren? Ich bin jetzt _sehr_ verwirrt, weil ich so oft gelesen habe, ich brauche mir bei Verwendung von mysqli und Preparede Statements keine weiteren Sorgen machen!?

      MfG

      Michael

      1. Merhaba!

        Wenn jetzt MAGIC QUOTES eingeschaltet _wäre_ (das habe ich abgedreht) und ich _nicht_ ein

        if (get_magic_quotes_gpc()) { $_POST = array_map( 'stripslashes', $_POST ); }

        verwende, was kann dann passieren? Ich bin jetzt _sehr_ verwirrt, weil ich so oft gelesen habe, ich brauche mir bei Verwendung von mysqli und Preparede Statements keine weiteren Sorgen machen!?

        Wenn Du weißt, daß magic_quotes deaktiviert sind, kannst Du das wirklich sorglos weglassen (die Bedingung liefert dann ja auch zwangsläufig false). Solche Beispiele sind immer darauf ausgelegt, überall zu funktionieren – auch bei Leuten, die diesen Schurrmurr noch nicht abgeschaltet haben.

        Viele Grüße vom Længlich

        --
        Mein aktueller Gruß ist:
        Türkisch
      2. Hi,

        Ich weiß schon, dass eingeschaltetes MAGIC QUOTES zu einem Escapen führt und stripslashes das wieder rückgängig macht.

        Gut.

        Wenn jetzt MAGIC QUOTES eingeschaltet _wäre_ (das habe ich abgedreht) und ich _nicht_ ein

        if (get_magic_quotes_gpc()) { $_POST = array_map( 'stripslashes', $_POST ); }

        verwende, was kann dann passieren?

        Es passiert genau das, von dem du oben sagst, dass du wüsstest, dass es passiert.

        Ich bin jetzt _sehr_ verwirrt, weil ich so oft gelesen habe, ich brauche mir bei Verwendung von mysqli und Preparede Statements keine weiteren Sorgen machen!?

        Wenn du die Daten in der gewünschten/korrekten Form vorliegen hast, nicht.

        Wenn magic_quotes_gpc on ist, hast du das aber nicht - dann hast du *verfälschte* Daten, und die wirst du so nicht in deine DB eintragen wollen.

        MfG ChrisB

        --
        “Whoever best describes the problem is the person most likely to solve the problem.” [Dan Roam]
        1. Hallo ChrisB,

          Wenn magic_quotes_gpc on ist, hast du das aber nicht - dann hast du *verfälschte* Daten, und die wirst du so nicht in deine DB eintragen wollen.

          Danke, ich hoffe, ich hab das besser verstanden jetzt. Ich bin jetzt aber noch verwirrter: mysqli_real_escape_string() ist _wieder_ was anderes oder gehts da um die selbe Sache?

          MfG

          Michael

          1. Hi!

            mysqli_real_escape_string() ist _wieder_ was anderes oder gehts da um die selbe Sache?

            Wie gesagt, du kannst auch mit mysqli den herkömmlichen Weg nehmen. Hinzugekommen ist der P.S.-Weg. (Man kann auch quasi beides mischen.)

            Herkömmlich:

            $mysqli = new mysqli(...);
            $sql = sprintf("... '%s' ...", $mysqli->real_escape_string(...));
            $mysqli_result = $mysqli->query($sql);
            $mysqli_result->fetch...()

            Prepared Statements:

            $mysqli = new mysqli(...)
            $sql = "... ? ...";
            $mysqli_stmt = $mysqli->prepare($sql);
            $mysqli_stmt->bind_param(...);
            $mysqli_stmt->execute();
            $mysqli_stmt->bind_result(...);
            $mysqli_stmt->fetch();

            Die Beispiele erheben keinen Anspruch auf Vollständigkeit, sie sollen nur die Zugehörigkeit der wichtigsten Methoden zeigen.

            Lo!

            1. Hallo Dedlfix,

              also hab ich das richtig verstanden? Wenn auf _meinem_ Server die Magic Quotes OFF sind, würd ichs nicht brauchen, aber für den Fall der Fälle, dass mein Skript mal auf einem PHP-Server läuft, wo Magic Quotes eingeschaltet sind, sollte ich _trotzdem_ das

              if (get_magic_quotes_gpc()) { $_POST = array_map( 'stripslashes', $_POST ); }

              verwenden. Ist das richtig so? Und reciht das dann?

              Und wenn ich nochmal fragen darf ... dieses "mysqli_real_escape_string()" ist was ganz anderes oder gehts da um die selbe Sache? Oder ist das _wieder_ was anders, das ich _auch_ verwenden soll aus Sicherheitsgründen?

              MfG

              Michael

              PS: Danke für den Vergleich!

              1. Ye'ti!

                also hab ich das richtig verstanden? Wenn auf _meinem_ Server die Magic Quotes OFF sind, würd ichs nicht brauchen, aber für den Fall der Fälle, dass mein Skript mal auf einem PHP-Server läuft, wo Magic Quotes eingeschaltet sind, sollte ich _trotzdem_ das

                if (get_magic_quotes_gpc()) { $_POST = array_map( 'stripslashes', $_POST ); }

                verwenden. Ist das richtig so? Und reciht das dann?

                Richtig. Und ja, das reicht. Nur das »sollte« würde ich differenzierter betrachten:
                Du solltest die Behandlung einfügen, wenn

                • Du den Server (noch) nicht kennst, auf dem das Script laufen wird
                • das Script eine (mehr oder weniger) öffentliche Bibliothek ist, die überall lauffähig sein soll
                • das Script ein öffentliches Beispiel ist.
                  (Im Prinzip laufen diese Punkte alle auf das gleiche hinaus: Serverkonfiguration ist nicht sicher bekannt.)

                Meine Scripte enthalten diese Zeile nicht. Ich veröffentliche sie nicht, und wenn ich jemanden abgucken lasse, sage ich dazu, daß ich mich nicht um Server-Config gekümmert habe – das muß derjenige dann nötigenfalls selbst tun.

                Und wenn ich nochmal fragen darf ... dieses "mysqli_real_escape_string()" ist was ganz anderes oder gehts da um die selbe Sache? Oder ist das _wieder_ was anders, das ich _auch_ verwenden soll aus Sicherheitsgründen?

                Du verwendest entweder mysql oder mysqli (letzteres ist das neuere), und davon dann alle benötigten Funktionen. Ich habe noch nie versucht, sie zu mischen, aber es ist vermutlich keine gute Idee. Und auf keinen Fall beide escapes hintereinander anwenden, sonst hast Du wieder zu viele Slashes im String!

                Viele Grüße vom Længlich

                --
                Mein aktueller Gruß ist:
                Tschuktschisch (gesprochen in Rußland)
                1. Hallo Længlich,

                  ich muß da jetzt noch ein paar mal nachfregen, weil ich möchte das unbedingt richtig verstehen.

                  Du verwendest entweder mysql oder mysqli (letzteres ist das neuere), und davon dann alle benötigten Funktionen. Ich habe noch nie versucht, sie zu mischen, aber es ist vermutlich keine gute Idee.

                  Nein nein, soweit ist mir das schon klar, auch der Unterschied.

                  Wenn ich _nicht_ mit Prepared Statements arbeite, dann muß ich Sonderzeichen escapen, weil es sonst zu erfolgreichen SQL Injections kommen kann. Dann würde ich also die Funktion mysqli.real-escape-string verwenden. Somit würde zB. der aus einem Formular kommende Name O'Rien als O'Rien in der Datenbank landen. Dann müßte ich allerdings vor der Ausgabe mit der Funktion stripslashes die \ entfernen. Und diese Hin- und Her- Prozedur spare ich mir bei der Verwendung von Prepared Statements. Stimmt das soweit?

                  MfG

                  Michael

                  1. Hi!

                    Wenn ich _nicht_ mit Prepared Statements arbeite, dann muß ich Sonderzeichen escapen, weil es sonst zu erfolgreichen SQL Injections kommen kann.

                    Ja.

                    Dann würde ich also die Funktion mysqli.real-escape-string verwenden.

                    Ja.

                    Somit würde zB. der aus einem Formular kommende Name O'Rien als O'Rien in der Datenbank landen.

                    Nein. Es geht beim Maskieren einzig und allein darum, einen String zu erzeugen, bei dem eindeutig Zeichen mit Sonderbedeutung von denen ohne Sonderbedeutung unterschieden werden können. Der Empfänger liest den Zeichenstrom und filtert die Maskierungen wieder raus, so dass er mit Rohdaten arbeiten kann. Das muss er auch, denn sonst könnte man ihn nie nach der korrekten Länge eines Strings fragen oder richtig vergleichen und sortieren.

                    Dann müßte ich allerdings vor der Ausgabe mit der Funktion stripslashes die \ entfernen. Und diese Hin- und Her- Prozedur spare ich mir bei der Verwendung von Prepared Statements. Stimmt das soweit?

                    Nein. Probier es bitte aus. Intern werden die entmaskierte Rohdaten gespeichert und in der Form bekommst du sie auch wieder zurück. Das was intern gespeichert wird, kannst du nicht sehen. Aber mit der Funktion CHARACTER_LENGTH() kannst du die Länge ermitteln und das was bei einer Abfrage zurückkommt, kannst du dir anzeigen lassen. (Auch das was die Funktion mysql(i)_real_escape_string() aus den Daten macht, kannst du mit einer Kontrollausgabe anzeigen lassen. Erzeuge dazu das SQL-Statement in einer Variable.)

                    Es ist genau das gleiche, wie wenn du in PHP

                    echo 'O'Brian';

                    schriebest. Was wird angezeigt? O'Brian ohne . Der \ dient nur dazu, PHP zu sagen, das nächste ' ist kein Stringabschluss. Beim Parsen schon erkennt PHP dies und legt sich intern O'Brian in den Speicher. Ein

                    echo strlen('O'Brian');

                    zeigt auch richtig 7 an, der \ ist beim Weiterarbeiten nicht mehr vorhanden.

                    Lo!

                    1. Hallo Dedlfix!

                      Danke für die Antwort. Leider stehe ich vor dem nächsten Problem. Ich möchte mir 4 Datensätze ab dem 3. Datensatz ausgeben lassen und bilde so den/das Query:

                      $aktion='SELECT testbank_id, testbank_vorname, testbank_familiennameFROM014_testbank LIMIT 3,4';

                      Und das funktioniert auch - es liefert mir genau die Datensätze 3, 4, 5 und 6.

                      Wenn ich aber LIMIT mit Variablen mache, zB so:

                      $startposition=3;  
                      $aktion='SELECT `testbank_id`, `testbank_vorname`, `testbank_familienname` FROM `014_testbank` LIMIT $startposition,4';
                      

                      ...dann kommt eine Warnung:

                      Notice: Trying to get property of non-object in /kunden/1234567890/webseiten/blabla/blaettern.php

                      In der entsprechenden Zeile steht:

                      echo"<p>Es wurden ".$ergebnis->num_rows." Einträge gefunden.</p>\n";

                      ...und eine Fehlermeldung:

                      Fatal error: Call to a member function fetch_object() on a non-object in /kunden/1234567890/webseiten/blabla/blaettern.php

                      In der entsprechenden Zeile steht:

                      while($zeile=$ergebnis->fetch_object())

                      Sobald ich das mit der Variablen weglasse und wieder LIMIT 3,4 draus mache, funktioniert die Ausgabe wieder.

                      Wieso ist das so? Ich werde doch auch hier mit Variablen arbeiten können, oder? Wie sollte man sonst je eine Blätter-Funktion verwirklichen können?

                      Eine Überprüfung mit " var_dump($startposition); " ergibt 3(int), also eh das, was sein soll.

                      Weißt Du Rat?

                      MfG

                      Michael

                      1. Hi!

                        Wenn ich aber LIMIT mit Variablen mache, zB so:

                        $startposition=3;

                        $aktion='SELECT testbank_id, testbank_vorname, testbank_familienname FROM 014_testbank LIMIT $startposition,4';

                          
                        Du verwendest die Variable $startposition nicht. Stattdessen steht in $aktion kurz vorm Schluss die Zeichenkette '$startposition'.  
                          
                        
                        > Eine Überprüfung mit " var\_dump($startposition); " ergibt 3(int), also eh das, was sein soll.  
                          
                        Und $aktion?  
                          
                        Nimm dir ein Grundlagenkapitel zu Strings unter PHP und vor allem den Unterschied zwischen '' und "" beim Interpretieren von Variablen noch einmal genauer zur Brust.  
                          
                        
                        > ...dann kommt eine Warnung:  
                        > Notice: Trying to get property of non-object in /kunden/1234567890/webseiten/blabla/blaettern.php  
                          
                        Das sind Folgefehler, weil du irgendwo eine Funktion verwendet hast, aber nicht kontrollierst, was sie zurückgibt. Im Fehlerfall liefern viele Funktionen ein false anstelle des sonst üblichen Wertes. Ein false ist kein Objekt und hat dann auch keine Eigenschaften.  
                          
                        
                        > In der entsprechenden Zeile steht:  
                        > `echo"<p>Es wurden ".$ergebnis->num_rows." Einträge gefunden.</p>\n";`{:.language-php}  
                          
                        Wenn du dir mal $ergebnis ansiehst:  
                          
                          var\_dump($ergebnis);   => false  
                          
                        ist da kein mysqli- oder mysqli\_stmt-Objekt zu sehen.  
                          
                        Fehler abfangen statt einfach weiterzumachen, als ob immer schönes Wetter ist, zeichnen ein robustes Script aus.  
                          
                          
                        Übrigens, die Werte für LIMIT kann man nicht per Platzhalter in einem Prepared Statement übergeben. Die müssen direkt reingeschrieben werden. Wenn du nicht absolut sicher bist, dass es Zahlen sind, musst du noch dafür sorgen, dass sie welche werden, sonst hast du da am Prepared-Statement-Mechanismus vorbei eine SQL-Injection-Lücke geschaffen.  
                          
                        Die LIMIT-Werte sind dann garantiert Zahlen, wenn sie aus einer einfachen Zuweisung stammen (so wie in deinem Beispiel), mit einer anderen Zahl verrechnet wurden (plus/minus/mal/durch irgendwas) oder mit intval() oder einem Typecast zu einer Zahl zwangskonvertiert wurden.  
                          
                          
                        Lo!
                        
                        1. Hallo Dedlfix,

                          sorry, aber ich versteh nicht, was Du meinst. :-(

                          Du verwendest die Variable $startposition nicht. Stattdessen steht in $aktion kurz vorm Schluss die Zeichenkette '$startposition'.

                          Aber wieso? Wenn bei var_dump ein "  3(int)  " rauskommt, dann heißt das doch, die Variable ist eine Zahl mit dem Wert 3 und _kein_ String.

                          Nimm dir ein Grundlagenkapitel zu Strings unter PHP und vor allem den Unterschied zwischen '' und "" beim Interpretieren von Variablen noch einmal genauer zur Brust.

                          Worauf willst Du mich hinweisen?

                          Fehler abfangen statt einfach weiterzumachen, als ob immer schönes Wetter ist, zeichnen ein robustes Script aus.

                          OK, und was soll ich also anders machen? Bei _unverändertem_ Skript funktioniert es ja, wenn ich _statt_ Variablen Zahlen verwende, also was an meiner Zeile ist falsch?

                          Übrigens, die Werte für LIMIT kann man nicht per Platzhalter in einem Prepared Statement übergeben.

                          Ich hab hier überhaupt kein Prepared Statement, sondern eine einfache Abfrage.

                          Die müssen direkt reingeschrieben werden.

                          Bei einer Blätterfunktion? Wo sich die Startposition laufend ändert? Wie soll das mit fixen Zahlen gehen? Da _muß_ ich mit Variablen arbeiten.

                          Wenn du nicht absolut sicher bist, dass es Zahlen sind, musst du noch dafür sorgen, dass sie welche werden, sonst hast du da am Prepared-Statement-Mechanismus vorbei eine SQL-Injection-Lücke geschaffen.

                          Ich sagte doch, ich _weiß_ dass es eine Zahl ist.

                            
                          $startposition=3;  
                          $aktion='SELECT `testbank_id`, `testbank_vorname`, `testbank_familienname` FROM `014_testbank` LIMIT $startposition,4';
                          

                          Wie gesagt, ich habe weiter Null Ahnung, was ich anders schreiben muß. :-(

                          MfG

                          Michael

                          1. Ich sagte doch, ich _weiß_ dass es eine Zahl ist.

                            $startposition=3;
                            $aktion='SELECT testbank_id, testbank_vorname, testbank_familienname FROM 014_testbank LIMIT $startposition,4';

                            
                            >   
                            > Wie gesagt, ich habe weiter Null Ahnung, was ich anders schreiben muß. :-(  
                              
                            Lass dir doch einfach mal $aktion ausgeben, dann siehst du was falsch läuft.  
                              
                            Struppi.
                            
                          2. Hi!

                            Du verwendest die Variable $startposition nicht. Stattdessen steht in $aktion kurz vorm Schluss die Zeichenkette '$startposition'.
                            Aber wieso? Wenn bei var_dump ein "  3(int)  " rauskommt, dann heißt das doch, die Variable ist eine Zahl mit dem Wert 3 und _kein_ String.

                            Ja, das stimmt soweit. Du nimmst jedoch nur an, dass du sie in $aktion einbaust. Tust du aber nicht, weil du die falschen Anführungszeichen verwendest. Es nützt nichts, sich nur $startposition anzuschauen, denn letztlich willst du ein ordentliches SQL-Statement in $aktion haben. Also schau dir an, was $aktion beinhaltet. Und dann wechsel mal die Anführungszeichen zu "" und schau noch einmal. (Und mach dir den Unterschied zwischen '' und "" klar!)

                            Fehler abfangen statt einfach weiterzumachen, als ob immer schönes Wetter ist, zeichnen ein robustes Script aus.
                            OK, und was soll ich also anders machen? Bei _unverändertem_ Skript funktioniert es ja, wenn ich _statt_ Variablen Zahlen verwende, also was an meiner Zeile ist falsch?

                            Das mit der Zeile wirst du nun hoffentlich verstanden haben. Ansonsten ist es vorteilhaft, die Abarbeitung des nächsten Schrittes vom Erfolg des jeweils vorhergehenden Schrittes abhängig zu machen.

                            $mysqli = new mysqli("localhost", "my_user", "my_password", "world");  
                              
                            // prüfe auf Verbindungsfehler  
                            if (mysqli_connect_errno()) {  
                              // Verbindungsaufbau fehlgeschlagen.  
                              // Fehler loggen.  
                              // Wenn möglich, eine für den Benutzer sinnvolle Alternative anbieten.  
                            } else {  
                              
                              if ($result = $mysqli->query("SELECT feldliste FROM tabelle")) {  
                                while ($row = $result->fetch_assoc()) {  
                                  // Ergebnis verarbeiten  
                                }  
                              
                                $result->close();  
                              } else {  
                                // Abfrage fehlgeschlagen.  
                                // Fehler loggen.  
                                // Wenn möglich, eine für den Benutzer sinnvolle Alternative anbieten.  
                              }  
                              
                              $mysqli->close();  
                            }
                            

                            Beim Erzeugen des mysqli-Objekts wird die Verbindung zum DB-Server aufgebaut. Wenn das misslang, liefert mysqli_connect_errno() einen Wert ungleich 0. $mysqli ist daraufhin nicht brauchbar und wird nicht weiter verwendet. Nur wenn kein Fehler auftrat, geht's in den else-Zweig und dort mit der Methode $mysqli->query() weiter. Auch hier kann wieder ein Fehler auftreten und dann steht in $result kein mysqli_result-Objekt sondern false. Auf $result zuzugreifen ist nur sinnvoll, wenn das SQL-Statement fehlerlos abgearbeitet werden konnte. Deshalb prüft ich wieder mit if ($result... ob etwas anderes als false geliefert wird und lasse nur in dem Fall das while auf das $result los.

                            Wenn du ohne all diese Prüfungen nur sowas notierst:

                            $mysqli = new mysqli("localhost", "my_user", "my_password", "world");  
                            $result = $mysqli->query("SELECT feldliste FROM tabelle");  
                            while ($row = $result->fetch_assoc()) {  
                              // Ergebnis verarbeiten  
                            }  
                            $result->close();  
                            $mysqli->close();
                            

                            bekommst du solche Folgefehler angezeigt, die du gesehen hast, wenn mal was schief läuft.

                            Übrigens, die Werte für LIMIT kann man nicht per Platzhalter in einem Prepared Statement übergeben.
                            Ich hab hier überhaupt kein Prepared Statement, sondern eine einfache Abfrage.

                            Aber mal angenommen, du machst das so, dann

                            Die müssen direkt reingeschrieben werden.

                            muss der MySQL-Server eine Zahl zu Gesicht bekommen und keinen Platzhalter. Das meinte ich mit direkt reinschreiben. Sie müssen direkt im SQL-Statement stehen. Wie du dieses vorher zusammenstückelst, bekommt der DB-Server ja nicht mit. Es zählt das Ergebnis, das fertige SQL-Statement. Darauf bezog sich meine Aussage.

                            Bei einer Blätterfunktion? Wo sich die Startposition laufend ändert? Wie soll das mit fixen Zahlen gehen? Da _muß_ ich mit Variablen arbeiten.

                            Beim Zusammenbau ja. Aus Sicht von MySQL sind im SQL-Statement fix und fertige Zahlenwerte zu sehen.

                            Wenn du nicht absolut sicher bist, dass es Zahlen sind, musst du noch dafür sorgen, dass sie welche werden, sonst hast du da am Prepared-Statement-Mechanismus vorbei eine SQL-Injection-Lücke geschaffen.
                            Ich sagte doch, ich _weiß_ dass es eine Zahl ist.

                            Ja, Folgefehler deinerseits :-)

                            Jetzt nehmen wir mal an, du schriebest sowas:

                            $aktion = "SELECT feld1,feld2 FROM table WHERE feld=? LIMIT ?,4";

                            Das geht so nicht als Prepared Statement, denn LIMIT will keine Platzhalter.

                            $aktion = "SELECT feld1,feld2 FROM table WHERE feld=? LIMIT $_GET[start],4";

                            So darf es nicht, denn trotz P.S. hast du nun eine SQL-Injection-Lücke. Bei feld=? kümmert sich der P.S.-Mechanismus um eine ordentlich (gefahrlose) Übergabe, beim LIMIT musst du das selbst tun.

                            $aktion = sprintf("SELECT feld1,feld2 FROM table WHERE feld=? LIMIT %s,4", intval($_GET['start']));

                            Jetzt sorgt das intval() für einen garantierten Zahlenwert, auch wenn der böse Bube SQL-Code in den Querystring schrieb (in dem Fall ist 0 der Rückgabewert von intval()).

                            Es mag sinnvoll sein, eine richtige Plausibilitätsprüfung für $_GET['start'] vorzunehmen, doch mit dem intval() bekommt man vielleicht ein eigenartiges Ergebnis, aber wenigstens keine SQL-Injection.

                            Lo!

                            1. Hallo Dedlfix!

                              Ja, das stimmt soweit. Du nimmst jedoch nur an, dass du sie in $aktion einbaust. Tust du aber nicht, weil du die falschen Anführungszeichen verwendest. Es nützt nichts, sich nur $startposition anzuschauen, denn letztlich willst du ein ordentliches SQL-Statement in $aktion haben. Also schau dir an, was $aktion beinhaltet. Und dann wechsel mal die Anführungszeichen zu "" und schau noch einmal. (Und mach dir den Unterschied zwischen '' und "" klar!)

                              Von welchen Anführungszeichen in meinem SQL-Statement sprichst Du denn?

                              $aktion='SELECT testbank_id, testbank_vorname, testbank_familiennameFROM014_testbank LIMIT 3,4';

                              funktioniert und

                              $aktion='SELECT testbank_id, testbank_vorname, testbank_familiennameFROM014_testbank LIMIT $startposition,4';

                              funktioniert nicht. Die Anführungszeichen sind in beiden Fällen gleich. Was meintest Du?

                              Das mit der Zeile wirst du nun hoffentlich verstanden haben.

                              Wenn Du das mit den Anführungszeichen meinst, dann nicht. :-(

                              Ansonsten ist es vorteilhaft, die Abarbeitung des nächsten Schrittes vom Erfolg des jeweils vorhergehenden Schrittes abhängig zu machen.

                              Das habe ich auch. Ich hab das nur hier nicht gepostet, um meinen Beitrag nicht zu groß werden zu lassen.

                              Übrigens, die Werte für LIMIT kann man nicht per Platzhalter in einem Prepared Statement übergeben.

                              Das wußte ich nicht, aber...

                              muss der MySQL-Server eine Zahl zu Gesicht bekommen und keinen Platzhalter. Das meinte ich mit direkt reinschreiben. Sie müssen direkt im SQL-Statement stehen. Wie du dieses vorher zusammenstückelst, bekommt der DB-Server ja nicht mit. Es zählt das Ergebnis, das fertige SQL-Statement. Darauf bezog sich meine Aussage.

                              $aktion = sprintf("SELECT feld1,feld2 FROM table WHERE feld=? LIMIT %s,4", intval($_GET['start']));

                              Jetzt sorgt das intval() für einen garantierten Zahlenwert, auch wenn der böse Bube SQL-Code in den Querystring schrieb (in dem Fall ist 0 der Rückgabewert von intval()).

                              ...$_GET['start'] ist ja auch eine Variable und keine Zahl. _Das_ geht und mit einer Normalen Definition a là $start=12 geht es _nicht_ ? Wieso?

                              MfG

                              Michael

                              1. Von welchen Anführungszeichen in meinem SQL-Statement sprichst Du denn?

                                Mach doch einfach mal das was ich dir geschrieben habe und lass dir $aktion ausgeben, das ist eine Zeile Code und du würdest sehen, was du falsch machst.

                                Struppi.

                                1. Hallo Dedlfix,
                                  hallo Struppi!

                                  Ich schäme mich direkt, _so_ einen Denkfehler begangen zu haben. Mir ist es grad wie Schuppen von den Augen gefallen. Ich mache hier ja nichts anders, als zuerst einen String zusammenzubauen und _dann_ erst wird dieser String ausgeführt bzw. verarbeitet.

                                  Und wie man ja schon in der ersten Stunde des PHP-Kindergartens lernt, werden Variablen in Strings nur bei doppelten Anführungszeichen übernommen. Somit weiß ich jetzt natürlich auch, was Dedlfix dauernd mit den Anführungszeichen meinte. :-)

                                  $mann=50;  
                                  $frau=45;  
                                  $satz="Hans ist $mann und seine Frau ist $frau Jahre alt.";  
                                  echo $satz;  
                                  // ergibt => Hans ist 50 und seine Frau ist 45 Jahre alt.
                                  

                                  und

                                  $mann=50;  
                                  $frau=45;  
                                  $satz='Hans ist $mann und seine Frau ist $frau Jahre alt.';  
                                  echo $satz;  
                                  // ergibt => Hans ist $mann und seine Frau ist $frau Jahre alt.
                                  

                                  Und mein Problem hatte natürlich die selbe Ursache.

                                  Wenn ich das

                                    
                                  $aktion='SELECT `testbank_id`, `testbank_vorname`, `testbank_familienname` FROM `014_testbank` LIMIT $startposition,4';
                                  

                                  umändere auf

                                  $aktion="SELECT testbank_id, testbank_vorname, testbank_familiennameFROM014_testbank LIMIT $startposition,4";

                                  dann, funktioniert natürlich auch die Abfrage weil _jetzt_ ergibt das eine Zahl im String und der Datenbankserver weiß, was man will.

                                  Ich hatte also kein Datenbank- oder Queryproblem, ich hab einfach einen hundsordinären String nicht richtig zusammengesetzt. Wirklich peinlich. Ich war so der Meinung, es muß sich um irgend einen mir nicht verständlichen Datenbankfehler oder ein hochkompliziertes Sonderproblem handeln, dass ich das basale Grundproblem nicht gesehen habe.

                                  Ich möchte mich dafür bedanken, dass Ihr so viel Geduld hattet und mir solange ein Brett ins Gesicht geschlagen habt, bis ich es _endlich_ gesehen und verstanden habe.

                                  Mein Problem ist somit gelöst. DANKE!

                                  MfG

                                  Michael

      3. Hi!

        Es soll die Verfälschung der Daten, die durch ggf. aktiviertes magic_quotes_gpc entstanden ist, wieder rückgängig machen.
        Ich weiß schon, dass eingeschaltetes MAGIC QUOTES zu einem Escapen führt und stripslashes das wieder rückgängig macht.
        Ich meinte vielmehr, dass durch die Verwendung von Prepared Statements - zumindesht habe ich das bisher so verstanden - egal ist, was der User eingibt, weil die Eingabe von der Ausführung (oder was auch immer) getrennt ist. Also das 1:1 die Eingabe aus dem Formular eingetragen wird. Mit allen Slashes, Anführungszeichen, etc.

        Ja, aber diese maskierenden Slashes willst du nicht im DBMS haben, denn sie sollen kein Bestandteil der Nutzdaten sein, sondern sind eigentlich für eine Maskierung für Strings im SQL-Statement vorgesehen. Bei Prepared Statements werden alle Zeichen wörtlich genommen, also auch eventuell enthaltene Slashes werden wie Nutzdaten behandelt, egal ob es absichtlich eingegebene oder von Magic Quotes hinzugefügte sind.

        $vorname=$_POST["vorname"];
        $familienname=$_POST["familienname"];

        Das Umkopieren bleibt auch im "Prepared-Statements-Zeitalter" unnötig.

        $aktion='INSERT INTO 014\_testbank (testbank\_vorname, testbank\_familienname) VALUES (?, ?)';
        $eintrag=$meine_datenbank->prepare($aktion );
        $eintrag->bind_param('ss',$vorname,$familienname);
        $eintrag->execute();
        if($eintrag->affected_rows==1)
            echo"<p>Der Datensatz " ".htmlentities($vorname)." ".htmlentities($familienname)." " wurde der Datenbank hinzugefügt.</p>\n";
        else
            echo"<p>Der Datensatz konnte der Datenbank leider nicht hinzugefügt werden.</p>\n";

        Die Auswertung von affected_rows ist keine Fehlerbehandlung. affected_rows sagt nur, wieviele Datensätze geändert wurden. Wenn die Daten gleich bleiben, wird nichts geändert, affected_rows ergibt 0, aber ein Fehler ist das per se erst einmal nicht. Fehlerzustände werden immer noch über die Rückgabewerte der Funktionen/Methoden bekanntgegeben.

        Wenn jetzt MAGIC QUOTES eingeschaltet _wäre_ (das habe ich abgedreht) und ich _nicht_ ein
        if (get_magic_quotes_gpc()) { $_POST = array_map( 'stripslashes', $_POST ); }
        verwende, was kann dann passieren?

        Jemand, der O'Brien heißt, wird als O'Brien im DBMS landen.

        Ich bin jetzt _sehr_ verwirrt, weil ich so oft gelesen habe, ich brauche mir bei Verwendung von mysqli und Preparede Statements keine weiteren Sorgen machen!?

        Keine Sorgen machen ist nicht gut. Prinzip verstehen und wissen, was oder dass nichts passieren kann, wäre besser. Wenn du weißt, dass Prepared Statements die Daten im Rohformat haben wollen, musst du dafür Sorge(!) tragen, dass sie auch in einem solchen vorliegen, und nicht einfach nur mit einem "mir kann nichts passieren"-Lächeln den Blick abwenden.

        Lo!

  2. Hi!

    // Maskierende Slashes aus POST Array entfernen

    if (get_magic_quotes_gpc()) { $_POST = array_map( 'stripslashes', $_POST ); }

    
    > Wofür soll das gut sein?  
      
    PHP versaut die Eingabedaten, wenn das Feature Magic Quotes eingeschaltet ist, indem es sie durch addslashes() schickt. Das braucht man nur, wenn man die Eingabedaten unbeachtet als String in ein SQL-Statement übergibt. Wenn du hingegen selbst maskieren willst oder einen Mechanismus verwendest, der keine Maskierung benötigt - Prepared Statements sind ein solcher - dann stören die Maskierungszeichen und müssen entfernt werden. Der gezeigte Einzeiler ist aber nur für "flache Eingabedaten" geeignet. Wenn mit [] im name-Attribut ein Array erzeugt wird, muss man das auch berücksichtigen. Im Handbuch-Kapitel [Disabling Magic Quotes](http://www.php.net/manual/en/security.magicquotes.disabling.php) ist ein Beispiel enthalten, das auch beliebig verschachtelte Werte behandelt.  
      
    
    > Wenn ich (wie auch in diesem Beispielskript auf der Seite) einen neunen Datensatz mit Prepared Statements, also objektorientiert, übermittle, dann brauche ich mich doch nicht um Magic Quotes oder Ähnliches kümmern.  
      
    Prepared Statements haben nichts mit objektorientiert zu tun. Um die Magic Quotes musst du dich wie gesagt kümmern, denn die stören. Ansonsten kannst du problemlos Rohdaten verwenden und solltest das auch tun.  
      
    
    > Meine einzige kontextbezogene "Behandlung" von Usereingaben ist, dass ich bei der Ausgabe - zB. bei einem Gästebuch - die Funktion [htmlentities](http://at2.php.net/manual/de/function.htmlentities.php) verwende.  
      
    Warum htmlentities() und nicht nur htmlspecialchars()? Arbeitest du intern mit einer anderen Zeichenkodierung als in Richtung Browser?  
      
    
    > Genau das \_ist\_ doch der Vorteil bei mysqli, wenn ich mit Prepared Statements Datensätze objektorientiert übermittle, dass ich dann \_keine\_ Angst vor SQL Injektions haben muß bzw. keine Gegenmaßnahmen treffen muß.  
      
    Angst muss man nie haben, wenn man das Prinzip verstanden hat, dass Daten stets kontextgerecht zu notieren sind.  
      
      
    Lo!
    
    1. Hallo Dedlfix,

      Prepared Statements haben nichts mit objektorientiert zu tun.

      Nein? Sondern? Wenn ich es so wie früher machen würde, also prozedural ... ohne mysqli ...  in der früheren Schreibweise, dann kann ich doch keine PS verwenden.

      Ich dachte, genau _das_ IST die "objektorientierte" Vorgehensweise?

      MfG

      Michael

      1. Hi!

        Prepared Statements haben nichts mit objektorientiert zu tun.
        Nein? Sondern? Wenn ich es so wie früher machen würde, also prozedural ... ohne mysqli ...  in der früheren Schreibweise, dann kann ich doch keine PS verwenden.

        Objektorientiert ist _eine_ Art und Weise, wie die mysqli-Extension angesprochen wird. Prozedural die andere. Ob du nun Prepared Statements oder den herkömmlichen Weg über mysqli_query() oder mysqli::query() nimmst, hat damit nichts zu tun. Beides kannst du sowohl objektorientiert als auch prozedural verwenden. Du findest im mysqli-Kapitel des PHP-Handbuchs die Beispiele jeweils in beiden Versionen.

        Lo!