Cross Site Scripting trotz htmlentities() [UPDATE]
Die PHP-Funktion htmlentities() soll vor Cross Site Scripting Angriffen schützen, denn sie kodiert Zeichen wie „<", ">„, sowie auch doppelte und einfache Anführungszeichen in deren HTML-Code-Entsprechung um. Dass allerdings die Kodierung der Anführungszeichen explizit eingeschaltet werden muss kann wohl als Designschwäche gesehen werden.
Eine weitere Schwachstelle beschrieb Chris Shifflet bereits 2005. Dabei behandelt htmlentites() UTF7-Codierte Zeichenketten nicht korrekt, was sich in manchen Fällen für XSS-Angriffe ausnutzen lässt.
Wie wird eine Zeichenkette konvertiert? Eine Zeichenkette wie diese:
<br />
<script>alert('XSS')</script><br />
wird durch htmlentities() umkodiert und wird so vom Browser nicht mehr ausgeführt. Der Cross-Site-Scripting-Angriff ist hier erstmal verhindert.
<br />
<script>alert('XSS')</script><br />
Anführungszeichen nicht benötigt
htmlentities() kodiert jedoch nicht andere Zeichen wie z.B. Klammern „(„, „)“ und Gleichheitszeichen „=“. Wird der Angriffscode in Zeichencodes konvertiert, lassen sich damit Sonderzeichen am Filter vorbei schleusen. Dieser Code passiert daher htmlentities() ohne Änderung:
<br />
javascript:eval(String.fromCharCode(97,108,101,114,116,40,39,88,83,83,39,41))<br />
Dies kann jetzt beispielsweise von den Angreifer an Stelle einer URL eingefügt werden.
<br />
<a href="echo htmlentities($_GET['homepage']); ?>„></p>
<p>wird zu:</p>
<p><a href="javascript:eval(String.fromCharCode(97,108,101,114,116,40,39,88,83,83,39,41))"><br />
Eventangriffe
Durch die Konvertierung der Anführungszeichen und spitzen Klammern kann der Angreifer den JavaScript-Code nicht in ein mit htmlentities() geschütztes Formularfeld einfügen oder aus diesen ausbrechen. Sind die Formularfeld-Attribute aber nicht durch einfache oder doppelte Anführungszeichen umschlossen – was nicht verpflichtend ist – dann kann der Angreifer, mit Hilfe der JavaScript-Events, seinen Schadcode einfügen:
Angriffsvektor:
<br />
5 onclick=eval(String.fromCharCode(97,108,101,114,116,40,39,88,83,83,39,41))<br />
Wird in ein Eingabefeld ohne umschließende Anführungszeichen zu einem JavaScript-Event:
<br />
<input type=text name=id value=5 onclick=eval(String.fromCharCode(97,108,101,114,116,40,39,88,83,83,39,41))><br />
Schutzmaßnahmen
- Eine Web-Firewall die Befehle wie „eval“ und „fromCharCode“ erkennt und abweist.
- Eine Validierung, die nur passende Eingaben zulässt:
- http:// oder www. für Webseiten
- Nur Zahlen für IDs
- usw.
- Eine bessere Kodierung der Ausgabe.
Konzept eines Ausgabefilters auf Grundlage einer Whitelist
In [BSI01, S. 35] wird ein Beispiel für einen Whitelist-Filter für die Programmiersprache Perl skizziert. Kern des Filters ist ein regulärer Ausdruck, der selektiv nur folgende Zeichen unkodiert akzeptiert:
Regulärer Ausdruck | Bedeutung |
a-z | Buchstaben von „a“ bis „z“, klein |
A-Z | Buchstaben von „a“ bis „z“, groß |
0-9 | Zahlen von 0 bis 9 |
_ | Unterstrich |
. | Punkt |
– | Bindestrich |
Tabelle 12: Whitelist – akzeptierte Zeichen
Zeichen, die nicht in dieser Liste stehen, werden hexadezimal kodiert und somit weiterhin korrekt in einen Webbrowser dargestellt. Jedoch können sie nicht länger zur Durchführung eines XSS-Angriffs verwendet werden. Für die Sicherheitsbibliothek SSEQ_LIB wurde die Funktion nach PHP portiert.
UPDATE 11.06.2009: Die neue Version der Funktion SEQ_OUTPUT kann auch multi-byte Zeichen korrekt kodieren. Ein Nachpflegen von Umlaute oder Sonderzeichen sollte damit entfallen.
(Code veraltet, siehe Update)
<br />
<?php
function SEQ_OUTPUT($string_ = '') {
$string = preg_replace_callback('/([^a-zA-Z0-9_.-])/',
create_function('$match_',
'return sprintf("&#x%04x;", ord($match_[1]));'),
$string_);
return $string;
}
?><br />
Abbildung 53: Whitelist-Filter für PHP-Ausgabe
Der oben gezeigte Angriffscode wird nun konvertiert:
<br />
5 onclick=eval(String.fromCharCode(97,108,101,114,116,40,39,88,83,83,39,41))</p>
<p>wird zu:</p>
<p>5 onclick=eval(String.fromCharCode(97,108,101,114,116,40,39,88,83,83,39,41))<br />
Da mit der Funktion SEQ_OUTPUT() auch Zeichen wie Klammern „(„, „)“ und Gleichheitszeichen „=“ konvertiert werden, wird der Cross-Site-Scripting-Angriff hier gestoppt.
Anmerkung: Eine derart konvertierte Zeichenkette wird von einem Formular in encodierter Form gesendet, kommt also beim Skript wieder in „ungeschützter“ Form an. In der weiteren Verarbeitung muss sie also ebenfalls mit der Funktion SEQ_OUTPUT() behandelt werden.
zitat:
[…] Sind die Formularfeld-Attribute aber nicht durch einfache oder doppelte Anführungszeichen umschlossen – was nicht verpflichtend ist – dann kann der Angreifer, mit Hilfe der JavaScript-Events, seinen Schadcode einfügen[…]
autsch. auf schlechtem (und missbilligtem) programmierstil aufbauend wird hier ein problem gepushed, was eigentlich keins darstellt.
damit steht der artikel in einer reihe mit zahllosen anderen xss-panikmachen, die im netz herumgeistern. anstatt die einzig vernünftige (und robuste) lösung zu nennen – nämlich alle werte eines HTML-attributs in single / double quotes zu setzen – wird ein völlig überflüssiges workaround konstruiert.
cx
Ich fürchte, Du hast den Artikel falsch verstanden. Diese Funktion ist kein Workaround sondern – eben auf Grund der hier gezeigten, eher untypischen Angriffsstelle – ganz allgemein eine etwas bessere Lösung gegen XSS-Angriffe als „htmlentites()“ alleine.
Bei unbekannten Anwendungen kann man außerdem leider nicht von einem „vernünftigen“ und „robusten“ Code ausgehen. Möchte man auch dafür eine Lösung schaffen, kommt man um die Analyse eher unbeachteter Schwachstellen nicht herum.
nun, ich fürchte, du hast meinen kommentar auch nicht ganz verstanden. htmlentities( ) KANN keine lösung gegen xss-angriffe sein – weder allein, noch im verbund mit deiner funktion / workaround. übrigens, wenn man um ein problem „drumherum“ arbeit, IST es ein workaround; dazu auch [http://de.wikipedia.org/wiki/Workaround].
die vielfältigen spielarten für xss-angriffsvektoren zeigt uns das xss-cheat sheet [http://ha.ckers.org/xss.html]. schaut man sich die sammlung genauer an, drängt sich die schwäche einer lösung, die sich stark / lediglich auf htmlentities( ) verlässt, von ganz allein auf. insofern stellt der artikel weder ein neues problem vor, noch zeigt eine – ja – robuste lösung.
ich wiederhole mich: ich brauche viele lustige (sicherheits-)skripte nicht, wenn ich sauber programmiere. artikel wie dieser stellen leider viel zu selten klar:
1. wer überhaupt betroffen ist (wie gross ist wohl der anteil der webanwendungen, die über einfachen text hinaus gehende eingaben wie HTML akzeptieren müssen?)
2. wie man die probleme grundlegend vermeidet und nicht, welche tollen abwehrmasznahmen in FRAGE kommen, wenn die GEFAHR besteht, dass das kind in den brunnen fallen KÖNNTE, weil man IRGENDWAS zusammengeschustert hat.
zu „unbekannten“ anwendungen… dazu fällt mir ehrlich gesagt nichts mehr ein, ausser: nicht verwenden / benutzen.
darüber hinaus… wie will ich die schwachstellen einer „unbekannten“ anwendung ausbügeln, wenn sie „unbekannt“ ist? im „unbekannten“ und undurchsichtigen quelltext herum hacken? wenn ich mit einer frenden anwendung arbeite, bin ich auf die kompetenz der entwickler angewiesen / muss mich darauf verlassen – ob ich die anwendung nun vom front- oder backend aus betrachte. die jüngsten vorfälle im dunstkreis von wordpress (und die hernach ausbrechende panik) haben uns allen gezeigt, wie sehr diese einschätzung zutrifft.
in diesem sinne,
cx
„[…]htmlentities( ) KANN keine lösung gegen xss-angriffe sein – weder allein, noch im verbund mit deiner funktion[…]“
Bitte beweisen. Behauptungen sind genug vorhanden.
„[…]noch zeigt eine – ja – robuste lösung[…]“
Ich darf doch um ein XSS-String deiner Wahl bitten, der durch meine Funktion nicht „entschärft“ wird. Bedien dich gerne aus [http://ha.ckers.org/xss.html].
„[…]ich brauche viele lustige (sicherheits-)skripte nicht, wenn ich sauber programmiere[…]“
Wie willst du diese Zeile ohne Mithilfe eines Skriptes gegen XSS-Angriffe schützen?
>> echo $_GET[‚username‘];
„[…]artikel wie dieser stellen leider viel zu selten klar:[…]“
Du geht mit der falschen Prämisse heran: hier geht es ganz bewusst um Abwehrmaßnahmen. Wenn wundert es also, dass nicht was anderes drin steht?
„[…]zu „unbekannten“ anwendungen […] nicht verwenden / benutzen[…]“
Also ich lese WordPress nicht „durch“, bevor ich es installiere.
„[…] wie will ich die schwachstellen einer „unbekannten“ anwendung ausbügeln, wenn sie „unbekannt“ ist? […]“
Es gibt in der Tat Möglichkeiten! Web-Firewalls sind z.B. eine davon.
„[…]bin ich auf die kompetenz der entwickler angewiesen / muss mich darauf verlassen[…]“
Die Presse ist voll von Fällen die zeigen, wie viel „Verlass“ darauf ist.
Zusammenfassen denke ich, dass du von diesem Artikel schlichtweg einen anderen Inhalt erwartet hast. Was würdest du denn gerne in einem Artikel über XSS-Angriffe und wie man sie durch saubere Programmierung vermeidet, lesen?
Grüße!
[…] denke ich, dass du von diesem Artikel schlichtweg einen anderen Inhalt erwartet hast[…]
vielleicht hast du recht…
gruss,
cx
hi erich,
ich verwende die funktion mittlerweile bei allen seiten die user output haben und verlasse mich somit nicht mehr auf htmlentities();. allerdings habe ich mein preg_replace_callback noch mit umlauten (öäüÖÄÜ) gefüttert. vielen dank für die info und für die function.
grüße
Hallo dirk,
inzwischen gibt es eine neue Version der SEQ_OUTPUT, die auch multi-byte Zeichen korrekt kodiert. Damit sollte das Nachrüsten für Umlaute entfallen.
Danke für Deine Anmerkung, ich habe im Artikel daraufhin die neue Version vermerkt.
Hhhm, der veraltete Code von SEQ_OUTPUT funktioniert bei mir. Der neue dagegen nicht. Ich setze zum Testen PHP 5.1.2 auf einem Win32 System ein.
Da bleibt das Script beim Aufruf der Funktion mb_convert_encoding hängen, die keinen Output liefert.
@Christian: welches encoding benutzt dein Webserver und was für ein Webserver ist es?