Cross-Site Request Forgery (kurz CSRF) ist seit etwa 2001 bekannt. Trotzdem kann man immer wieder feststellen, dass es nach wie vor Probleme gibt, sich tatsächlich vor CSRF zu schützen. Entweder wurde überhaupt kein CSRF-Schutz implementiert oder dieser weist Fehler auf, sodass er wirkungslos ist.
Ein Cross-Site Request Forgery ist ein Angriff, bei dem ein Benutzer – ohne sein Wissen – eine Aktion innerhalb einer Applikation ausführt. Zum einen können Angreifer über diesen Umweg Aktionen innerhalb einer Anwendung durchführen, ohne dass dieser Zugriff auf die Applikation haben. Zum anderen erscheint in Audit-Logs der aufrufende Benutzer, was im Falle eines Incident Response-Prozesses negative Auswirkungen hat. Nicht nur dauert der Incident Response-Prozess länger, sondern es wird auch erst einmal der „Falsche“ verdächtigt.
Grundsätzlich ist es eher aufwändig, einen CSRF-Angriff praktisch durchzuführen, da mehrere Voraussetzungen erfüllt sein müssen:
Es muss der Aufruf an die Applikation bekannt sein. CSRF funktioniert unabhängig davon, ob HTTP-GET oder -POST verwendet wird!
Folgender Aufruf verdeutlicht den Angriff. Dies ist bewusst über POST gelöst, damit nicht immer der typische GET-Aufruf verwendet wird
POST /account/transfer HTTP/1.1
Host: evilbank.com
Content-Length: 54
from_account=12233456&to_account=66666666&amount=10000
Das ist die verkürzte Form eines HTTP-Aufrufs, einer Demo-Online-Banking-App, der 10.000 € (Parameter amount) von dem Konto 1223346 (Parameter from_account) auf das Konto 66666666 (Parameter to_account) überweist.
Wir gehen folgendermaßen vor:
Im ersten Schritt wird der Schadcode erzeugt, der die Abfrage absendet. Da der Aufruf über POST erfolgen soll, hat er die beiden Möglichkeiten, dies per XMLHttpRequest (AJAX) umzusetzen oder alternativ über ein einfaches Formular, welches per JavaScript abgesendet wird. Zur Demonstration verwenden wir den zweiten Fall
<html> <body> <form action="https://evilbank.com/money/transfer" id="myCSRFForm" method="post"> <input type="hidden" name="from_account" value="12233456" /> <input type="hidden" name="to_account" value="66666666" /> <input type="hidden" name="amount" value="10000" /> </form> <script type="text/javascript"> csrf_form = document.getElementById("myCSRFForm"); csrf_form.submit(); </script> </body> </html>
Wird diese HTML-Seite aufgerufen, wird die Anfrage ohne Zutun des Benutzers übertragen, da das Abschicken des Formulars über JavaScript erfolgt. Der Angreifer muss also diesen HTML-Code nur irgendwo platzieren, wo das Opfer früher oder später vorbeisurft.
Grundsätzlich klassifizieren wir Cross-Site Request Forgery t „Hoch“, also Stufe 4 von 5. Das liegt darin begründet, dass der Schaden enorm ist – unabhängig davon, ob der Angriff leicht durchzuführen ist oder nicht. Wie oben bereits erwähnt können CSRF-Angriffe für Rechte-Erweiterungen oder andere kritische Dinge verwendet werden.
Die meisten modernen Frameworks zur Entwicklung von Web-Applikationen haben schon CSRF-Schutzmechanismen integriert. Das ist die mit Abstand einfachste und damit wirtschaftlichste Methode, seine Anwendung vor CSRF zu schützen. Sollte dies nicht der Fall sein, und es muss selbst ein CSRF-Schutz implementiert werden, ist wie folgt vorzugehen:
Steht ein Session-Management zur Verfügung, ist es das Einfachste, kryptografisch sichere Zufallswerte zu erzeugen (mindestens 16 Bytes). Dieses CSRF-Token wird in der Session des Benutzers gespeichert und zusätzlich als Parameter bei jedem Aufruf, der den Zustand der Applikation ändert, mit übertragen. Die Übertragung des Parameters ist mithilfe von HTTP-POST oder als HTTP-Request-Header möglich. Dieses Vorgehen implementiert das Synchronizer Token Pattern.
Wichtig: Das so erzeugte CSRF-Token darf nicht über GET übertragen werden, da es sonst leicht abhandenkommen könnte (es steht z.B. in Server-Logfiles oder wird bei Copy&Paste mit übergeben). Außerdem darf es nicht ausschließlich als Cookie übertragen werden, da dieses automatisch bei jedem Aufruf mitgesendet wird. Damit wird es auch im Falle eines CSRF-Angriffs automatisiert übertragen.
Haben Sie keine Möglichkeit, Daten über einen Server-State abzuspeichern, beispielsweise im Embedded-Bereich oder bei massiv parallelen Systemen, gibt es einen anderen Ansatz. Dabei wird bei jeder Zustandsveränderung Aufruf ein Cookie mit einem CSRF-Token übertragen, das auch gleichzeitig als Parameter mitgesendet wird. Nur wenn diese übereinstimmen, nimmt der Server den Request an. Dies ist die Implementierung des „Double Submit Cookie“-Pattern des OWASP.
In meinen Augen muss allerdings sichergestellt werden, dass das Token serverseitig erzeugt wurde. Hätte der Angreifer nämlich über eine andere Schwachstelle die Möglichkeit, Cookies zu setzen (z.B. auf einer anderen Subdomain), könnte er den CSRF-Schutz aushebeln.
Natürlich ist das kein Problem, was nicht schon andere Entwickler auch hatten. PayPal hat deswegen sogar eine Bibliothek (jwt-csrf) entwickelt, die auf JSON Web Token (JWT) aufbaut.
Ist der Einsatz dieser Bibliothek nicht möglich, beispielsweise aufgrund beschränkter Ressourcen, kann folgendes vereinfachtes Verfahren verwendet werden, um ein verifizierbares Token zu erzeugen:
secret = (siehe beschreibung unten) user = (Benutzername, wenn vorhanden) timestamp = (Aktueller Timestamp. Empfohlen: Millisekunden seit epoch) random = (16-Byte kryptographisch sicherer Zufallswert) signature = sha256(user + timestamp + random + secret) token = url_encode(base64(user + timestamp + random + signature)) # auf keinen Fall das Secret!
Das oben erwähnte secret muss aus mindestens 32 Byte bestehen, kryptografisch sicher erzeugt werden und – das ist das allerwichtigste – pro Installation bzw. Gerät einmalig sein. Die einzige Ausnahme ist, wenn es ein verteiltes System ist – da muss das secret natürlich systemweit identisch sein.
Mit den Klartext-Informationen von user und timestamp kann die Gültigkeit der Signatur sichergestellt werden. Die Mitgabe des Zeitstempels erlaubt es sicherzustellen, dass Token nur eine bestimmte Zeit gültig sind. Zu empfehlen sind hier ein Zeitraum von 15 Minuten bis zu ein paar wenigen Stunden – je nach Kritikalität der Anwendung, wobei kürzer natürlich grundsätzlich besser ist.
Hier eine kleine Zusammenfassung unserer wichtigsten Prüfschritte währen eines Web-Applikations-Penetrationstest, um zu analysieren, ob eine Anwendung für Cross-Site Request Forgery anfällig ist.