Rolf Bensch: Ajax: Problem mit Hochkomma " => \"

Hallo zusammen,

habe eine Seite, die Input-Felder via Ajax an einen Apache mit PHP 5.3.2 schickt um sie dort in eine DB zu schreiben. Das funktioniert soweit auch recht gut (auch mit Umlauten), wenn da nicht diese Hochkommas wären. Habe den Code auf ein Minimum zusammengestrichen:

  
<?php  
  header('content-type: text/html; charset=utf-8');  
  if (isset($_POST['val'])) {  
    echo "<br>".$_POST['val'];  
    echo "<br>".urldecode($_POST['val']);  
    die();  
  }  
?>  
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">  
<html>  
  <head>  
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8">  
    <title>Testseite</title>  
    <script type="text/javascript">  
      function clack() {  
        var elm = document.getElementById('eingabe')  
        var t = elm.value + "\n" + encodeURIComponent(elm.value);  
        alert(t);  
        var AjaxScript = "<?php echo $_SERVER['PHP_SELF']?>";  
        var AjaxParam = "val=" + encodeURIComponent(elm.value);  
        HttpRequest = new XMLHttpRequest();;  
        HttpRequest.open("POST", AjaxScript, false);  
        HttpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');  
        HttpRequest.send(AjaxParam);  
        document.getElementById('rueck').innerHTML = HttpRequest.responseText;  
      }  
    </script>  
  </head>  
  <body>  
    <input type='text' id='eingabe' value="<?php echo $_POST['val'] ?>">  
    <input type='button' onclick="javascript:clack()" value='Klick'>  
    <hr>  
    AjaxScript empfängt: <span id='rueck'></span><br>  
  </body>  
</html>  

Das ist vielleicht etwas tricky, daher eine kurze Erläuterung:

  • ein Input-Feld in einem body zur Aufnahme des Text
  • ein Button (Klick) ruft eine Javascript-Routine zum Senden des Inhalts an den Server.
  • das Javascript zeigt zunächst den Inhalt und den mit encodeURIComponent() gewandelten Inhalt der Inputbox in einer Alert-Box an
  • das Javascript erstellt ein HTTPRequest-Objekt und schickt den gewandelten Inhalt der Inputbox an die URL der dargestellten Seite (synchron)
  • PHP gibt den empfangenen Inhalt des $_POST-Arrays unverändert und mit urldecode() gewandelt aus.
  • Javascript empfängt die Ausgabe und stellt sie auf der Seite in einem <span>-Element dar

Das Eingabefeld kann beliebigen Text aufnehmen und PHP liefert die von Javascript gewandelte Eingabe in Originalform zurück an die Seite. Sobald ein Hochkomma ins Spiel kommt, wird ein \ vorangestellt (aus ' wir ' und aus " wird "). Das landet so auf der Beispielseite und natürlich auch in meiner DB.

Gemäß diverser Dokus wir ein mit encodeURIComponent() gewandelter Inhalt in $_GET und $_REQUEST-Arrays automatisch zurück gewandelt. Da die PHP-Ausgaben des Scripts identisch sind, sollte das auch für $_POST-Arrays gelten.

Ich könnte natürlich einfach das Ergebnis in PHP mit str_replace() nachbearbeiten, vermute aber, dass ich einem Denkfehler aufgelaufen bin. Welchem? Bin für jeden Tipp dankbar.

mfg Rolf

  1. Hi,

    Das ist vielleicht etwas tricky,

    Nein, eigentlich überhaupt nicht.

    Das Eingabefeld kann beliebigen Text aufnehmen und PHP liefert die von Javascript gewandelte Eingabe in Originalform zurück an die Seite. Sobald ein Hochkomma ins Spiel kommt, wird ein \ vorangestellt (aus ' wir ' und aus " wird ").

    Stichwort: magic_quotes_gpc

    Gemäß diverser Dokus wir ein mit encodeURIComponent() gewandelter Inhalt in $_GET und $_REQUEST-Arrays automatisch zurück gewandelt.

    Die URL-gerechte Kodierung hat absolut nichts mit der Problematik zu tun.

    MfG ChrisB

    --
    RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
    1. Hallo!

      Wow! Und mit einem einfachen stripslashes() habe ich das im Griff? War heute wohl zu lange vor der Kiste.

      Danke für die Info

      Rolf

      1. Hi,

        Wow! Und mit einem einfachen stripslashes() habe ich das im Griff? War heute wohl zu lange vor der Kiste.

        Besser wäre es, die Ursache (moronic_ äh magic_quotes) zu beseitigen (d.h. wegzukonfigurieren), als nachträglich an den Symptomen rumzupfuschen.

        cu,
        Andreas

        --
        Warum nennt sich Andreas hier MudGuard?
        O o ostern ...
        Fachfragen per Mail sind frech, werden ignoriert. Das Forum existiert.
        1. Hallo Andreas,

          habe mich in die Materie mal reingelesen. "magic_quotes" wurden zum Schutz von Datenbanken (SQL-Injection) eingeführt, werden aber mit PHP 6.0 wieder abgeschafft. Fand im Netz eine Routine, die nach function_exists('get_magic_quotes_gpc') das gesammte POST-Array mit array_stripslashes() nachbearbeitet und abschließend mit if(function_exists('set_magic_quotes_runtime')) set_magic_quotes_runtime(FALSE); die magic_quotes abschaltet. Das ist einerseits PHP6-fest und andererseits wird nicht zwingend ein Zugriff auf php.ini erforderlich. Das sieht für mich soweit gut aus.

          Bleibt die Frage nach dem Schutz der DB. Ich schreibe derzeit die Daten mit PDO und Prepared-Statements bzw. bindParam() in eine MySQL-DB und hoffe damit auf dem richtigen Weg zu sein. Gibt es da noch etwas zu beachten?

          Danke für die Info

          Rolf

          1. Hi,

            habe mich in die Materie mal reingelesen. "magic_quotes" wurden zum Schutz von Datenbanken (SQL-Injection) eingeführt,

            Diesen Schutz bieten sie aber nicht.
            Gegen SQL-Injection hilft, die jeweilige Funktion der Datenbank aufzurufen (bei mysql wäre das z.B. mysql_real_escape_string oder so ähnlich). Ob das bei prepared-Statements nötig ist, weiß ich nicht. Das sollen die Experten beantworten.

            cu,
            Andreas

            --
            Warum nennt sich Andreas hier MudGuard?
            O o ostern ...
            Fachfragen per Mail sind frech, werden ignoriert. Das Forum existiert.
            1. Hi!

              habe mich in die Materie mal reingelesen. "magic_quotes" wurden zum Schutz von Datenbanken (SQL-Injection) eingeführt,
              Diesen Schutz bieten sie aber nicht.

              Die MQ behandeln zwar alle für MySQL unbedingt notwendigen Zeichen, aber sie wirken an der falschen Stelle. Es werden zum einen nur die Eingangsdaten behandelt, nicht die, die anderswo herkommen und zum DBMS gelangen sollen, und zum anderen will man vielleicht die Eingangsdaten erst einmal prüfen und gegebenenfalls dem Eingebenden wieder zur Korrektur vorlegen oder sie gleich gar nicht ins DBMS schicken, dann stören die MQs nur. Deswegen bieten sie keinen umfassenden Schutz und können sogar störend wirken. Außerdem sind sie deswegen schon seit langem deprecated. Es ist also besser, selbst und richtig die Kontextwechsel zu berücksichtigen.

              Gegen SQL-Injection hilft, die jeweilige Funktion der Datenbank aufzurufen (bei mysql wäre das z.B. mysql_real_escape_string oder so ähnlich).

              Nicht nur gegen SQL-Injection. Auch bei lauteren Absichten ist es kontraproduktiv, Syntaxfehler wegen nicht maskierter Zeichen zu bekommen.

              Ob das bei prepared-Statements nötig ist, weiß ich nicht. Das sollen die Experten beantworten.

              Generell nicht, wenn man den vorhandenen Platzhaltermechanismus verwendet.

              Lo!

              1. Hallo Lo!

                Ob das bei prepared-Statements nötig ist, weiß ich nicht. Das sollen die Experten beantworten.

                Generell nicht, wenn man den vorhandenen Platzhaltermechanismus verwendet.

                Ich glaube das jetzt verstanden zu haben. Mit "Platzhaltermechanismus" meinst Du so etwas?

                $dbh = [PDO-Objekt]; $Feld = "Ort"; $id = "22";  
                try {  
                  $SQL = "Update Tabelle SET $Feld = :Wert WHERE `id` = :id";  
                  $stmt = $dbh->prepare($SQL);  
                  $stmt->bindParam(':Wert', $Wert);  
                  $stmt->bindParam(':id', $id);  
                  $stmt->execute();  
                } catch (PDOException $e) {  
                  $Fehler="Fehler beim Speichern von $Feld:".$e->getMessage();  
                  echo $Fehler;  
                }
                

                mfG Rolf

                1. Hi!

                  Mit "Platzhaltermechanismus" meinst Du so etwas?

                  Ja.

                  $SQL = "Update Tabelle SET $Feld = :Wert WHERE id = :id";

                  Aber wenn $Feld von einer unsicheren Quelle kommt, ist es nach den Regeln für Bezeichner zu behandeln. Dafür gibt es jedoch keine vorgefertigte Funktion.

                  Lo!

                  1. Hi,

                    $SQL = "Update Tabelle SET $Feld = :Wert WHERE id = :id";

                    Aber wenn $Feld von einer unsicheren Quelle kommt, ist es nach den Regeln für Bezeichner zu behandeln. Dafür gibt es jedoch keine vorgefertigte Funktion.

                    Hmmm. $Feld kommt aus "unsicherer Quelle". Im Original sitzt $Feld bereits in Backticks. Gemäß Doku müsste ich aber auch auf "ungültige Zeichen" und "reservierte Worte" prüfen. Macht das noch Sinn? Einer SQL-Injection müsste durch die Parametrisierung eigentlich ausreichend entgegengewirkt sein. Enthält der "Identifier" Müll bzw. Schadcode, müsste m.E. automatisch eine PDOException ausgelöst werden. Ich sehe da jetzt kein wirkliches Problem - oder habe ich etwas übersehen?

                    mfG Rolf

                    1. Hi!

                      $SQL = "Update Tabelle SET $Feld = :Wert WHERE id = :id";
                      Hmmm. $Feld kommt aus "unsicherer Quelle". Im Original sitzt $Feld bereits in Backticks. Gemäß Doku müsste ich aber auch auf "ungültige Zeichen" und "reservierte Worte" prüfen. Macht das noch Sinn?

                      Das steht so nicht im MySQL-Handbuch. Vielmehr reicht es, Backticks zu verwenden und du kannst alle Zeichen und alle reservierten Wörter verwenden. Lediglich Backticks sind zu verdoppeln, damit sie nicht als Ende des Bezeichners aufgefasst werden.

                      Einer SQL-Injection müsste durch die Parametrisierung eigentlich ausreichend entgegengewirkt sein.

                      Nein, nicht im Falle des Bezeichners.

                      Enthält der "Identifier" Müll bzw. Schadcode, müsste m.E. automatisch eine PDOException ausgelöst werden.

                      Vorausgesetzt, es ergibt sich durch den eingefügten Wert eine ungültige Syntax.

                      Ich sehe da jetzt kein wirkliches Problem - oder habe ich etwas übersehen?

                      Du hast gültige Syntax übersehen. Eine erfolgreiche Injection basiert darauf, dass der Ausnutzer gültige Syntax erzeugen kann. Hier reicht es, wenn im Wert für den Bezeichner ein Backtick unverdoppelt zu stehen kommt, denn dann hat man den Bezeichner verlassen und der Rest des Wertes wird als Code interpretiert. Ein Ausnutzer wird in eigenem Interesse für eine gültige Fortsetzung des Statements sorgen.

                      Lo!

                      1. Hi,

                        $SQL = "Update Tabelle SET $Feld = :Wert WHERE id = :id";
                        Hmmm. $Feld kommt aus "unsicherer Quelle". Im Original sitzt $Feld bereits in Backticks. Gemäß Doku müsste ich aber auch auf "ungültige Zeichen" und "reservierte Worte" prüfen. Macht das noch Sinn?

                        Das steht so nicht im MySQL-Handbuch. Vielmehr reicht es, Backticks zu verwenden und du kannst alle Zeichen und alle reservierten Wörter verwenden. Lediglich Backticks sind zu verdoppeln, damit sie nicht als Ende des Bezeichners aufgefasst werden.

                        ok, das hatte ich falsch interpretiert.

                        »»...

                        Ich sehe da jetzt kein wirkliches Problem - oder habe ich etwas übersehen?

                        Du hast gültige Syntax übersehen. Eine erfolgreiche Injection basiert darauf, dass der Ausnutzer gültige Syntax erzeugen kann. Hier reicht es, wenn im Wert für den Bezeichner ein Backtick unverdoppelt zu stehen kommt, denn dann hat man den Bezeichner verlassen und der Rest des Wertes wird als Code interpretiert. Ein Ausnutzer wird in eigenem Interesse für eine gültige Fortsetzung des Statements sorgen.

                        Da habe ich jetzt etwas recherchiert. Diese Backtick-Geschichte ist offensichtlich eine MySQL-Spezialität. Andere RDBMS setzen auf ANSI-Quotes. Die Frage ist daher, wie geht PDO mit den Backticks um? Das Manual schweigt sich darüber aus (jedenfalls habe ich nichts gefunden). An anderer Stelle las ich, dass PDO in Backticks gesetzte Identifier für andere RDBMS umsetzt, aber keine Silbe darüber, wie Backticks innerhalb der Identifier behandelt werden. Ich plane eigentlich keinen Einsatz anderer DB-Systeme,  man weiß aber nie was die Zukunft so bringt. Aktuell plane ich einfach alle Backticks aus $Feld zu löschen und den Rest PDO zu überlassen. Mit dieser Einschränkung kann ich gut leben und ich befinde mich auf der sicheren Seite.

                        Nochmals Danke für die Infos, sie haben meine Horizont deutlich erweitert.

                        Rolf

                        1. Hi!

                          Da habe ich jetzt etwas recherchiert. Diese Backtick-Geschichte ist offensichtlich eine MySQL-Spezialität. Andere RDBMS setzen auf ANSI-Quotes.

                          Oder andere proprietäre Geschichten, wie beispielsweise der MS SQL-Server mit [].

                          Die Frage ist daher, wie geht PDO mit den Backticks um?

                          Dafür interessiert sich PDO eigentlich nicht.

                          An anderer Stelle las ich, dass PDO in Backticks gesetzte Identifier für andere RDBMS umsetzt, aber keine Silbe darüber, wie Backticks innerhalb der Identifier behandelt werden.

                          Wenn es nicht im PHP-Manual dokumentiert ist, gehe ich nicht davon aus, dass man das ernst nehmen muss. Mir ist darüber noch nichts zu Ohren gekommen.

                          Ich plane eigentlich keinen Einsatz anderer DB-Systeme,  man weiß aber nie was die Zukunft so bringt.

                          Wenn man kompatibel zu anderen Systemen bleiben will, kann man diverse Vorteile des Systems nicht verwenden, weil man immer nur den SQL-Standard verwenden kann und selbst der ist nicht überall (gleich) umgesetzt. Ich halte das für eine Illusion, nur den Connection-String ändern zu müssen, um problemlos das DBMS zu wechseln. So ein Wechsel will wohl überlegt sein, und da muss man auch Anpassungs- und Test-Zeit mit reinrechnen.

                          Lo!