Cross-Site-Scripting (XSS) in PHP
Cross-Site-Scripting (XSS) bedeutet das Einschleusen von HTML-Code oder JavaScript-Code in eure Anwendung. Solch ein Angriff kann auf eurer Seite entsprechenden Schaden anrichten, beispielsweise indem Besucher vertrauliche Daten in ein manipuliertes Formular eingeben oder der Besucher auf eine fremde, möglicherweise bösartige Website weitergeleitet wird.
Inhaltsverzeichnis
Grundlagen
Cross-Site-Scripting tritt dann auf, wenn eure Webanwendung Daten von einem Nutzer annimmt und diese ohne Überprüfung und Filterung an den Browser weiter schickt. Ein Angreifer kann so HTML-Code und JavaScript-Code in eure Webanwendung einschleusen. Ein bösartiger HTML-Code könnte beispielsweise euer Login-Formular manipulieren, so dass die eingegebenen Nutzerdaten an den Server des Angreifers gesendet wird.
Man unterscheidet dabei zwischen drei Arten von Cross-Site-Scripting:
Persistener Cross-Site-Scripting-Angriff
Bei einem persistentem Cross-Site-Scripting-Angriff wurde der potenziell schadhafte HTML-Code auf dem Server abgespeichert und wird nun ohne Filterung ausgegeben. Beispielsweise speichert ihr in einer Datenbank den Namen und den Text von eine Gästebuch ab und gebt die Einträge wie folgt aus:
1 2 3 4 5 6 7 8 9 10 |
<?php $pdo = new PDO('mysql:host=localhost;dbname=databasename', 'username', 'password'); $statement = $pdo->prepare("SELECT * FROM gaestebuch ORDER BY id DESC"); $statement->execute(array()); while($row = $statement->fetch()) { echo $row['name']." schrieb:<br />"; echo $row['text']."<br /><br />"; } ?> |
Hat nun einer eurer Besucher als Namen oder als Text folgenden Inhalt hinterlassen:
1 |
<script>alert("XSS");</script> |
So wird dieser JavaScript-Code ausgegeben und ausgeführt. In diesem Fall wird nur eine Warnmeldung ausgegeben. Der Angreifer könnte aber auch JavaScript-Code einbetten der euer Login-Formular manipuliert und die eingegebener Passwörter an den Angreifer sendet. Oder es wird JavaScript-Code ausgegeben, der den Besucher auf eine fremde Seite umleitet:
1 |
<script>window.location.replace("http://www.php-einfach.de");</script> |
Schon würden alle Aufrufer eures Gästebuchs auf PHP-Einfach.de umgeleitet werden. Uns würde es freuen, aber euch vermutlich nicht so.
Persistente XSS-Angriffe gehören meistens zur gefährlichsten Sorte der XSS-Angriffe, denn oftmals bekommt jeder Benutzer eurer Seite diesen schadhaften HTML-/JavaScript-Code untergeschoben. Deswegen sollten stets alle Nutzereingaben aus einer Datenbank entsprechend gefiltert werden. Wie dies geht erfahrt ihr später im Artikel.
Nicht-persistenter Cross-Site-Scripting-Angriff
Bei einem nicht-persistenten Angriff wird der eingeschleuste HTML-/JavaScript-Code nicht auf dem Server gespeichert, sondern nur direkt an den Browser gesendet. Ein Beispiel ist eine Suchfunktion, der man mittels dem GET-Parameter ?suche ein Suchbegriff übergeben kann. Typischer, verwundbarer Code sieht wie folgt aus:
1 2 3 4 5 |
<?php $suchbegriff = $_GET['suche']; echo "Deine Suche nach: $suchbegriff"; //... weiterer Code |
Ruft ein Angreifer diesen Script mittels suche.php?suche=<script>alert('XSS');</script> auf, so würde beim Aufruf der Website ein entsprechendes alert-Fenster erscheinen. Wie zuvor ist der Zerstörungskraft des Angreifers hier keine Grenze gesetzt und so kann das Login-Formular manipuliert werden oder der Besucher wird auf eine andere Seite umgeleitet.
Im Gegensatz zu persistenten Angriffen betritt diese HTML-Code-Injection nur den Aufrufer der Website. Das Gefahrenpotential ist deutlich geringer, sollte aber nicht vernachlässigt werden. Solch eine manipulierte URL kann ein Angreifer beispielsweise an andere Nutzer weiterschicken, beispielsweise per E-Mail. Rufen diese Nutzer den Link auf, wird der schadhafte Code bei ihnen ausgeführt. So könnte ein Angreifer beispielsweise Links zu ebay.de/suche.php?suche=<script>alert('Böser XSS Code');</script> verteilen. Der unbedachte Benutzer würde einen Link von eBay sehen und ohne weitere Bedenken diesen aufrufen und dabei gleichzeitig den eingeschleusten Code des Angreifers ausführen.
DOM-basierte Angriffe
Die letzte Art des Angriffs ist nicht direkt PHP spezifisch. Bei DOM-basierten Angriffen wird eine XSS-Schwachstelle in eurem JavaScript-Code ausgenutzt. Fragt ihr mittels JavaScript Benutzereingaben ab und gebt diese aus, beispielsweise GET-Parameter, so kann es wie bei PHP zu einer HTML-Code-Injection kommen. Auch bei Ausgaben von Benutzereingaben mittels JavaScript gilt es, diese entsprechend zu filtern.
Schutz vor Cross-Site-Scripting
Zum Schutz sollten alle Benutzereingaben, wirklich alle Benutzereingaben, mittels htmlspecialchars() behandelt werden. htmlspecialchars() wandelt Sonderzeichen in entsprechende HTML-Codes um, so wird ein < durch < ersetzt. Im Browser wird dieser HTML-Code so nicht mehr ausgeführt, sondern wie gewünscht dargestellt.
Es ist egal, ob ihr die Eingaben aus der Datenbank auslest, ihr GET-Parameter ausgebt oder alte Formulardaten, alle Daten sollten mittels htmlspecialchars() entsprechend umgewandelt werden. Um die Arbeit etwas zu vereinfachen, bietet es sich an kürzere Schreibweise für das behandeln von Nutzerdaten zu definieren, beispielsweise die Funktion e() für escape:
1 2 3 |
function e($string) { return htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); } |
Die Gästebuch-Anwendung von oben sieht dann beispielsweise wie folgt aus:
1 2 3 4 5 6 7 8 9 10 |
<?php $pdo = new PDO('mysql:host=localhost;dbname=databasename', 'username', 'password'); $statement = $pdo->prepare("SELECT * FROM gaestebuch ORDER BY id DESC"); $statement->execute(array()); while($row = $statement->fetch()) { echo e($row['name'])." schrieb:<br />"; echo e($row['text'])."<br /><br />"; } ?> |
Im obigen Beispiel wird UTF-8 als Zeichensatz verwendet. Nutz eure Anwendung noch ISO-8859-1, dann kann dies zu Problemen bei Umlauten führen. In diesem Fall solltet ihr UTF-8 entsprechend durch ISO-8859-1 austauschen.
Das escapen der Benutzereingaben sollte nach dem Abspeichern erfolgen, sprich, immer wenn ihr die Eingaben wieder ausgebt. Das escapen vor dem Abspeichern kann zu einigen Komplikationen führen wenn ihr wieder die Originaldaten benötigt.
Autor: Nils Reimers