Cross-Site Request Fälschungen

Abgesehen von der Ähnlichkeit im Namen haben Cross-Site-Request Forgeries (CSRF) mit XSS kaum etwas gemeinsam. Während XSS auf das Vertrauen eines Benutzers/Browsers in eine Webseite setzt, knüpft CSRF an das Vertrauen an, das eine Webseite in Ihre Benutzer setzt; also genau anders herum. CSRF Angriffe sind weniger bekannt, gefährlicher und schwieriger abzuwehren als XSS-Angriffe.

CSRF-Angriffe haben die folgenden Merkmale:

  • Ausnutzen des Vertrauens einer Seite in Ihre Benutzer
    Es geht hier nicht unbedingt um "Vertrauen" im üblichen Sinn, vielmehr darum, dass viele Anwendungen Ihren Benutzern einen Login anbieten und nach dem Login erweitere Rechte zur Verfügung stellen. User mit diesen Rechten sind potentielle Opfer – ebenso unwissende Komplizen des Angreifers.
  • Betrifft in erster Linie Seiten die auf die Identität eines Users Vertrauen
    Üblicherweise wird der (beispielsweise durch Anmeldung erlangte) Identität eines Benutzers ein hohe Bedeutung zugemessen und spiegelt sich auch in erweiterten Möglichkeiten wieder. Allerdings können auch mit einem sicheren Session-Management CSRF Angriffe erfolgreich sein.
  • Es werden HTTP Requests des Angreifers ausgeführt
    CSRF Angriffe beinhalten alle Angiffe, bei denen der Angreifer die http-Requests eines Users verfälscht. Es gibt hierzu verschiedene Techniken, die im folgenden beispielhaft aufgezeigt werden sollen.

Da es bei CSRF-Angriffen um das Fälschen von http-Requests geht ist es wichtig, einige Grundlagen zum Thema http zu vermitteln. Ein Web-Browser ist ein http-Client, der Webserver ist der http-Server. Der Client startet einen Datenaustausch mit dem Senden des Requests und der Webserver vervollständigt die Transaktion indem er eine Antwort (Response) sendet.

Ein typischer http-Request sieht wie folgt aus:

ZitatGET / HTTP/1.1
Host: example.org
User-Agent: Mozilla/5.0 Gecko
Accept: text/xml, image/png, image/jpeg, image/gif, */*

Die erste Zeile wird Request genannt und beinhaltet die Methode, Adresse und verwendete HTTP Version. Die anderen Zeilen sind die "HTTP Header". Jede Header-Bezeichnung wird von einem Doppelpunkt abgeschlossen und durch ein Leerzeichen von dem zugehörigen Wert getrennt.
Man sollte mit dem Umgang dieser Daten in PHP vertraut sein. Der folgende Code stellt den HTTP Request in einem String nach:

Zitat<?php
$request = '';
$request .= "{$_SERVER['REQUEST_METHOD']} ";
$request .= "{$_SERVER['REQUEST_URI']} ";
$request .= "{$_SERVER['SERVER_PROTOCOL']}\r\n";
$request .= "Host: {$_SERVER['HTTP_HOST']}\r\n";
$request .= "User-Agent: {$_SERVER['HTTP_USER_AGENT']}\r\n";
$request .= "Accept: {$_SERVER['HTTP_ACCEPT']}\r\n\r\n";
?>

Eine Antwort auf diese Anfrage könnte so aussehen:

ZitatHTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 57

<html>
<img src="http://example.org/image.png" />
</html>

Den Inhalt der Antwort sieht man, wenn man sich den Quelltext einer Seite im Browser ansehen. Der img-Tag in diesem Response-Auszug meldet dem Browser, dass eine weitere Ressource, ein Bild, benötigt wird. Der Browser fragt dieses Bild nun mit einem eigenen Request ab, so wie jede andere Ressource auch. Hier ein Beispiel für den Request des Bildes:

ZitatGET /image.png HTTP/1.1
Host: example.org
User-Agent: Mozilla/5.0 Gecko
Accept: text/xml, image/png, image/jpeg, image/gif, */*

Dies sollte nachdenklich machen: Der Browser fragt die im src-Attribut des img-Tags angegebene Adresse so ab, als würde der Benutzer sie selber eingetippt haben. Der Browser selber hat keine Vorrichtung eingebaut, die eine Prüfung vorsieht, dass hier wirklich ein Bild liegt bzw. dies erwartet wird.

Kombiniert man dies nun mit dem, was bereits über Formulare gesagt wurde und stelle sich eine Adresse im SRC des IMG-Tags entsprechend diesem Muster vor:

Zitathttp://stocks.example.org/buy.php?symbol=SCOX&quantity=1000

Ein Form-Submit kann von einem Bild-Request nicht unterschieden werden – beide können Anfragen der gleichen Art sein. Wenn register_globals aktiviert ist, spielt selbst die Formularroutine keine Rolle mehr (solange der Programmierer nicht sauber _POST etc. nutzt). Man kann also in einem IMG-Tag problemlos einen Formularaufruf per GET platzieren. Die Gefahren sind hoffentlich deutlich geworden.

Ein weiteres Merkmal, dass CSRF so mächtig macht, ist die Tatsache, dass alle zu einem URL hinterlegten Cookies in dem Request dieses URL mitgesendet werden. Ein User, der eine authentifizierte Verbindung zu der Seite stocks.example.tld aufgebaut hat – etwa durch einen Anmeldung - kann möglicherweise 1000 Anteile von SCOX kaufen, nur indem er eine Seite aufruft, die als IMG-Tag die Formularadresse aus obigem Beispiel aufruft.

Als Beispiel ein Formular das hypothetisch auf stocks.example.tld/form.html liegt:

Zitat<p>Buy Stocks Instantly!</p>
<form action="/buy.php">
<p>Symbol: <input type="text" name="symbol" /></p>
<p>Quantity:<input type="text" name="quantity" /></p>
<input type="submit" />
</form>

Wenn der Benutzer "SCOX" als Symbol und als Anzahl "1000" eingibt, wird damit folgender HTTP-Request erzeugt:

ZitatGET /buy.php?symbol=SCOX&quantity=1000 HTTP/1.1
Host: stocks.example.org
User-Agent: Mozilla/5.0 Gecko
Accept: text/xml, image/png, image/jpeg, image/gif, */*
Cookie: PHPSESSID=1234

Es wurde ein Cookie-Header mit in diesen Request aufgenommen, um zu zeigen, dass ein Cookie mit einer Session-ID zur Identifikation des Users verwendet wird. Wenn ein IMG-Tag auf die gleiche Adresse verweisen würde, würde der gleiche Cookie im Header mitgesendet – und der Server könnte die gefälschte Anfrage nicht von einer echten unterscheiden.

Es gibt ein paar Dinge, die Sie unternehmen können, um sich gegen CSRF zu schützen:

  • Besser POST als GET in Formularen nutzen.
  • $_POST Variablen $_REQUEST (und insbesondere register_globals=on) vorziehen.
  • Nicht auf Bequemlichkeit setzen. Auch wenn es vernünftig erscheint, dem Benutzer so viel Komfort wie möglich bei der Nutzung der Web-Anwendung zuzugestehen: Zu viel Komfort kann schnell zu problematischen Konsequenzen führen.
  • Immer die Nutzung der eigenen Formulare erzwingen und damit sicherstellen, dass gesendete Daten auch wirklich aus einem Formular der eigenen Web-Anwendung stammen und keinen anderen Ursprung haben können.

Als Beispiel die weitergehende Optimierung des bereits angesprochenen Muster-Messages-Boards:

Zitat<?php
$token = md5(time());
$fp = fopen('./tokens.txt', 'a');
fwrite($fp, "$token\n");
fclose($fp);
?>
<form method="POST">
<input type="hidden" name="token" value="<?php echo $token; ?>" />
<input type="text" name="message"><br />
<input type="submit">
</form>
<?php
$tokens = file('./tokens.txt');
if (in_array($_POST['token'], $tokens))
{
    if (isset($_POST['message']))
    {
        $message = htmlentities($_POST['message']);
        $fp = fopen('./messages.txt', 'a');
        fwrite($fp, "$message<br />");
        fclose($fp);
    }
}
readfile('./messages.txt');
?>

Das Message-Board hat aber immer noch Optimierungsmöglichkeiten...

Zeit ist immer vorhersagbar. Ein md5() auf einen timestamp anzuwenden ist keine wirkliche Zufallszahl. Besser sind Funktionen wie rand() oder uniqid(). Noch wichtiger: Es ist zu einfach für einen Angreifer, ein erlaubtes Token zu finden – einfach nur durch den Aufruf der Seite wird eines erzeugt und im Code hinterlegt. Er kann es jederzeit betrachten, so dass es momentan nicht mehr Sicherheit durch die Token-Funktion gibt als vorher. Hier nun ein unter diesen Aspekten erweitertes Message-Board:

Zitat<?php
session_start();
if (isset($_POST['message']))
{
if (isset($_SESSION['token']) && $_POST['token'] == $_SESSION['token'])
    {
        $message = htmlentities($_POST['message']);
        $fp = fopen('./messages.txt', 'a');
        fwrite($fp, "$message<br />");
        fclose($fp);
    }
}
$token = md5(uniqid(rand(), true));
$_SESSION['token'] = $token;
?>
<form method="POST">
<input type="hidden" name="token" value="<?php echo $token; ?>" />
<input type="text" name="message"><br />
<input type="submit">
</form>
<?php
readfile('./messages.txt');
?>


Netzwerke