Christian_D: Variablen absichern per mysqli_real_escape_string: Reicht das aus?

Hallo, ich entwickle gerade ein kleines CMS mit PHP, AJAX, JS und MYSQL.

Ich sichere alle ($_POST) Variablen, die (z.B. in SQL-Abfragen) verarbeitet werden mit mysqli_real_escape_string ab. Reicht das aus? Ist das ausreichend sicher oder muss ich die Variablen noch anderweitig absichern (z.B. mit strip_tags, stripslashes oder htmlspecialchars).

Vielen Dank für eure Einschätzung!

Christian

  1. Hallo Christian,

    es kommt auf den jeweiligen Kontext (SQL, HTML, JSON, …) an, aus dem die Daten kommen und in welchen Kontext sie wechseln.

    Viele Grüße
    Robert

    1. Die Daten kommen aus einem HTML Eingabefeld (input und textarea) und sollen in einer MYSQL-Datenbank gespeichert werden. Ich möchte z.B., dass in der DB auch die echten Anführungszeichen (" oder ') und nicht die HTML-Zeichen (") gespeichert werden. Ich bin mir unsicher, ob das ein Sicherheitsrisiko ist, wenn ich die Variable vor dem Speichern in der DB nur mit mysqli_real_escape_string sichere.

      1. Hallo Christian_D,

        HTML Symbole wie " haben für SQL keine Bedeutung. Es ist unproblematisch, wenn sie in einem SQL Statement innerhalb einer Zeichenkette auftauchen.

        Wenn ein Anwender in einem input-Element so etwas wie Der "größte" Mist eingibt, solltest Du auch genau das in PHP bekommen, und nicht etwa Der &quot;gr&ouml;&szlig;te&quot; Mist. Eine Codierung von speziellen Zeichen für HTML ist nur erforderlich, wenn Du Daten für die Ausgabe im HTML aufbereitest. Wenn Du deine HTML Seite UTF-8 codiert ausgibst (und dein PHP Code in UTF-8 Codierung geschrieben ist), brauchst Du die meisten Zeichen sowieso nicht zu verschlüsseln, das ist nur für < > & erforderlich.

        Du musst bei der Eingabe nur dahinhgend mit ö und ß aufpassen, dass Du sie bei ordentlich konfiguriertem Server als UTF-8 Codierung bekommst, d.h. die klassischen Stringfunktionen von PHP behandeln sie wie zwei Zeichen. Wenn Du die Connection zum SQL Server korrekt herstellst, sollte das aber vom SQL Server transparent behandelt werden und korrekt in der Datenbank landen. Prüfen solltest Du trotzdem, ob ein ö als ö oder als ö gespeichert wird.

        Ich gebe zu, das ist alles verwirrend. Es gibt mehrere Kontexte, es gibt unterschiedliche Zeichencodierungen, da muss man sich erst einmal hindurchfinden. Aber man muss es tun. Andernfalls fällt man auf Mythen herein oder führt unnötige oder gar falsche Schritte aus.

        Rolf

        --
        sumpsi - posui - obstruxi
      2. Moin,

        Die Daten kommen aus einem HTML Eingabefeld (input und textarea) und sollen in einer MYSQL-Datenbank gespeichert werden.

        Sofern das ein normales input oder eine textarea ist, kommen die Daten also einem Reintext-Kontext in den SQL-Kontext. Da kannst du das SQL-Statement unter Verwendung von mysqli_real_escape_string zusammenbauen oder besser: Du verwendest prepared statements.

        Ich möchte z.B., dass in der DB auch die echten Anführungszeichen (" oder ') und nicht die HTML-Zeichen (&quot;) gespeichert werden.

        Die HTML-Entitäten sind nur im Textfeld, wenn eine Nutzerin sie dort einträgt. Es gibt aber neben den ASCII-Anführungszeichen auch noch „die ‚deutschen‘“ oder »die ›Gänsefüßchen‹« …

        Ich bin mir unsicher, ob das ein Sicherheitsrisiko ist, wenn ich die Variable vor dem Speichern in der DB nur mit mysqli_real_escape_string sichere.

        Bei der Verwendung der Escape-Funktion kann dir natürlich beim Zusammenbauen des Statements noch an anderer Stelle ein Fehler unterlaufen. Mit prepared statements kannst du zumindest für die einzufügenden Werte vorsorgen.

        Viele Grüße
        Robert

        1. Tach!

          Sofern das ein normales input oder eine textarea ist, kommen die Daten also einem Reintext-Kontext in den SQL-Kontext.

          Ja, wenn man UTF-8 als Zeichenkodierung (korrekt) verwendet. Oder wenn man ISO-8859-1 verwendet und die Nutzer keine Zeichen außerhalb davon eingeben.

          <!DOCTYPE html>
          <html>
          <head>
              <title>Test</title>
              <meta charset="UTF-8">
          </head>
          <body>
              <form accept-charset="ISO-8859-1" action="">
                  <button type="submit" name="test" value="😀 & 😒">Send ISO</button>
              </form>
          
              <form action="">
                  <button type="submit" name="test" value="😀 & 😒">Send</button>
              </form>
          <?php
          echo $_REQUEST['test'] . ' - ' . urlencode($_REQUEST['test']);
          

          Das erste Formular sendet die Smileys als NCR und das & unmaskiert dazwischen, das zweite als UTF-8-kodierte Zeichen. Alternativ kann man die Seite auch als ISO-8859-1 und das Form ohne accept-charset ausliefern lassen und die Zeichen über ein Input-Feld eingeben, um das gleiche Problem zu haben.

          Fazit: UTF-8 verwenden. Und wenn man solche Smileys auch in MySQL/MariaDB speichern möchte, muss man utf8mb4 und nicht nur utf8 verwenden.

          dedlfix.

  2. Hello Christian,

    ich entwickle gerade ein kleines CMS mit PHP, AJAX, JS und MYSQL.

    Ich sichere alle ($_POST) Variablen, die (z.B. in SQL-Abfragen) verarbeitet werden mit mysqli_real_escape_string ab. Reicht das aus? Ist das ausreichend sicher oder muss ich die Variablen noch anderweitig absichern (z.B. mit strip_tags, stripslashes oder htmlspecialchars).

    Mach Dir eine Zeichnung!

    • In welcher Richtung (eigentlich "Orientierung") findet die Übertragung von Daten statt?
    • Aus welchem Kontext heraus (z. B. Browser im HTML-Modus oder im URL-Modus) in welchen Kontext hinein (z. B. Zwischenspeicherung im Hauptspeicher des Scriptes) findet die Übertragung statt?
    • Dann geht es vielleicht weiter vom Hauptspeicher des Scriptes (Rohdaten) in die Textschnittstelle der Datenbank (SQL) oder in die Datenschnittstelle der Datenbank (blockbasierte Methoden), oder ...

    Und der Rückweg ist auch spannend, damit kein Cross-Site-Scripting (XSS) oder ähnliche Angriffe am Client (Browser, HTML, JavaScript) stattfinden kann.

    Daten sind etwas sehr Verwandlungsfähiges und überall dort, wo sie mit möglichen Befehlen gemixt werden, lauern die Gefahren, mindestens!

    Glück Auf
    Tom vom Berg

    --
    Es gibt nichts Gutes, außer man tut es!
    Das Leben selbst ist der Sinn.
  3. Hallo Christian_D,

    wenn Du einen Wert, den Dir der Browser übergibt (sei es $_GET, $_POST, $_REQUEST oder $_COOKIE), in einen SQL String einsetzt, dann ist mysqli_real_escape_string Schritt 1 auf einem von drei Wegen.

    Weg 1: Du erhältst eine Zeichenkette. Diese musst Du escapen (s.o.). Schritt 2 ist, dass sie im SQL in Anführungszeichen zu setzen sind.

    Weg 2: Du erhältst eine ZAHL. Diese solltst Du nicht escapen, sondern in eine Zahl konvertieren (was bei einem Wert, der keine Zahl ist, als Fehler auffällt. Damit entfällt Escaping, denn rein numerische SQL-Injection Attacken gibt es meines Wissens nicht). Problem an der Konvertierung ist, dass PHP sich hier recht nutzlos anstellt. intval(" 047a") liefert fröhlich die 47 statt "Fehler" und eine 0 bei nicht-integer Werten, und filter_input erbricht sich über "047", weil es das für oktal hält und man ihm das nicht abgewöhnen kann[1]. Also: natives Verhalten von atoi aus der Sprache C, ohne Rücksicht auf praktische Erwägungen der Anwendungsprogrammierung. Für Zahlen solltest Du zunächst mit einer Regex prüfen, ob die Eingabe eine valide Zahl ist, und sie dann mit intval konvertieren. Oder floatval, je nach Bedarf (floatval akzeptiert übrigens führende Nullen). Diese Zahl setzt Du dann ohne Anführungszeichen in dein SQL ein.

    Weg 3: Verwende prepared Statements und ? Marker für deine Werte. Dann musst Du gar nichts escapen.

    Rolf

    --
    sumpsi - posui - obstruxi

    1. Der entsprechende Bug wurde von Rasmus (Lerdorf, nehme ich an) zurückgewiesen. Dezimale Eingaben mit führenden Nullen existieren für ihn nicht. Eine der vielen Stellen in PHP, wo man diese Sprache und ihren Chefautor verfluchen kann. Führende Nullen wurden als "von PHP nicht behandelter Sonderfall" klassifiziert und zur Behandlung an den Anwendungsprogrammierer verwiesen. Oh boy... ↩︎

    1. Hallo Rolf,

      intval(" 047a") liefert fröhlich die 47

      nicht etwa 39? Die führende Null sollte doch IMO auch hier die Oktaldarstellung anzeigen.

      und filter_input erbricht sich über "047", weil es das für oktal hält ...

      ... und 047 als Oktalzahl völlig in Ordnung ist?

      Zugegeben, die Interpretation als Oktalzahl ist eine beliebte Stolperfalle, weil man daran oft nicht denkt. Ich halte es aber auch historisch schon für eine blöde Idee, eine bloße führende Null als Kennzeichnung einer von 10 abweichenden Zahlenbasis zu nehmen. Bei Hexadezimalzahlen mit 0x als Präfix ist es wenigstens nicht nur die Null. Hätte man mal besser 0o (analog zu 0x) als Präfix festgelegt ...

      Live long and pros healthy,
       Martin

      --
      Eine Patella ist ein traditionelles spanisches Reisgericht.
      1. Hallo Martin,

        intval bekommt einen Radixparameter und interpretiert keine Radixpräfixe wie 0 oder 0x.

        filter_input bekommt keinen Radixparameter und erkennt die Präfixe IMMER, man kann es ihm nicht abgewöhnen. Zusätzlich muss man aber auch noch den Gebrauch der Präfixe erlauben. Wenn er ein Präfix bekommt, das nicht freigeschaltet ist (FILTER_INPUT_ALLOW_OCTAL und FILTER_INPUT_ALLOW_HEX), wird der Input als falsch zurückgewiesen. Wnen man die Präfixe explizit freischaltet, wird die passende Radixkonvertierung durchgeführt. Lerdorf hat einfach nicht kapiert, dass filter_input Userdaten entgegen nimmt, keinen Programmcode. Die Verwendung von Radixpräfixen ist bei Userdaten sehr unwahrscheinlich, deshalb ist dieses Verhalten von filter_input totaler Bullshit. Aber heute nicht mehr zu ändern, es sei denn, man fügt ein Flag FILTER_INPUT_USE_RADIX hinzu und verwendet eine Option für den Radixwert. Platz genug wäre in der Flags-Bitmap noch...

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Tach!

          Lerdorf hat einfach nicht kapiert, dass filter_input Userdaten entgegen nimmt, keinen Programmcode.

          Die Filter kamen erst mit PHP 5.2 ins Spiel. Seit Version 3 ist Zend der maßgebliche Treiber von PHP. Bist du sicher, dass Lerdorf für die Filter-Extension verantwortlich ist?

          dedlfix.

          1. Hallo dedlfix,

            ich weiß nur, wie rasmus@php.net auf den Bug reagiert hat, und habe angenommen, dass das Rasmus Lerdorf ist.

            Die Runtime kommt seit PHP3 von ZEev und aNDi, klar, und ich weiß nicht, wieviel Anteil Lerdorf noch am Gesamtprojekt hat. Professionell sicher nicht, wenn man sich seinen beruflichen Werdegang anschaut, und er ist auch keine gelistete Führungskraft bei Zend, aber bestimmt hat er die Finger noch in dieser Pastete drin. Oder hatte es zu dieser Zeit; man weiß ja nicht, was nach dem Aufkaufen durch Rogue Wave bzw. Perforce aus Zend Technologies wird. Wenn ich da an Java und Oracle denke… Aber ich spinne gerade wilde Hypothesen.

            Rolf

            --
            sumpsi - posui - obstruxi
        2. Hello,

          intval bekommt einen Radixparameter und interpretiert keine Radixpräfixe wie 0 oder 0x.

          filter_input bekommt keinen Radixparameter und erkennt die Präfixe IMMER, man kann es ihm nicht abgewöhnen. Zusätzlich muss man aber auch noch den Gebrauch der Präfixe erlauben. Wenn er ein Präfix bekommt, das nicht freigeschaltet ist (FILTER_INPUT_ALLOW_OCTAL und FILTER_INPUT_ALLOW_HEX), wird der Input als falsch zurückgewiesen. Wenn man die Präfixe explizit freischaltet, wird die passende Radixkonvertierung durchgeführt.

          Lerdorf hat einfach nicht kapiert, dass filter_input Userdaten entgegen nimmt, keinen Programmcode. Die Verwendung von Radixpräfixen ist bei Userdaten sehr unwahrscheinlich, deshalb ist dieses Verhalten von filter_input totaler Bullshit.

          Das würde ich nicht so hart bezeichnen wollen. Was unwahrscheinlich ist, bestimmt sicherlich in hohem Maße die Applikation. Und da würde ich diesen Anwendungsfall nicht ausschließen wollen.

          Was ist denn mit HEX-Notation?

          Aber heute nicht mehr zu ändern, es sei denn, man fügt ein Flag FILTER_INPUT_USE_RADIX hinzu und verwendet eine Option für den Radixwert. Platz genug wäre in der Flags-Bitmap noch...

          Ideen zu haben und deren Formulierung später zu verändern sind schon immer zweierlei Intellekt gewesen :-P
          Darum nennt sich so ein Prozess auch "Entwicklung".

          Glück Auf
          Tom vom Berg

          --
          Es gibt nichts Gutes, außer man tut es!
          Das Leben selbst ist der Sinn.
          1. Hallo TS,

            Was unwahrscheinlich ist, bestimmt sicherlich in hohem Maße die Applikation.

            Einverstanden. Wieviele Webseiten kennst Du, die Oktalzahlen oder Hexzahlen als regulären Input verwenden? Abgesehen von den Seiten, die sich mit deren Konvertierung befassen, oder mit Linux Dateirechten (einer der wenigen Anwendungsfälle von Oktalzahlen, abgesehen von Museumscomputern mit 24-bit Wortlänge oder 9-bit Bytes). Keine? Ich auch nicht. Aber wenn ich oktalen Input vom Anwender erwarte, ist es ja völlig okay, dass das mit einem opt-in aktiviert wird. Aktiviere ich es nicht, ist wohl davon auszugehen, dass die Anwendung mit Oktalzahlen nichts am Hut hat, und dann ist eine führende 0 auch kein Fehler. Sondern einfach eine 0. Der normale Mensch, der kein C-Programmierer ist, verbindet führende Nullen nicht mit Oktalzahlen. Selbst eine Menge C- oder PHP-Programmierer tun das nicht, und fallen auf 012 == 10 herein. Die führende 0 als Oktalkennung ist ein grundsätzlicher Mist der C-Syntax, keine Ahnung wer Dennis Ritchie da ins Gehirn geschissen hat (oder woher er das übernommen hat), und es ist größter Quatsch, sie bei einer Anwenderorientierten Funktion wie filter_input als unabschaltbare Komponente drin zu haben.

            Was ist denn mit HEX-Notation?

            Schrub ich doch: Wird bei 0x-Präfix und Freischaltung durch FILTER_FLAG_ALLOW_HEX akzeptiert und nach int konvertiert.

            Was ich meinte, war: Das Feature, ein Präfix zu erkennen und zu verarbeiten, sollte per opt-in aktiviert oder per opt-out deaktiviert werden können. Die filter-Funktionen besitzen einen opt-in für die Konvertierung von Oktal oder Hex nach Dezimal, aber keinen opt-out für die Präfix-Erkennung. D.h. für den Fall, wo man Zahlenpräfixen nichts zu tun haben will, muss man einen eigenen Säuberungsschritt vorschalten - was die Funktion in diesem Fall so gut wie obsolet macht. Und das ist Bullshit im API.

            Man braucht also einen dieser beiden Replace-Schritte, um die Oktalpräfix-Erkennung auszuhebeln.

            $noOctal = preg_replace("/([^0-9])0*([0-9]+)/", "$1$2", $_POST["input"]);
            $noOctal = preg_replace("/(?<![0-9])0*([0-9]+)/", "$1", $_POST["input"]);
            

            Was wesentlich aufwändiger ist als es im Kern von filter_input zu tun - denn da ist die Präfixerkennung schon drin und man muss nur im "Oktalzweig" entscheiden, ob das Oktalpräfix zu beachten, zu ignorieren oder als Fehler zu melden ist.

            Aber hilft ja nichts, der Bug ist rejected, wir können alle unsere Meinungen dazu haben und ein Pullrequest für PHP würde sicherlich nicht beachtet werden.

            Rolf

            --
            sumpsi - posui - obstruxi
            1. Hallo Rolf,

              Wieviele Webseiten kennst Du, die Oktalzahlen oder Hexzahlen als regulären Input verwenden?

              in freier Wildbahn? Keine. Aber viele Embedded Devices haben integrierte Mini-Webserver zur Konfiguration. Da sind dann Eingaben in Hex-Notation durchaus gängig.

              Der normale Mensch, der kein C-Programmierer ist, verbindet führende Nullen nicht mit Oktalzahlen.

              Richtig. Sogar in Tabellen oder sonstwie untereinander stehenden Zahlenkolonnen sind führende Nullen keine Seltenheit.

              Die führende 0 als Oktalkennung ist ein grundsätzlicher Mist der C-Syntax, keine Ahnung wer Dennis Ritchie da ins Gehirn geschissen hat

              Sagte ich doch schon. Nur nicht so deutlich. 😉

              Was ich meinte, war: Das Feature, ein Präfix zu erkennen und zu verarbeiten, sollte per opt-in aktiviert oder per opt-out deaktiviert werden können.

              Ein Präfix, das nicht mit anderen Dingen verwechselt werden kann (so wie das 0x, in manchen Sprachen auch das Dollarzeichen für Hex-Notation), kann meinetwegen auch generell mit ausgewertet werden.

              Live long and pros healthy,
               Martin

              --
              Eine Patella ist ein traditionelles spanisches Reisgericht.
            2. Der normale Mensch, der kein C-Programmierer ist, verbindet führende Nullen nicht mit Oktalzahlen.

              Auch als C Programmierer, normal oder nicht lasse ich mal dahingestellt 😀, hatte ich noch nie das Bedürfnis etwas in Oktal anzugeben.

              Schwachsinn ist das, völlige Zustimmung.

    2. Tach!

      wenn Du einen Wert, den Dir der Browser übergibt (sei es $_GET, $_POST, $_REQUEST oder $_COOKIE), in einen SQL String einsetzt, dann ist mysqli_real_escape_string Schritt 1 auf einem von drei Wegen.

      Der Teilsatz "den Dir der Browser übergibt ..." muss gestrichen werden. Die Herkunft von Daten ist für das Übergeben in einen anderen Kontext irrelevant. Wichtig ist nur, dass ein gültiges und erwünschtes Ergebnis entsteht, also dass dem Kontext entsprechend vorgegangen wird.

      Falls die Daten vom vorherigen Kontext nicht in Rohform ankommen (zum Beispiel mit Escape-Zeichen gespickt sind), dann sollten sie in ihre Rohform konvertiert werden. Das ist aber ein Schritt, der vom Zielkontext unabhängig ist. Die meisten Verarbeitungsschritte liefern nur dann richtige Ergebnisse, wenn sie nicht mit für den Transport benötigten zusätzlichen Zeichen angereichert sind (beispielsweise Ermittlung der String-Länge). Deshalb sollte eine solche Bereinigung gleich beim Entgegennehmen der Daten geschehen.

      Das Thema Zeichenkodierung ist nochmal eine andere nicht zu vernachlässigende Baustelle.

      dedlfix.

      1. Moin

        wenn Du einen Wert, den Dir der Browser übergibt (sei es $_GET, $_POST, $_REQUEST oder $_COOKIE), in einen SQL String einsetzt, dann ist mysqli_real_escape_string Schritt 1 auf einem von drei Wegen.

        Der Teilsatz "den Dir der Browser übergibt ..." muss gestrichen werden. Die Herkunft von Daten ist für das Übergeben in einen anderen Kontext irrelevant. Wichtig ist nur, dass ein gültiges und erwünschtes Ergebnis entsteht, also dass dem Kontext entsprechend vorgegangen wird.

        Diese Sätze kann man gar nicht genug unterstreichen. Browser stellen manche Dinge in Requests an (z.B. /.%2e/ normalisieren, bevor es beim Server ankommt), die man auf anderen Wegen umgehen kann.

        Viele Grüße
        Robert