PsyCamari: PHP vernünftig escapen?

Sers,

ich versuche gerade Benutzereingaben für ein mini-selfmade Forum vernünftig zu escapen. Zum testen hab ich mal eine simple Funktion gebaut:

public function Escape($String) {  
		$String = $this->Link->real_escape_string($String);  
		$String = nl2br(htmlentities($String),ENT_QUOTES);  
		return $String;  
	}

$this->Link ist die mysqli-Instanz. Zur Erklärung: htmlentities encoded ja wirklich alle Entities, die es so gibt. real_escape_string encoded laut wiki NUL (ASCII 0), \n, \r, , ', ", and Control-Z und daher muss ich es benutzen, weil das nicht alles von htmlentities() abgedeckt wird. Jetzt habe ich folgendes Problem:

Ich gebe zum testen irgendeinen Schwachsinn ein.

EINS<br>
<br><br>
DREI
<p><br><br /><test>
FÜNF
<div></div><span><br>
LOL
ACHT

Wenn real_escape_string drauf angewendet wird, sieht es so aus:

EINS<br>
\r\n<br><br>
\r\nDREI
\r\n<p><br><br /><test>
\r\nFÜNF
\r\n<div></div><span><br>
\r\nLOL
\r\nACHT

Und wenn htmlentities und nl2br drauf angewendet wird sieht es so aus:

EINS&lt;br&gt;<br />\r\n&lt;br&gt;&lt;br&gt;<br />\r\nDREI<br />\r\n&lt;p&gt;&lt;br&gt;&lt;br /&gt;&lt;test&gt;<br />\r\nF&Uuml;NF<br />\r\n&lt;div&gt;&lt;/div&gt;&lt;span&gt;&lt;br&gt;<br />\r\nLOL<br />\r\nACHT

Jetzt frage ich mich:

1. Wieso werden nicht alle Zeichen umgewandelt? zb. <br /> bleibt <br /> anstatt umgewandelt zu werden.
2. Die Sachen die encodet wurden zb. &lt;/div&gt; sind doch richtig encodet, sie müssten mir doch im Forum als <div> angezeigt werden, oder? Tun sie aber nicht. Sie stehen dort genau so wie hier.

Was mache ich falsch?

  1. Wenn real_escape_string drauf angewendet wird, sieht es so aus:

    EINS<br>
    \r\n<br><br>

    ...

    Und wenn htmlentities und nl2br drauf angewendet wird sieht es so aus:

    EINS&lt;br&gt;<br />\r\n&lt;br&gt;&lt;br&gt;<br />\r\nDREI<br />\r\n

    ...

    Jetzt frage ich mich:

    1. Wieso werden nicht alle Zeichen umgewandelt? zb. <br /> bleibt <br /> anstatt umgewandelt zu werden.

    Das ist einfach: Du wendest erst real_escape_string() an. Dann htmlentities(). Hierbei werden die <br> umgewandelt. Das findest Du auch in der Ausgabe: &lt;br&gt;. Doch dann kommt nl2br und fügt neue Umbrüche ein. Du hast nichts falsch gemacht.

    1. Die Sachen die encodet wurden zb. &lt;/div&gt; sind doch richtig encodet, sie müssten mir doch im Forum als <div> angezeigt werden, oder?

    Nur wenn die Anzeige als HTML (und also nicht als HTML-Quell-Text) erfolgt erfolgt und nur wenn der String nicht nochmals durch htmlspecialchars gejagt wird. Du hast den Text sozusagen falsch betrachtet, aber das liegt außerhalb der für uns sichtbaren Programmteile.

    Jörg Reinholz

  2. Hallo,

    ich versuche gerade Benutzereingaben für ein mini-selfmade Forum vernünftig zu escapen.

    für welchen konkreten Zweck? Ein Escaping, eine Maskierung oder Codierung verwendet man normalerweise, wenn man Daten von einem Kontext in einen anderen überführen will, z.B. PHP-String zu mySQL. Einfach "auf Verdacht" irgendwas escapen ist aber nicht sinnvoll, manchmal sogar nachteilig.

    public function Escape($String) {

      $String = $this->Link->real_escape_string($String);  
      $String = nl2br(htmlentities($String),ENT_QUOTES);  
      return $String;  
    

    }

      
    Hier kombinierst du schon zwei Dinge, die sich widersprechen. Einerseits real\_escape\_string(), das einen String für den SQL-Kontext aufbereitet, und andererseits nl2br(), das eindeutig auf die Übergabe in HTML zugeschnitten ist.  
      
    Daher nochmal die Frage: Was genau willst du an dieser Stelle erreichen?  
      
    
    > EINS<br>  
    > <br><br>  
    > DREI  
    > <p><br><br /><test>  
    > FÜNF  
    > <div></div><span><br>  
    > LOL  
    > ACHT  
    >   
    > Wenn real\_escape\_string drauf angewendet wird, sieht es so aus:  
    >   
    > EINS<br>  
    > \r\n<br><br>  
    > \r\nDREI  
    > \r\n<p><br><br /><test>  
    > \r\nFÜNF  
    > \r\n<div></div><span><br>  
    > \r\nLOL  
    > \r\nACHT  
      
    Die Darstellung sieht ungewohnt aus, weil man \r\n normalerweise am Ende der Zeile notiert, aber das ist wirklich nur eine Frage der Darstellung.  
      
    
    > Und wenn htmlentities und nl2br drauf angewendet wird sieht es so aus:  
    >   
    > EINS&lt;br&gt;<br />\r\n&lt;br&gt;&lt;br&gt;<br />\r\nDREI<br />\r\n&lt;p&gt;&lt;br&gt;&lt;br /&gt;&lt;test&gt;<br />\r\nF&Uuml;NF<br />\r\n&lt;div&gt;&lt;/div&gt;&lt;span&gt;&lt;br&gt;<br />\r\nLOL<br />\r\nACHT  
      
    Ja. Eine furchtbare Zeichenwurst.  
      
    
    > 1. Wieso werden nicht alle Zeichen umgewandelt? zb. <br /> bleibt <br /> anstatt umgewandelt zu werden.  
      
    Doch, es wird umgewandelt. <br />\r\n wird von htmlentities() zu &lt;br /&gt;\r\n gewandelt, und von nl2br() schließlich zu &lt;br /&gt;<br />\r\n. Es ist ein Trugschluss, dass nl2br() Zeilenumbrüche (\r) in <br> \_umwandelt\_, stattdessen fügt es nur ein <br /> hinzu und lässt den eigentlichen Zeilenumbruch noch stehen. Auf diese Weise entsteht auch im HTML-Quellcode ein Zeilenumbruch nach dem <br />.  
      
    
    > 2. Die Sachen die encodet wurden zb. &lt;/div&gt; sind doch richtig encodet, sie müssten mir doch im Forum als <div> angezeigt werden, oder? Tun sie aber nicht. Sie stehen dort genau so wie hier.  
      
    Dann machst du noch weitere Fehler, die aus dem gezeigten Code nicht hervorgehen. Beispielsweise einen weiteren Aufruf von htmlentities(), der die & noch zu &amp; konvertiert.  
      
    
    > Was mache ich falsch?  
      
    Wo fange ich an ...?  
      
    Ciao,  
     Martin  
    
    -- 
    Lieber arm dran als Arm ab.  
    Selfcode: fo:) ch:{ rl:| br:< n4:( ie:| mo:| va:) de:] zu:) fl:{ ss:) ls:µ js:(
    
    1. für welchen konkreten Zweck? Ein Escaping, eine Maskierung oder Codierung verwendet man normalerweise, wenn man Daten von einem Kontext in einen anderen überführen will, z.B. PHP-String zu mySQL. Einfach "auf Verdacht" irgendwas escapen ist aber nicht sinnvoll, manchmal sogar nachteilig.

      Nunja, Sinn und Zweck ist es, dass man in diesem selfmade Forum etwas eingeben soll. Und damit da kein Murks eingegeben werden kann (SQLInjection etc.), hat man mir beigebracht muss ich das escapen. Und genau das tu ich doch.

      public function Escape($String) {

        $String = $this->Link->real_escape_string($String);  
        $String = nl2br(htmlentities($String),ENT_QUOTES);  
        return $String;  
      }
      
      
      >   
      > Hier kombinierst du schon zwei Dinge, die sich widersprechen. Einerseits real\_escape\_string(), das einen String für den SQL-Kontext aufbereitet, und andererseits nl2br(), das eindeutig auf die Übergabe in HTML zugeschnitten ist.  
        
      Gut, das habe ich jetzt verstanden. Also muss ich es so machen: 1. User gibt etwas ein, 2. real\_escape\_string(), damit der Kram ohne Probleme in die Datenbank geschrieben wird UND dabei keine Injection benutzt werden kann, 3. beim Auslesen aus der DB erst htmlentities & nl2br anwenden, da es ja jetzt erst an die HTML Anzeige geht und dementsprechend jetzt erst das ganze angezeigt werden soll. Richtig so?  
        
      
      > > 1. Wieso werden nicht alle Zeichen umgewandelt? zb. <br /> bleibt <br /> anstatt umgewandelt zu werden.  
      >   
      > Doch, es wird umgewandelt. <br />\r\n wird von htmlentities() zu &lt;br /&gt;\r\n gewandelt, und von nl2br() schließlich zu &lt;br /&gt;<br />\r\n. Es ist ein Trugschluss, dass nl2br() Zeilenumbrüche (\r) in <br> \_umwandelt\_, stattdessen fügt es nur ein <br /> hinzu und lässt den eigentlichen Zeilenumbruch noch stehen. Auf diese Weise entsteht auch im HTML-Quellcode ein Zeilenumbruch nach dem <br />.  
        
      Danke, hatte ich nicht gemerkt.  
        
      
      > > 2. Die Sachen die encodet wurden zb. &lt;/div&gt; sind doch richtig encodet, sie müssten mir doch im Forum als <div> angezeigt werden, oder? Tun sie aber nicht. Sie stehen dort genau so wie hier.  
      >   
      > Dann machst du noch weitere Fehler, die aus dem gezeigten Code nicht hervorgehen. Beispielsweise einen weiteren Aufruf von htmlentities(), der die & noch zu &amp; konvertiert.  
        
      Ja, das wars tatsächlich. Danke.
      
      1. Hallo,

        für welchen konkreten Zweck? Ein Escaping, eine Maskierung oder Codierung verwendet man normalerweise, wenn man Daten von einem Kontext in einen anderen überführen will, z.B. PHP-String zu mySQL. Einfach "auf Verdacht" irgendwas escapen ist aber nicht sinnvoll, manchmal sogar nachteilig.
        Nunja, Sinn und Zweck ist es, dass man in diesem selfmade Forum etwas eingeben soll.

        das hatte ich schon verstanden; ich meinte die Frage konkreter: Welcher Zweck (welcher Zielkontext) soll es an genau dieser Stelle sein? Aber anscheinend hast du das trotzdem so verstanden, wie ich es meinte.

        Gut, das habe ich jetzt verstanden. Also muss ich es so machen: 1. User gibt etwas ein, 2. real_escape_string(), damit der Kram ohne Probleme in die Datenbank geschrieben wird UND dabei keine Injection benutzt werden kann, 3. beim Auslesen aus der DB erst htmlentities & nl2br anwenden, da es ja jetzt erst an die HTML Anzeige geht und dementsprechend jetzt erst das ganze angezeigt werden soll. Richtig so?

        Absolut richtig. Ich würde noch 'ne kleine Abweichung vorschlagen: Nimm htmlspecialchars() anstatt htmlentities(). Denn wie du selbst schon festgestellt hast, wandelt htmlentities() so ziemlich alles um, was als Entity-Referenz geschrieben werden kann. Das ist aber völlig unnötig - man möchte ja nachher nicht zahllose &auml; und &quot; und ähnlichen Ramsch im HTML-Quellcode haben, sondern lesbare Zeichen. Dagegen ersetzt htmlspecialchars() nur die Zeichen, bei denen das nötig ist, weil sie in HTML eine besondere Bedeutung haben. Das sind AFAIR nur '<' (wird zu &lt;), '>' (wird zu &gt;) und '&' (wird zu &amp;).

        Es ist ein Trugschluss, dass nl2br() Zeilenumbrüche (\r) in <br> _umwandelt_, stattdessen fügt es nur ein <br /> hinzu und lässt den eigentlichen Zeilenumbruch noch stehen. Auf diese Weise entsteht auch im HTML-Quellcode ein Zeilenumbruch nach dem <br />.
        Danke, hatte ich nicht gemerkt.

        Ich hatte früher auch gedacht, nl2br() würde \r oder \r\n durch <br> ersetzen. Der Name der Funktion suggeriert das ja auch irgendwie.

        Dann machst du noch weitere Fehler, die aus dem gezeigten Code nicht hervorgehen. Beispielsweise einen weiteren Aufruf von htmlentities(), der die & noch zu &amp; konvertiert.
        Ja, das wars tatsächlich. Danke.

        Okay, dann funktioniert die Glaskugel manchmal sogar, wenn sie trüb und fleckig ist. ;-)

        Ciao,
         Martin

        --
        Vater Staat bringt uns noch alle unter Mutter Erde.
        Selfcode: fo:) ch:{ rl:| br:< n4:( ie:| mo:| va:) de:] zu:) fl:{ ss:) ls:µ js:(
        1. @@Der Martin:

          nuqneH

          Dagegen ersetzt htmlspecialchars() nur die Zeichen, bei denen das nötig ist, weil sie in HTML eine besondere Bedeutung haben. Das sind AFAIR nur '<' (wird zu &lt;), '>' (wird zu &gt;) und '&' (wird zu &amp;).

          Und ggfs. einfache und doppelte Anführungszeichen*. [PHP]

          Qapla'

          * die Programmcode-Anführungszeichen, nicht Text-Anführungszeichen

          --
          „Talente finden Lösungen, Genies entdecken Probleme.“ (Hans Krailsheimer)
  3. Tach!

    ich versuche gerade Benutzereingaben für ein mini-selfmade Forum vernünftig zu escapen.

    Das ist nicht der richtige Ansatz. Nicht die Benutzereingaben sind das Problem, sondern dass Daten, egal welcher Herkunft, stets dem Ausgabekontext gerecht notiert/maskiert werden müssen.

    Vergiss deine Funktion und betrachte in jeder Situation lediglich nur das jeweilige Ziel.

    • Strings im MySQL-Statement: Escaping für MySQL (mysqli_real_escape_string())
    • Zahlen im MySQL-Statement: innerhalb von Anführungszeichen wie Strings; außerhalb mit intval() o.ä. die Zahl sicherstellen
    • HTML: htmlspecialchars() (htmlentities() macht viel zu viel)
    • URL: ...
    • Javascript: ...
    • ...

    Siehe Kontextwechsel-Artikel.

    dedlfix.