Ankündigung

Einklappen
Keine Ankündigung bisher.

Byte / Multibyte: Bestimmte Zeichen verbieten

Einklappen

Neue Werbung 2019

Einklappen
X
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • Byte / Multibyte: Bestimmte Zeichen verbieten

    Hallo,

    ich habe aktuell ein paar Verständnisprobleme bzw. habe mich in das Thema Byte/Multibyte verrannt.

    Also ich habe eine UTF-8 codierte Seite. Input und Output ist entsprechend nur UTF-8 zulässig.

    Um mich vor XSS zu schützen, habe ich nun die folgende Funktion realisiert (deckt auch charset xss ab):
    Code:
    function filter_text($val, $charset='UTF-8') {
    	return !mb_check_encoding($val, $charset) ? '' : htmlentities(trim($val), ENT_QUOTES, $charset);
    }
    Soweit so gut.

    Nun möchte ich einen Filter für Usernamen schreiben. Hier möchte ich neben der Prüfung auf einen validen Zeichensatz und das Escapen von Entities auch bestimmte Zeichen ausschließen.

    Dazu zählt z.B. auch chr(160) (non-breaking space) oder chr(170) (soft hyphen).

    Bei meiner Recherche bin ich auch auf die ganzen ASCII non-printing Zeichen gestoßen (chr(0) bis chr(31)).

    Erstmal soweit mein Ergebnis dazu:
    Code:
    function filter_username($val, $charset='UTF-8') {
    	if (!mb_check_encoding($val, $charset)) {
    		return '';
    	}
    	if ($val != str_replace(array(
    		"\n", // line feed
    		"\r", // carriage return
    		chr(0), chr(1), chr(2), chr(3), chr(4), chr(5), chr(6), chr(7), chr(8), chr(9), chr(10), chr(11), chr(12), chr(13), chr(14), chr(15), chr(16), chr(17), chr(18), chr(19), chr(20), chr(21), chr(22), chr(23), chr(24), chr(25), chr(26), chr(27), chr(28), chr(29), chr(30), chr(31), chr(127), chr(129), chr(141), chr(143), chr(144), chr(157), // non-printing ascii
    		chr(160), // non-breaking space
    		chr(173), // soft hyphen
    		), '', $val)) {
    			return '';
    	}
    	return htmlentities(trim($val), ENT_QUOTES, $charset);
    }
    (ich weiß trim ist eigentlich nicht für Multi-Byte empfohlen, aber ich habe noch keinen Kommentar gefunden, der Probleme resultierte)

    Nun meine Fragen dazu:

    1.) Ersetze ich mit str_replace() versehentlich ein Byte eines Multibyte-Zeichens oder sind die 255 ASCII Zeichen "reserviert", so dass sowas nicht passieren kann?

    2.) Treffe ich mit str_replace() überhaupt meine gewünschten Zeichen, wenn der Zeichensatz UTF-8 ist? Also gibt es für das jeweilige ASCII-Zeichen noch einen (zusätzlichen) entsprechenden UTF-8 Multibyte-Ersatz, den ich separat erkennen müsste?
    meine PHP Scripte


  • #2
    Anmerkung: Wenn ich "ASCII" sage, meine ich ASCII-7 (also die ersten 128 Zeichen). Ich weiß nicht, ob das "korrekt" ist, aber so verwende ich hier die Bezeichnung.

    Zitat von hondatuner Beitrag anzeigen
    charset_xss
    Das ist nur ein sehr theoretisches Problem, oder? Als Standard-Charset nehmen die Funktionen htmlspecialchars und htmlentities ISO-8859-1 an. Dieses Charset ist ASCII-kompatibel, das heißt, die ersten 128 Zeichen (darunter "<" und ">") entsprechen dem ASCII-Zeichensatz. Die Seite müsste schon mit einem Charset ausgeliefert werden, das nicht ASCII-kompatibel ist, damit sich eine Lücke für XSS ergibt. Alle Charsets der ISO-8859-Familie sowie UTF-8 sind dies jedoch.

    Edit: Das Encoding von Eingabedaten dürfte dabei übrigens keinerlei Rolle spielen.

    Deine Funktion wäre ohne das mb_check_encoding ebenso sicher vor XSS. Wieso überhaupt htmlentities, wenn du offensichtlich mit UTF-8 arbeitest? Wieso nicht htmlspecialchars?

    (ich weiß trim ist eigentlich nicht für Multi-Byte empfohlen, aber ich habe noch keinen Kommentar gefunden, der Probleme resultierte)
    Solange du nur Zeichen "trimmen" möchtest, die in ASCII liegen, ist das wenigstens in UTF-8 kein Problem, denn die sind dort binär identisch (1 Byte) kodiert. Die Standardliste von trim liegt komplett in ASCII.

    1.) Ersetze ich mit str_replace() versehentlich ein Byte eines Multibyte-Zeichens oder sind die 255 ASCII Zeichen "reserviert", so dass sowas nicht passieren kann?
    Die ersten 128 sind es (hinsichtlich UTF-8 ), die darüber sind es nicht. Da zerschießt du dir mit str_replace potentiell die Multibyte-Characters.

    2.) Treffe ich mit str_replace() überhaupt meine gewünschten Zeichen, wenn der Zeichensatz UTF-8 ist? Also gibt es für das jeweilige ASCII-Zeichen noch einen (zusätzlichen) entsprechenden UTF-8 Multibyte-Ersatz, den ich separat erkennen müsste?
    Die ersten 128 ja, die darüber nicht. Für die Zeichen ab dem 129. müsstest du nach einer Multibyte-Sequenz suchen. (Ein schönes Tool zur Konvertierung: Unicode code converter [ishida >> utilities].)

    Ich würde aber anders an die Sache rangehen und einen übergebenen String mit Regex und Unicode Character Properties prüfen. Das ist sehr elegant.

    Kommentar


    • #3
      Zitat von mermshaus Beitrag anzeigen
      Deine Funktion wäre ohne das mb_check_encoding ebenso sicher vor XSS
      Wenn ich nun einen UTF-7 String reinbekomme, würde dieser nicht auf Basis von UTF-8 über htmlentities escaped also im Grunde gar nicht escaped?

      In einem meiner Scripte hatte ich durch Zufall gesehen, das htmlentities dann ein leeres Ergebnis zurückwirft, aber ich war mir nicht sicher, ob das immer der Fall ist.

      Wieso überhaupt htmlentities, wenn du offensichtlich mit UTF-8 arbeitest? Wieso nicht htmlspecialchars?
      Die schlechtere Performance oder der erhöhte Speicherplatzverbrauch ist mir an der Stelle egal. Ich habe eben Angst vor sowas wie das:
      Code:
      žscriptualert(EXSSE)ž/scriptu
      Vielleicht gibt es ja doch einen Browser, der mit Zeichen aus unterschiedlichen Zeichensätzen falsch umgeht, obwohl UTF-8 vorgegeben wurde. Wer weiß das schon.

      Unicode Character Properties prüfen. Das ist sehr elegant.
      Sieht kompliziert aus. So als müsste ich für jeden denkbaren Typ (Letter, Dezimal, etc.) eine eigene Bedingung bauen. Und welche Zeichen in der jeweiligen Klasse drin sind, kann ich auch nicht erkennen. z.B. wäre die Frage in welcher Klasse Zeichen wie { / @ usw. sind oder ob kyrillische Zeichen in Letter enthalten sind. Hast Du dazu mehr Infos?
      meine PHP Scripte

      Kommentar


      • #4
        Wenn ich nun einen UTF-7 String reinbekomme, würde dieser nicht auf Basis von UTF-8 über htmlentities escaped also im Grunde gar nicht escaped?
        Der Bitstring würde so interpretiert, als wäre er UTF-8, ja. Das heißt, wenn ein Byte drin vorkommt, das in UTF-8 etwa "<" bedeuten würde, würden diese 8 Bits durch "&lt;" ersetzt. Das heißt, die gefährlichen Zeichen würden für eine Ausgabe als UTF-8 korrekt escaped. Insgesamt würde der String natürlich zu ziemlichem Unsinn, weil UTF-7 als UTF-8 interpretiert mehr oder weniger eine "Zufallsfolge" von Characters ergeben dürfte. Aber sicher vor XSS müsste es sein.

        Ob diese Art der Behandlung ausreicht oder ob die Anwendung versuchen soll, falsche Zeichensätze zu erkennen und entsprechend umzuformen oder zurückzuweisen, ist damit natürlich nicht entschieden.

        In einem meiner Scripte hatte ich durch Zufall gesehen, das htmlentities dann ein leeres Ergebnis zurückwirft, aber ich war mir nicht sicher, ob das immer der Fall ist.
        Was mit wirklich falschen Eingabedaten (zum Beispiel illegalen Characters in UTF- passiert, überlasse ich ehrlichgesagt einfach PHP. Serverseitige Software muss in der Lage sein, mit sowas umzugehen, das wäre sonst ein Bug. Da die Daten ohnehin falsch sind, ist es mir relativ gleichgültig, was aus ihnen wird. Entweder schaffen sie es nicht durch einen Validator oder ich gebe sie im Zweifel als den "Unsinn" aus, zu dem sie geworden sind.

        Vielleicht gibt es ja doch einen Browser, der mit Zeichen aus unterschiedlichen Zeichensätzen falsch umgeht, obwohl UTF-8 vorgegeben wurde. Wer weiß das schon.
        Gerade "<" und ">" werden ja ohnehin escaped. Nein, ich verstehe schon, was du meinst. Ich denke aber, der Gedankengang führt zu nichts, denn ab einem gewissen Level muss man bei Software einfach darauf vertrauen, dass sie sicher ist. Jede Desktopanwendung kann potentiell deine gesamten Nutzerdaten von der Festplatte löschen usw. Millionen von Internetseiten nutzen UTF-8. Das fiktive Risiko in diesem Fall reduzieren zu wollen, erscheint mir persönlich übertrieben. Mit der gleichen Argumentation könntest du sicherlich auch darauf verzichten, PNG-Bilder oder JavaScript einzusetzen oder Umlaute in E-Mails einzufügen. Das wirkt etwas willkürlich. Aber gut, so entscheidend ist das jetzt auch nicht.

        [Unicode Character Properties]
        Das sind im Prinzip nur die Varianten von [A-Za-z] oder [0-9], die auch Deutsch, Französisch und Chinesisch können. Die im letzten Post verlinkte Seite ist eine überaus hilfreiche Anlaufstelle für alle Fragen zum Thema.

        Auch diese Resource ist dort verlinkt (über UniView): Unicode Utilities: Character Properties

        Kommentar


        • #5
          Zitat von mermshaus Beitrag anzeigen
          Gerade "<" und ">" werden ja ohnehin escaped.
          Eben nicht. In dem Beispielfall mit UTF-7 erkennt PHP die Zeichen nicht, weil htmlspecialchars / htmlentities kein UTF-7 kann. Der Browser dagegen schon.

          Einzig die Quotes werden erkannt, wenn man entsprechend ENT_QUOTES übergibt. Allerdings bekommt man XSS auch ohne Quotes zum laufen z.B. so:
          Code:
          $string = "<script>alert(/XSS/.source)</script>";
          $string = "<script>alert(String.fromCharCode(88,83,83))</script>";
          Ich will eben ungern was in meine Datenbank packen, was durch irgendeinen blöden Zufall bei der Ausgabe dann doch zur Ausführung führt.

          Ob nun PHP einen Bug hat oder nicht ist mir an der Stelle eigentlich egal. Schlimmer ist, dass ich vielleicht irgendwann penne und dann habe ich den Salat
          meine PHP Scripte

          Kommentar


          • #6
            Zitat von hondatuner Beitrag anzeigen
            Eben nicht. In dem Beispielfall mit UTF-7 erkennt PHP die Zeichen nicht, weil htmlspecialchars / htmlentities kein UTF-7 kann. Der Browser dagegen schon.
            Das verlinkte Beispiel und auch die genannte XSS-Lücke bei Google beziehen sich explizit auf den Fall, dass die Zeichenkodierung nicht angegeben wurde, und der Browser dann halt zufällig „rät“, dass es UTF-7 sein könnte, und die Daten als solches interpretiert.

            Wenn die Daten jedoch explizit als UTF-8 ausgeliefert werden, dann mögen sie zwar fehlerhaft sein, die XSS-Problematik ist dann aber nicht gegeben.

            Kommentar


            • #7
              Jo und genau für den Fall, dass ICH den passenden Header vergesse, will ich eben erst gar nicht die Daten annehmen.

              Ich habe zwischenzeitlich übrigens mit den in PHP verfügbaren Zeichensätzen ein kleinen Test gemacht:
              XSS Charset attack

              Wie man sieht passiert das bisher nur in UTF-7. Eigentlich hätten die PHP-Entwickler da ruhig mal htmlentities/htmlspecialchars verbessern können

              EDIT:
              Ok kleines Problem:
              Code:
              <?php
              header('Content-Type: text/html; charset=utf-7');
              $string = "<script>alert(String.fromCharCode(88,83,83))</script>";
              $string = mb_convert_encoding($string, 'UTF-7');
              echo 'mb_detect_encoding: ' . mb_detect_encoding($string) . '<br />';
              if (mb_check_encoding($string, "utf-7")) {
              	echo 'mb_check_encoding with "utf-7"<br />';
              }
              if (mb_check_encoding($string, "utf-8")) {
              	echo 'mb_check_encoding with "utf-8"<br />';
              }
              echo htmlentities($string, ENT_QUOTES);
              echo htmlentities($string, ENT_QUOTES, 'UTF-7');
              echo htmlentities($string, ENT_QUOTES, 'UTF-8');
              ?>
              Das resultiert in allen drei Fällen eine erfolgreiche XSS-Attacke und folgendes:
              mb_detect_encoding: ASCII
              mb_check_encoding with "utf-7"
              mb_check_encoding with "utf-8"

              Warning: htmlentities() [function.htmlentities]: charset `UTF-7' not supported, assuming iso-8859-1 in
              Also obwohl der String in UTF-7 konvertiert wurde, erkennt ihn mb_check_encoding() auch als UTF-8.

              Ich kann die Annahme also weder mit htmlentities() noch mit mb_convert_encoding() verweigern.

              Auf Grund der Erkennung von ASCII kann man auch nicht so arbeiten:
              Code:
              <?php
              header('Content-Type: text/html; charset=utf-7');
              $string = "<script>alert(String.fromCharCode(88,83,83))</script>";
              $string = mb_convert_encoding($string, 'UTF-7');
              $string = mb_convert_encoding($string, 'UTF-8', mb_detect_encoding($string));
              echo htmlentities($string, ENT_QUOTES, 'UTF-8');
              ?>
              Das hier:
              Code:
              <?php
              $charsets = mb_list_encodings();
              $string = mb_convert_encoding("<script>alert(String.fromCharCode(88,83,83))</script>", 'UTF-7');
              foreach ($charsets as $charset) {
              	if (@mb_check_encoding($string, $charset)) {
              		echo 'mb_check_encoding utf-7 string with ' . $charset . '<br />';
              	}
              }
              $string = mb_convert_encoding("<script>alert(String.fromCharCode(88,83,83))</script>", 'UTF-8');
              foreach ($charsets as $charset) {
              	if (@mb_check_encoding($string, $charset)) {
              		echo 'mb_check_encoding utf-8 string with ' . $charset . '<br />';
              	}
              }
              ?>
              resultiert:
              mb_check_encoding utf-7 string with auto
              mb_check_encoding utf-7 string with wchar
              mb_check_encoding utf-7 string with HTML-ENTITIES
              mb_check_encoding utf-7 string with Quoted-Printable
              mb_check_encoding utf-7 string with 7bit
              mb_check_encoding utf-7 string with 8bit
              mb_check_encoding utf-7 string with UTF-8
              mb_check_encoding utf-7 string with UTF-7
              mb_check_encoding utf-7 string with UTF7-IMAP
              mb_check_encoding utf-7 string with ASCII
              mb_check_encoding utf-7 string with EUC-JP
              mb_check_encoding utf-7 string with SJIS
              mb_check_encoding utf-7 string with eucJP-win
              mb_check_encoding utf-7 string with SJIS-win
              mb_check_encoding utf-7 string with CP51932
              mb_check_encoding utf-7 string with JIS
              mb_check_encoding utf-7 string with ISO-2022-JP
              mb_check_encoding utf-7 string with ISO-2022-JP-MS
              mb_check_encoding utf-7 string with Windows-1252
              mb_check_encoding utf-7 string with ISO-8859-1
              mb_check_encoding utf-7 string with ISO-8859-2
              mb_check_encoding utf-7 string with ISO-8859-3
              mb_check_encoding utf-7 string with ISO-8859-4
              mb_check_encoding utf-7 string with ISO-8859-5
              mb_check_encoding utf-7 string with ISO-8859-6
              mb_check_encoding utf-7 string with ISO-8859-7
              mb_check_encoding utf-7 string with ISO-8859-8
              mb_check_encoding utf-7 string with ISO-8859-9
              mb_check_encoding utf-7 string with ISO-8859-10
              mb_check_encoding utf-7 string with ISO-8859-13
              mb_check_encoding utf-7 string with ISO-8859-14
              mb_check_encoding utf-7 string with ISO-8859-15
              mb_check_encoding utf-7 string with ISO-8859-16
              mb_check_encoding utf-7 string with EUC-CN
              mb_check_encoding utf-7 string with CP936
              mb_check_encoding utf-7 string with HZ
              mb_check_encoding utf-7 string with EUC-TW
              mb_check_encoding utf-7 string with BIG-5
              mb_check_encoding utf-7 string with EUC-KR
              mb_check_encoding utf-7 string with UHC
              mb_check_encoding utf-7 string with ISO-2022-KR
              mb_check_encoding utf-7 string with Windows-1251
              mb_check_encoding utf-7 string with CP866
              mb_check_encoding utf-7 string with KOI8-R
              mb_check_encoding utf-8 string with auto
              mb_check_encoding utf-8 string with wchar
              mb_check_encoding utf-8 string with HTML-ENTITIES
              mb_check_encoding utf-8 string with Quoted-Printable
              mb_check_encoding utf-8 string with 7bit
              mb_check_encoding utf-8 string with 8bit
              mb_check_encoding utf-8 string with UTF-8
              mb_check_encoding utf-8 string with UTF7-IMAP
              mb_check_encoding utf-8 string with ASCII
              mb_check_encoding utf-8 string with EUC-JP
              mb_check_encoding utf-8 string with SJIS
              mb_check_encoding utf-8 string with eucJP-win
              mb_check_encoding utf-8 string with SJIS-win
              mb_check_encoding utf-8 string with CP51932
              mb_check_encoding utf-8 string with JIS
              mb_check_encoding utf-8 string with ISO-2022-JP
              mb_check_encoding utf-8 string with ISO-2022-JP-MS
              mb_check_encoding utf-8 string with Windows-1252
              mb_check_encoding utf-8 string with ISO-8859-1
              mb_check_encoding utf-8 string with ISO-8859-2
              mb_check_encoding utf-8 string with ISO-8859-3
              mb_check_encoding utf-8 string with ISO-8859-4
              mb_check_encoding utf-8 string with ISO-8859-5
              mb_check_encoding utf-8 string with ISO-8859-6
              mb_check_encoding utf-8 string with ISO-8859-7
              mb_check_encoding utf-8 string with ISO-8859-8
              mb_check_encoding utf-8 string with ISO-8859-9
              mb_check_encoding utf-8 string with ISO-8859-10
              mb_check_encoding utf-8 string with ISO-8859-13
              mb_check_encoding utf-8 string with ISO-8859-14
              mb_check_encoding utf-8 string with ISO-8859-15
              mb_check_encoding utf-8 string with ISO-8859-16
              mb_check_encoding utf-8 string with EUC-CN
              mb_check_encoding utf-8 string with CP936
              mb_check_encoding utf-8 string with HZ
              mb_check_encoding utf-8 string with EUC-TW
              mb_check_encoding utf-8 string with BIG-5
              mb_check_encoding utf-8 string with EUC-KR
              mb_check_encoding utf-8 string with UHC
              mb_check_encoding utf-8 string with ISO-2022-KR
              mb_check_encoding utf-8 string with Windows-1251
              mb_check_encoding utf-8 string with CP866
              mb_check_encoding utf-8 string with KOI8-R
              Der einzige Unterschied ist demnach, dass ein UTF-8 string nicht als UTF-7 erkannt wird. Andersherum aber schon.

              Um sich nun vor dem falschen Input zu schützen, muss man so vorgehen:
              Code:
              function filter_text($val, $charset='UTF-8') {
              	return !mb_check_encoding($val, $charset) || mb_check_encoding($val, 'UTF-7') ? '' : htmlentities(trim($val), ENT_QUOTES, $charset);
              }
              meine PHP Scripte

              Kommentar


              • #8
                Zitat von hondatuner Beitrag anzeigen
                Eben nicht. In dem Beispielfall mit UTF-7 erkennt PHP die Zeichen nicht, weil htmlspecialchars / htmlentities kein UTF-7 kann. Der Browser dagegen schon.
                Der Kontext war eigentlich die Frage, warum du htmlentities statt htmlspecialchars verwendest. Außerdem sprachst du von einem Beispiel, in dem die Seite als UTF-8 ausgewiesen ist.

                Jo und genau für den Fall, dass ICH den passenden Header vergesse, will ich eben erst gar nicht die Daten annehmen.
                Du programmierst etwas einzig aus dem Grund, weil du Angst hast, zu vergessen, etwas anderes zu programmieren.

                Aber zugegeben, es hat einen Vorteil, das Encoding der Escape-Funktion explizit zu setzen: Die Seite könnte auch mit einem nicht ASCII-kompatiblen Zeichensatz ausgeliefert werden. Um mal von der Theorie ein wenig wegzukommen: Bei einer ordentlichen Architektur stellt sich die Frage überhaupt nicht. Hier etwa die escape-Funktion von Zend_View:

                PHP-Code:
                    /**
                     * Escapes a value for output in a view script.
                     *
                     * If escaping mechanism is one of htmlspecialchars or htmlentities, uses
                     * {@link $_encoding} setting.
                     *
                     * @param mixed $var The output to escape.
                     * @return mixed The escaped value.
                     */
                    
                public function escape($var)
                    {
                        if (
                in_array($this->_escape, array('htmlspecialchars''htmlentities'))) {
                            return 
                call_user_func($this->_escape$varENT_COMPAT$this->_encoding);
                        }

                        return 
                call_user_func($this->_escape$var);
                    } 
                "Nutze htmlspecialchars oder htmlentities mit $_encoding oder nutze eigene Funktion."

                Für Zeichensätze, die htmlspecialchars/-entities nicht unterstützt, könnte so eine eigene Funktion eingesetzt werden, ohne dass an der tatsächlichen Implementation (echo $view->escape('<p>Hallo Welt!</p>');) etwas geändert werden müsste. Natürlich müsste dran gedacht werden, eine entsprechende Funktion zuzuweisen.

                UTF-7 kann übrigens wahrscheinlich deshalb nicht erkannt werden, weil UTF-7 nur aus ASCII-Zeichen besteht.

                Demnach kann man sich nur vor dem falschen Input schützen, in dem man so vorgeht:
                Du brauchst für eine Ausgabe als UTF-7 bloß ein Äquivalent zu htmlspecialchars zu schreiben, das auf UTF-7 anspricht. mb_check_encoding usw. ist nicht notwendig.

                Kommentar


                • #9
                  Du brauchst für eine Ausgabe als UTF-7 bloß ein Äquivalent zu htmlspecialchars zu schreiben, das auf UTF-7 anspricht. mb_check_encoding usw. ist nicht notwendig.
                  Keine Ahnung wie ich das machen soll. Habe schon versucht die Bytes der jeweiligen Zeichen auszugeben und diese dann zu suchen, aber irgendwie wechseln die je nach Zeichenkette. Und das einzelne Umwandeln der escapewürdigen Zeichen per mb_convert_encoding(), um sie dann zu erkennen, brachte auch keinen Erfolg.

                  Aktuell gehts nur so ^^
                  Code:
                  function filter_text($val, $charset='UTF-8') {
                  	return !mb_check_encoding($val, $charset) || mb_check_encoding($val, 'UTF-7') ? '' : htmlspecialchars(trim($val), ENT_QUOTES, $charset);
                  }
                  function filter_username($val, $charset='UTF-8') {
                  	return !($val = filter_text($val)) || $val != str_replace(array(
                  		// non-printing ascii
                  		chr(0), chr(1), chr(2), chr(3), chr(4), chr(5), chr(6), chr(7), chr(8), chr(9), chr(10), chr(11), chr(12), chr(13), chr(14), chr(15), chr(16), chr(17), chr(18), chr(19), chr(20), chr(21), chr(22), chr(23), chr(24), chr(25), chr(26), chr(27), chr(28), chr(29), chr(30), chr(31), chr(127), chr(129), chr(141), chr(143), chr(144), chr(157),
                  		// our prohibited chars
                  		chr(160),// non-breaking space
                  		chr(173),// soft hyphen
                  		), '', $val) ? '' : $val;
                  }
                  meine PHP Scripte

                  Kommentar


                  • #10
                    PHP-Code:
                    <?php // Encoding der PHP-Datei: UTF-8

                    function htmlspecialchars_utf7($string)
                    {
                        
                    $string mb_convert_encoding($string'UTF-8''UTF-7');
                        
                    $string htmlspecialchars($stringENT_QUOTES'UTF-8');
                        return 
                    mb_convert_encoding($string'UTF-7''UTF-8');
                    }

                    // Charset zu UTF-8 ändern, um "Raw"-Ausgabe des UTF-7-Strings zu sehen
                    header('Content-Type: text/html; charset=utf-7');

                    $string "<script>alert(String.fromCharCode(88,83,83))</script>äöü€";
                    $string mb_convert_encoding($string'UTF-7''UTF-8');

                    #echo $string; // Test für UTF-7 XSS-Attacke
                    echo htmlspecialchars_utf7($string);
                    Sieht nicht sonderlich effizient aus, aber so sollte es zumindest gehen.

                    Wie gesagt, die Idee ist, aus jedem String, der auf der Seite ausgegeben werden soll, diejenigen Zeichen zu escapen, die bei Ausgabe im jeweiligen Charset der Seite als "Spezialzeichen" interpretiert würden.

                    Das heißt, das Charset muss explizit gesetzt werden und auch bekannt sein, um die richtige Escaping-Funktion auszuwählen und anzuwenden.

                    Dazu behaupte ich, dass es grundsätzlich unmöglich ist, Eingabedaten in einem unbekannten Charset so zu bearbeiten, dass sie in einem unbekannten Ausgabe-Charset XSS-sicher ausgegeben werden können.

                    Kommentar


                    • #11
                      Ja aber da ich eh kein UTF-7 annehmen möchte, kann ich mir auch das hin- und herwandeln sparen. Da reicht dann:
                      Code:
                      if (mb_check_encoding($val, 'UTF-7')){
                        return false;
                      }
                      Wenn man alles annehmen möchte und in UTF-8 konvertiert, sollte man diese Prüfung ja im Grunde eh machen, bevor man htmlspecialchars_utf7() anwendet, um keine Verluste bei anderen Zeichensätzen zu provozieren.

                      Deine Idee würde ich nur verfolgen, wenn man sich das hin- und herwandeln sparen und einfach nur die Multibytefolgen von &, ", ', <, und > replacen könnte. Aber wie gesagt resultiert mein Test immer andere Multibytefolgen, je nach Position des jeweiligen Zeichens:
                      String: <>
                      Hex: 2b 41 44 77 41 50 67 2d
                      Binary: 101011 1000001 1000100 1110111 1000001 1010000 1100111 101101
                      String: ><
                      Hex: 2b 41 44 34 41 50 41 2d
                      Binary: 101011 1000001 1000100 110100 1000001 1010000 1000001 101101
                      Also das hier geht auch nicht (dann hätte man sich die jeweiligen Bytefolgen in einen Zwischenspeicher legen und komplett auf mb_convert_encoding verzichten können):
                      Code:
                      <?php
                      $charsets = mb_list_encodings();
                      header('Content-Type: text/html; charset=utf-7');
                      $string = '<>';
                      $string = mb_convert_encoding($string, 'UTF-7');
                      echo str_replace(array(mb_convert_encoding('<', 'UTF-7'), mb_convert_encoding('>', 'UTF-7')), array('&gt;', '&lt;'), $string);
                      ?>
                      Ich verstehe nicht, warum das hier nicht geht:
                      Code:
                      echo mb_strpos($string, mb_convert_encoding('>', 'UTF-7'));
                      Mir scheint es so zu sein, als könnte PHP nicht bei jeder Multibyte-Funktion mit UTF-7 umgehen.
                      meine PHP Scripte

                      Kommentar


                      • #12
                        Ich habe mal ein paar Benchmarks gemacht und die jeweiligen Ergebnisse verglichen. Noch schneller und besser als mb_check_encoding() ist das:
                        Code:
                        function is_utf8($str) {
                        	return mb_detect_encoding($str, 'CP51932, UTF-8') === 'UTF-8' ? true : false;
                        }
                        Auf die Art deckelt man sogar UTF-7:
                        UTF-8 Charset Check

                        Ich habe fast alle denkbaren Kombinationen und auch mehrere Zeichensätze (bis zu 5) übergeben und die Reihenfolge geändert. Am Ende blieb "CP51932, UTF-8" übrig.

                        Statt CP51932 kann man auch einen der folgenden Zeichensätze als ersten Zeichensatz angeben:
                        EUC-JP eucJP-win EUC-CN EUC-TW BIG-5 EUC-KR
                        Alle resultieren die gleichen Ergebnisse. CP51932 war nur maginal schneller bei der Erkennung, daher habe ich mich für diesen entschieden.

                        Getestet habe ich einmal mit dem XSS-Angriff-String, aber auch mit einer längeren Lorem Ipsum Zeichenkette, die zusätzlich Sonderzeichen und kyrillische Zeichen enthielt.

                        Umso größer die Zeichenkette um so zuverlässiger wird die Erkennung auf UTF-8. Die Mini-XSS-Attacke als ISO wird zwar durchaus auch als UTF-8 bewertet, aber wird dann durch htmlspecialchars korrekt escaped. Also es resultiert daraus kein Problem.

                        Daraus resultiert meine Empfehlung:
                        Code:
                        function filter_text($val) {
                        	return mb_detect_encoding($string, 'CP51932, UTF-8') === 'UTF-8' ? htmlspecialchars(trim($val), ENT_QUOTES, 'UTF-8') : '';
                        }
                        Und so der Filter für Usernamen:
                        Code:
                        function filter_username($val) {
                        	return !($val = filter_text($val)) || $val !== str_replace(array(
                        		// non-printing ascii
                        		chr(0), chr(1), chr(2), chr(3), chr(4), chr(5), chr(6), chr(7), chr(8), chr(9), chr(10), chr(11), chr(12), chr(13), chr(14), chr(15), chr(16), chr(17), chr(18), chr(19), chr(20), chr(21), chr(22), chr(23), chr(24), chr(25), chr(26), chr(27), chr(28), chr(29), chr(30), chr(31), chr(127), chr(129), chr(141), chr(143), chr(144), chr(157),
                        		// our prohibited chars
                        		chr(160),// non-breaking space
                        		chr(173),// soft hyphen
                        		), '', $val) ? '' : $val;
                        }
                        meine PHP Scripte

                        Kommentar


                        • #13
                          Zitat von hondatuner
                          Code:
                          if (mb_check_encoding($val, 'UTF-7')){
                            return false;
                          }
                          UTF-7 besteht lediglich aus ASCII-Zeichen. Das heißt, auf diese Weise bekommst du haufenweise "False Negatives", weil mb_check_encoding potentiell auch einen unglücklich aufgebauten ASCII-String als UTF-7 erkennt.

                          Code:
                          <?php
                          
                          header('Content-Type: text/plain; charset=UTF-8');
                          
                          $val = '+AOQA9gD8AN8-'; // Byte_Repräsentation von "äöüß" in UTF-7
                          
                          var_dump($val);
                          var_dump(mb_check_encoding($val, 'UTF-7'));
                          var_dump(mb_check_encoding($val, 'UTF-8'));
                          var_dump(mb_check_encoding($val, 'ASCII'));
                          Du kannst jetzt natürlich argumentieren, dass niemand so aufgebaute Daten schickt, es sei denn, sie liegen im UTF-7-Format vor. Mir wäre das aber zu spekulativ. Ich möchte lieber verhindern, dass die Eventualität einer Verwechslung überhaupt besteht.

                          Deine Idee würde ich nur verfolgen
                          Hier solltest du vielleicht doch noch mal ein paar Schritte zurückgehen und überlegen, warum du gerade UTF-7-Daten überhaupt zurückweisen willst und nicht etwa auch Daten, die in einem beliebigen anderen Charset vorliegen (könnten), das bei einem unglücklichen Ausgabe-Charset zu XSS-Lücken führen könnte.

                          Zitat von mermshaus
                          Dazu behaupte ich, dass es grundsätzlich unmöglich ist, Eingabedaten in einem unbekannten Charset so zu bearbeiten, dass sie in einem unbekannten Ausgabe-Charset XSS-sicher ausgegeben werden können.
                          Es ist nicht auszuschließen, dass irgendjemand irgendwann ein Charset definiert (oder das bereits eines existiert), das aus dem Byte \x97 (das kleine "a" in ASCII) aus irgendeinem Grund oder dummen Zufall beim Mapping auf das Ausgabeformat ein "<" werden lässt. Anders ausgedrückt: Dass es ein UTF-6 gibt oder geben wird, das genau dasselbe "Problem" verursacht wie aktuell das UTF-7, das aber in deiner Validierung nicht berücksichtigt wird, weil du nicht um seine Existenz wusstest, als du den Code geschrieben hast.

                          Eingabevalidierung reicht einfach nicht aus. Daten *müssen* vor Ausgabe explizit gegen ein Charset escapet werden.

                          Wenn dich diese Argumentation überzeugt, sind wir wieder bei der simplen Lösung mit der Escape-Funktion aus #8 und eine htmlspecialchars_utf7-Funktion brauchst du wirklich nur dann, wenn du deine Seite als UTF-7 ausliefern möchtest. Solange das nicht der Fall ist, ist es eigentlich müßig, sich über ein effizientes Escaping-Verfahren zu unterhalten.

                          Zitat von hondatuner
                          wenn man sich das hin- und herwandeln sparen und einfach nur die Multibytefolgen von &, ", ', <, und > replacen könnte. Aber wie gesagt resultiert mein Test immer andere Multibytefolgen, je nach Position des jeweiligen Zeichens:
                          Lies dir bitte mal durch, wie das UTF-7-Format aufgebaut ist. Es ist (ähnlich wie base64) kein auf Bytes basierendes Format, deshalb ist eine Betrachtung auf Byte-Ebene wenig fruchtbar. Das kann nichts werden. Du müsstest eine Eingabe vermutlich zumindest teilweise in 6-Bit-Stücke zerhacken (so genau habe ich es mir auch nicht angesehen). Ein entsprechender Algorithmus ließe sich in PHP implementieren (oder wahrscheinlich auch googlen), aber ich wage nicht zu beurteilen, ob der im Zweifel überhaupt schneller wäre als der doppelte Aufruf der nativ kompilierten Umwandlungsfunktion.

                          Um es noch mal zu betonen: Diese Überlegungen sind hochgradig irrelevant, es sei denn, du willst deine Seite aus irgendeinem Grund als UTF-7 ausgeben.

                          * * *

                          Nächster Post:

                          Daraus resultiert meine Empfehlung:
                          Entschuldige, aber gegen diese beiden Funktionen muss ich ein starkes Veto einlegen. Da scheint völlig der theoretische Unterbau zu fehlen. Du hast in meinen Augen einfach solange herumprobiert, bis das Ergebnis zufällig zufriedenstellend war.

                          Ich habe fast alle denkbaren Kombinationen und auch mehrere Zeichensätze (bis zu 5) übergeben und die Reihenfolge geändert. Am Ende blieb "CP51932, UTF-8" übrig.
                          CP51932 war nur maginal schneller bei der Erkennung, daher habe ich mich für diesen entschieden.
                          Erklär bitte mal, was das zu bedeuten hat.

                          Kommentar


                          • #14
                            mb_check_encoding() nutzt zum Vergleich einfach nur den übergebenen Zeichensatz. In meinem Fall UTF-8.

                            Dadurch erkennt mb_check_encoding() häufig UTF-8 wo gar keines ist. Unter anderem bei ASCII, kyrillischen oder asiatischen Zeichensätzen. Aber auch bei UTF-7 (kommt natürlich auf die Stringlänge / -inhalt an).

                            Beim Testen bin ich dann auf mb_detect_encoding($str, 'CP51932, UTF-8') gestoßen. Hier wird alles erstmal auf CP51932 geprüft und damit ist die Fehlerquote dann auch weit aus geringer.

                            Du kannst jetzt natürlich argumentieren, dass niemand so aufgebaute Daten schickt, es sei denn, sie liegen im UTF-7-Format vor. Mir wäre das aber zu spekulativ. Ich möchte lieber verhindern, dass die Eventualität einer Verwechslung überhaupt besteht.
                            Jo ich hab das jetzt auch auf Grund der oben genannten Erkenntnis fallen gelassen. Ich kann jetzt zuverlässig UTF-7 erkennen und ablehnen. Das ist das was ich von Anfang an wollte.

                            Was ich bei Deiner Argumentation nicht verstehe ist, dass Du die Daten überhaupt annehmen willst und dann bei der Ausgabe erst escapest. Das wäre mir viel zu performancelastig. Denk doch mal an das Verhältnis Schreiben:Lesen. Es macht doch viel mehr Sinn den Input zu filtern als den Output.

                            Schon vom Empfinden her, wäre das nichts für mich, zu wissen, dass ich evtl. UTF-7 Attacken in der Datenbank gespeichert habe und die quasi nur darauf warten, dass ich einen Fehler bei der Ausgabe mache.
                            meine PHP Scripte

                            Kommentar


                            • #15
                              Ich halte deine Funktionen für unsinnig. Falls das so funktioniert, wie du es dir vorstellst, ist das Zufall. Nimm's bitte nicht persönlich, aber ich rate jedem davon ab, sie in dieser Form zu nutzen.

                              Was ich bei Deiner Argumentation nicht verstehe ist, dass Du die Daten überhaupt annehmen willst und dann bei der Ausgabe erst escapest. Das wäre mir viel zu performancelastig. Denk doch mal an das Verhältnis Schreiben:Lesen. Es macht doch viel mehr Sinn den Input zu filtern als den Output.
                              Ich hänge dem Paradigma an, konsequent Originaldaten zu speichern, um flexibel jede Art von Ausgabe erzeugen zu können, die die Daten zulassen. Wenn du beispielsweise vor dem Eintragen in die Datenbank bereits ein htmlspecialchars über die Daten bügelst, dann aber später feststellst, dass du doch ganz gerne noch weitere Entities ersetzt hättest, weil du die Daten als ISO-8859-1 ausgeben willst, stehst du vor dem Problem, dass dir das htmlspecialchars aus "<" bereits "&lt;" gemacht hat und ein weiteres htmlentities daraus ohne vorherige Rückumformung erstmal ein "&amp;lt;" machen würde.

                              Mir fällt spontan kein überzeugendes Beispiel ein, aber man kann zu sehr unschönen Lösungen gezwungen sein, wenn man die Originaldaten in etwas weiterverarbeitet hat, das doch nicht so optimal war, wie man anfangs dachte.

                              Nimm vielleicht einen BBCode-Parser. Die laufen auch erst vor der Ausgabe, nicht nach der Eingabe, um nachträglich Änderungen am Parser vornehmen zu können und um nachträglich den Code bearbeiten zu können.

                              Wenn das zu sehr auf die Performance schlägt, würde ich irgendeine Art von Caching empfehlen.

                              Schon vom Empfinden her, wäre das nichts für mich, zu wissen, dass ich evtl. UTF-7 Attacken in der Datenbank gespeichert habe und die quasi nur darauf warten, dass ich einen Fehler bei der Ausgabe mache.
                              Ohne dich beunruhigen zu wollen, aber vielleicht hast du UTF-6-Daten (vgl. einer der letzten meiner Posts) in deiner Datenbank, die nur darauf warten, dass du einen Fehler bei der Ausgabe machst.

                              Mit deinen Funktionen bekämpfst (falls überhaupt...) du lediglich ein Symptom (UTF-7), nicht die strukturelle Ursache (Möglichkeit der Erzeugung "gefährlicher" Zeichen durch Interpretation von Daten in einem anderen Zeichensatz).

                              Übrigens mal überlegt, was passiert, wenn du einen Fehler bei der Prüfung der Eingabe machst? Dann würdest du dir unter Umständen wünschen, die Originaldaten gespeichert zu haben, weil du den Fehler eventuell nicht "verlustfrei" für die umgeformten Daten beheben kannst.

                              Kommentar

                              Lädt...
                              X