Blog

CSRF in HTTP-basierten Binärprotokollen

Einleitung: Was ist eine CSRF?

Wir haben zwar letzte Woche schon einmal ausführlich über CSRF geschrieben, dennoch möchte ich hier rekapitulieren, damit dieser Artikel auch für sich stehen kann.

HTTP ist im Grunde ein stateless-Protokoll. Man stellt eine Anfrage an einen Server und bekommt darauf die Antwort. Der Server schließt nun die Verbindung und das Spiel beginnt von neuem. Damit der Server weiß, wer man ist, muss zwingend eine Art Token mitgeschickt werden. Dies geschieht traditionell mit Cookies. Mit Cookies kann der Server auf dem Client Informationen speichern. Ein Cookie kann für einen bestimmten Pfad für einen gewissen Zeitraum vom Server beim Client gesetzt werden. Diese Informationen werden auch bei jeder Anfrage automatisch mitgeschickt. Da diese Cookies einen State in HTTP implementieren, werden sie auch verwendet, um Authentifizierungen zu regeln. Meist wird nach einem Login ein Session Cookie generiert mit einem zufälligen Token, um dem Client die Möglichkeit zu bieten, ohne weiteren Login die Seite zu verwenden.

Man kann also davon ausgehen, dass, wenn eine Seite aufgerufen wird und ein gültiges Session Cookie existiert, der Benutzer automatisch eingeloggt ist. Eine Cross-Site-Request-Forgery-Attacke beruht also darauf, ein Opfer auf eine vom Angreifer präpierte Seite zu locken, von der ein Request zu der betreffenden Seite gestartet wird, welcher, da der Benutzer durch das Session Cookie eingeloggt ist, in seinem Kontext ausgeführt wird.

Betrachten wir ein einfaches Beispiel:
Eine Bank führt ihre Überweisungen mit einem einfachen Aufruf aus, der wie folgt aussieht:

<form action="transferMoney.php" method="GET">
 <input type="text" name="ktnr" value="">
 <input type="text" name="amount" value="">
 <input type="submit" name="submit" value="Let it rain money">
</form>

Wenn man sich den Aufruf näher betrachtet, wird dieser Request gemacht:

GET /transferMoney.php?ktnr=123456&amount=100 HTTP/1.1
Host: bank.me
Cookie: SessionID=<totallyRandomSessionID>

Der gleiche Request lässt sich durch den Aufruf der URL: „http://bank.me/transferMoney.php?ktnr=123456&amount=100“ erzielen. Dies hat zur Folge, dass der Angreifer nur diese URL im Browser des Opfers aufrufen muss, um Geld auf das Konto 123456 zu transferieren.
Der einfachste Weg, eine URL aufzurufen, ist ein iframe.

Da der Session Cookie jedes Mal mitgeschickt wird, wenn das Opfer die Domain bank.me aufruft, präpariere ich eine Seite mit folgendem Code:

<iframe src="http://bank.me/transferMoney.php?ktnr=evilKtnr&blz=77050000&amount=100000"></iframe>

Beim Betreten der Seite wird die Seite bank.me aufgerufen und Geldtransfer von 100.000 Euro auf das Konto „evilKtnr“ veranlasst.

Man könnte jetzt natürlich die Parameter per POST übertragen und nicht an die URL anhängen. Gehen wir davon aus und die Bank veranlasst dieses, um ihre Applikation weiter abzusichern. Der Aufruf reicht also nicht mehr, da der Aufruf nun so aussieht:

POST /transferMoney.php HTTP/1.1
Host: bank.me
Cookie: SessionID=<totallyRandomSessionID>
Content-Length:22

ktnr=123456&amount=100

Die einfachste Möglichkeit, einen POST Request an dieses Skript „transferMoney.php“ zu schicken, ist wieder ein Formular zu bauen oder das der Bank mit einer kleinen Änderung zu übernehmen.

<form action="http://bank.me/transferMoney.php" method="POST">
 <input type="text" name="ktnr" value="evilKtnr">
 <input type="text" name="amount" value="100000">
</form>
<script>
document.getElementByTagName("form")[0].submit();
</script>

Das beigefügte JavaScript bewirkt, dass das Formular ohne Interaktion des Opfers abgeschickt wird.
So kann nun alles übertragen werden.

Dann verwende ich halt JSON

Wir überlegen uns, was passieren würde, wenn wir ein Formular mit einer Textarea mit dem Namen „x“ bauen und einem JSON als Value.
Nach dem oben genannten Beispiel sollte klar sein, dass der Request aussieht wie folgt:

POST /somewhere HTTP/1.1
Cookie: SessionID=<totallyRandomSessionID>
Content-Length:

x={'kant':'x**2+y**2=z**2'}

Wenn man versucht, „x={‚kant‘:’x**2+y**2=z**2′}“ durch JSON parsen zu lassen, wird es einen Fehler produzieren.

Die Formular-Variante

Betrachten wir aber, was da eigentlich geschickt wird, fällt uns auf, dass es sich um Strings handeln könnte.

An sich ist der Name „x“ auch nur Ballast, aber selbst, wenn wir es leer setzen, bleibt das „=“ bestehen. Wenn wir weiterhin Formulare verwenden wollen, ist es wohl die einfachste Möglichkeit, das = in das JSON einzugliedern. Im oben genannten Beispiel sähe das Formularfeld dazu so aus:

<input type="{'kant':'x**2+y**2" value="z**2'}">

Damit wuerde das = in das JSON eingebunden werden und ein korrektes JSON produzieren.

XHR-Requests

Um das Ganze zu umgehen und da wir eh JavaScript einsetzen müssen, um das Formular ohne Interaktion abzusenden, kann durch die Möglichkeit der neueren Browser auch direkt auf reines JavaScript mit XHR Requests zurückgegriffen werden.

XHR bezeichnet den im Umgangssprachlichen besser bekannten AJAX Request.

Durch einen einfachen Request (hier mit Jquery vereinfacht), kann sehr einfach ein JSON-Request gemacht werden.

$.ajax({
 url: '%your_service_url%',
 type: 'POST',
 contentType: 'text/json', 
 data: {"a": true,"b": "some string"},
 processData: false
});

Man kann nicht wirklich binär schicken?

Das ist das schöne an XHR Requests. Die einfache Antwort ist: Doch! Man kann JavaScript XHR Requests Typed Arrays übergeben, was nicht nur die JSON-Angriffsvariante deutlich erleichtert, sondern es auch möglich macht, Binärdaten in einem HTTP Request unterzubringen.

var bytesToSend = [253, 0, 128, 1],
 bytesToSendCount = bytesToSend.length;

var bytesArray = new Uint8Array(bytesToSendCount);
for (var i = 0, l = bytesToSendCount; i < l; i++) {
 bytesArray[i] = bytesToSend[i];
}

$.ajax({
 url: '%your_service_url%',
 type: 'POST',
 contentType: 'application/octet-stream', 
 data: bytesArray,
 processData: false
});

Dabei werden die einzelnen Bytes in ein Typed Array gebracht, welches man mit einem normalen XHR Request (Hier mit JQuery vereinfacht), abschickt.

Der Content-Type kann dabei aber nur drei Werte annehmen (text/plain, multipart/form-data, application/x-www-form-urlencoded), ohne dass ein Pre-Flight geschickt wird. Auch können die Header nicht bearbeitet werden, was einen Eingriff massiv einschränkt.

Trotzdem sollte hier beachtet werden, dass dieser Pre-Flight spezifiziert ist, aber in einzelnen Fällen von Plugins umgangen werden kann. So gab es eine Lücke in Flash, bei der man eine Anfrage an einen dritten Server (Server eines Angreifers) stellte. Diese Seite gab den Pre-Flight, der alles erlaubte und leitet mit einem 307er weiter auf die eigentlich anzugreifende Seite. Die Folge war, dass alle Header mitgeschickt wurden und kein neuer Pre-Flight gesendet wurde.

Dies macht es nun möglich, CSRF Angriffe auf alle Protokollarten durchzuführen, die auf HTTP basieren.

Einschränkungen

Ich erwähnte bereits Einschränkungen, mit denen in neueren Browsern zu rechnen ist.

  • Vorhandenes Session Cookie im Browser: Die erste große Einschränkung wäre wohl das zur Verfügung stehende Session Cookie. Eine CSRF-Attacke findet standardgemäß im Browser statt. Dementsprechend muss auch hier das Session Cookie vorhanden sein. Bei einer reinen Desktop-Applikation ist dies eher unwahrscheinlich.
  • Veränderung der Header: eine Veränderung der Header hat im aktuellen Browser einen Pre-Flight zur Folge. Dies führt zu keiner korrekten Ausführung der CSRF-Attacke.
  • Verwendung eines vernünftigen CSRF Token: Naja, dann ist alles richtig implementiert und man kann einpacken mit CSRF.

Fazit

Es ist also nicht leicht, diese Art des Angriffs auszuführen, weil es doch recht viele Einschränkungen gibt. Sollte man aber alle Bedingungen erfüllen, kann der Angriff sehr effektiv sein.
Es sollte also bei jedem Request, der irgendwie an eine API geht, ein CSRF-Token mitgeschickt werden, damit so ein Angriff erkannt werden kann. Wie genau ein solches Token implementiert wird, kann im Artikel von letzter Woche nachgelesen werden (https://www.securai.de/veroeffentlichungen/blog/cross-site-request-forgery-unendliche-geschichte/).

Vorheriger Beitrag
Cross-Site Request Forgery (CSRF) – die unendliche Geschichte
Nächster Beitrag
Sichere Konfiguration eines VPN mit OpenVPN

Ähnliche Beiträge

Es wurden keine Ergebnisse gefunden, die deinen Suchkriterien entsprechen.