PHP: XSS-Lücken in Webapplikationen verhindern

Das Cross-Site-Scripting (abgekürzt: XSS) ist ein Angriffsverfahren, bei dem man eine Sicherheitslücke in einer Webapplikation ausnutzt. Meistens treten XSS-Lücken dort auf, wo Benutzereingaben stattfinden. Das ist zum Beispiel bei einem Gästebuch der Fall, in das Benutzer ihren Namen, den Text und ihre E-Mail-Adresse hinterlassen können.

In diesem Artikel erfahrt ihr, wie ihr eure Webapplikationen vor XSS-Angriffen schützen könnt. Des Weiteren werden hier Beispiele aufgezeigt, mit denen man eine XSS-Lücke für einen Angriff ausnutzen kann.

Grundlegendes zum Verhindern von XSS-Lücken

Vorhandene XSS-Lücken sind in der Regel sehr einfach zu schließen. In den meisten Fällen werden diese aber einfach übersehen, so dass der Programmierer sich am Ende gar nicht bewusst ist, dass er eine XSS-Lücke in seinem Quellcode hat. Es gibt eine ganz einfache Regel, an die sich Programmierer von Webapplikationen immer halten sollten:

Alle Eingaben, die von außerhalb oder von einem Benutzer stammen, sind immer als feindlich zu betrachten. Das gilt solange, bis man die Daten mit entsprechenden Funktionen überprüft und gefiltert hat.

Eine PHP-Funktion zum Verhindern der XSS-Code-Ausführung wäre zum Beispiel htmlspecialchars. Diese Funktion erwartet als Parameter einen String und ersetzt in diesem alle Zeichen, die für eine erfolgreiche XSS-Code-Ausführung erforderlich wären. Eine weitere PHP-Funktion wäre htmlentities. Der Unterschied besteht darin, dass htmlentities wirklich alle Zeichen durch ihren entsprechenden HTML-Entity-Code ersetzt. Darunter also auch Umlaute.

$evilString = "Hello World <script>alert('XSS Code Execution');</script>";
$cleanString = htmlspecialchars($evilString);

Im Grunde reicht die Funktion htmlspecialchars aber aus, da es die wichtigsten Zeichen, die für die XSS-Code-Ausführung relevant sind, entsprechend ersetzt. Mehr Informationen zu dieser Funktion findet ihr im PHP-Manual.

Angriffsverfahren

Als Beispiel nehme ich jetzt mal ein Gästebuchskript, bei dem der Besucher folgende Felder ausfüllen kann: Name, Webseite und Nachricht. In das Nachrichtenfeld baue ich jetzt eine XSS-Lücke ein, mit der man feindlichen Javascript-Code injizieren kann. Dadurch kann man dann die nächsten Besucher, die das Gästebuch aufrufen, zum Beispiel auf eine mit Malware verseuchte Webseite weiterleiten oder Cookies-Informationen stehlen. Im Folgenden das HTML-Formular.

<form action="send.php" method="POST">
	<input type="text" name="name" /><br />
	<input type="text" name="website" /><br />
	<textarea name="message"></textarea><br />
	<input type="submit" name="submit" />
</form>

Und nun noch das PHP-Skript, das die HTTP-POST-Daten entgegennimmt und in einer MySQL-Datenbank speichert.

Die Funktion, die zum Parsen der Daten zuständig ist, sollte man übrigens immer bei der Ausgabe und nicht beim Schreiben in die MySQL-Datenbank ausführen. Das erspart einem viel Arbeit, wenn man später mal etwas ändern möchte und der Text leider schon "kaputt escapet" in der MySQL-Datenbank liegt.

if($_SERVER['REQUEST_METHOD'] == 'POST' AND isset($_POST['submit'])) {
	$DB = new Database();

	$data['name']    = $DB->real_escape_string($_POST['name']);
	$data['website'] = $DB->real_escape_string($_POST['website']);
	$data['message'] = $DB->real_escape_string($_POST['message']);

	$DB->query("INSERT INTO guestbook (
		name, website, message, time
	) VALUES (
		'".$data['name']."', '".$data['website']."', '".$data['message']."', '".time()."'
	)");
}

Der wesentliche Teil kommt erst bei der Ausgabe der Einträge aus der MySQL-Datenbank. Hierbei baue ich jetzt mal einen kleinen Flüchtigkeitsfehler ein, der sicher jedem Programmierer schon untergekommen ist. Ich werde alle Felder, außer das Feld mit der Nachricht, mit der Funktion htmlspecialchars parsen.

$DB = new Database();
$SQL = $DB->query("SELECT name, website, message, time FROM guestbook");

while($obj = $SQL->fetch_object()) {
	$output['name']     = htmlspecialchars($obj->name);
	$output['website']  = htmlspecialchars($obj->website);
	$output['message']  = $obj->messsage;

	$string = '<div>
		Geschrieben von: <span class="name">'.$output['name'].'</span><br />
		Geschrieben am: <span class="time">'.date('H:i:s', $obj->time).'</span><br /><br />
		<div class="message">'.$output['message'].'</div>
	</div>';

	echo $string;
}

Das Resultat ist jetzt eine offene XSS-Lücke im Feld für die Nachricht. Hier kann der Angreifer jetzt beliebigen HTML- und Javascript-Code ausführen. Wenn der Angreifer beim Eintragen ins Gästebuch in das Feld für die Nachricht zum Beispiel <script>alert('XSS')</script> geschrieben hätte, dann würde dieses Javascript genau so auch ausgeführt werden, da die Zeichen < und > nicht in ihren HTML-Entity-Code umgewandelt wurden.

In den anderen beiden Feldern hätte dieser Javascript-Code und generell HTML-Code keine Chance, weil die Zeichen < und > jeweils durch &lt; und &gt; ersetzt worden wären.

Wenn dies der Fall ist, dann kann man den Javascript-Code zwar auf der Webseite sehen, aber er kann nicht ausgeführt werden. Aus diesem Grund ist es so wichtig, dass man Benutzereingaben immer mit den entsprechenden Filter-Funktionen behandelt, denn sonst kann ein Angreifer beliebigen Javascript-Code ausführen und sogar Cookie-Informationen des Besuchers stehlen.

Fazit

Nehmt Eingaben, die von außen oder von einem Besucher kommen, immer ganz genau unter die Lupe und sorgt dafür, dass ein Angreifer keinen schädlichen Javascript-Code oder HTML-Code ausführen kann. Andernfalls kann das fatale Folgen für die Besucher und den Webmaster der Webseite haben.

Hinweis:
Dies ist ein älterer Artikel von meinem alten Blog. Die Kommentare zu diesem Artikel werden (falls vorhanden) später noch hinzugefügt.

Der Autor

Unter dem Namen »TheBlackPhantom« alias »BlackY« veröffentlichte ich auf meinem alten Blog, BlackPhantom.DE, in der Zeit von 2011 bis 2015 leidenschaftlich Beiträge über Computer, Internet, Sicherheit und Malware. Während der BlackPhantom-Zeit war ich noch grün hinter den Ohren und lernte viel dazu. Mehr Infos vielleicht in Zukunft...