Skip to main content

So schützen wir Benutzer vor Website-übergreifenden Angriffen – Cross-Site-Request-Forgery

Oktober

22, 2020

by Iron_Legion


Tech

Stell dir folgendes Szenario vor: Nachdem du dich zum Online-Banking eingeloggt hast, um deinen Kontostand zu überprüfen, gehst du zu einer anderen Website. Plötzlich stellst du fest, dass dein Konto gestohlen wurde! Dein Guthaben wird geplündert und der Angreifer versucht, dein Passwort zurückzusetzen. Du bist eigentlich ziemlich sicherheitsbewusst und fällst nie auf Phishing-Versuche herein oder teilst dein Passwort. Wie konnte das also passieren?

Es kann sein, dass es sich hierbei um Cross-Site-Request-Forgery handelt, was oft als CSRF oder manchmal auch XSRF abgekürzt wird. XSRF ist eine weitverbreitete Internet-Sicherheitslücke, vor der in der Vergangenheit sogar Tech-Giganten wir Google, Netflix und Microsoft nicht immun waren. Zum Glück hat Roblox Schutzmaßnahmen gegen XSRF eingerichtet, aber lassen wir uns erst einmal ansehen, wie es funktioniert und welche Tools wir einsetzen, um unsere Website zu schützen.

Du besuchst die Website eines Angreifers und diese Website veranlasst deinen Browser, Requests in deinem Namen zu senden. Sie können entweder deine Daten manipulieren oder bewirken, dass dein Browser Aktionen an deiner Stelle durchführt, wie z. B. ein Passwort zurückzusetzen, deine E-Mail zu ändern oder einen virtuellen Gegenstand zu kaufen. Da dein Browser deine Cookies und Sitzungsinformationen mit dem Request enthält, vertraut der Web-Server diesem Request und führt die Anforderung aus.

Diese Lücke gibt es schon fast so lange wie das Internet selbst. Eine Reihe von Lösungen wurden entwickelt, um den Referrer oder den Origin-Header zu bestätigen, diese funktionieren jedoch nicht in allen Fällen. SameSite-Cookies sind in vielen Fällen hilfreich, werden aber nicht von allen Browsern unterstützt und benötigen eine zusätzliche Schutzebene.

Die Industriestandardlösung ist es, einen “XSRF-Token” an alle AJAX-Requests oder Formular-Posts, die von einer Website gesendet wurden, zu koppeln. Der XSRF-Token ist ein geheimer, für jeden Benutzer einzigartiger Wert, der üblicherweise in der Seitenquelle eingebettet ist, wodurch er für das JavaScript, das auf deinem Domain läuft, zugänglich wird, nicht jedoch für Websites Dritter.

Wenn ein HTTP-Request das Backend erreicht, bestätigt der Web-Server, dass der XSRF-Token mit dem erwarteten Wert übereinstimmt. Wenn keine Übereinstimmung festgestellt wurde, wird der Request abgelehnt und der Endpoint verursacht eine entsprechende Fehlermeldung. Es ist zu beachten, dass der Wert für jeden Benutzer einzigartig ist und sich mit der Zeit ändert, um das Risiko von Replay-Angriffen zu verringern.

XSRF bei Roblox

Bei Roblox wurde traditionell XSRF-Schutz auf Opt-in-Basis angewandt, wobei wir manuell jeden daten-mutierenden Endpoint mit einem C#-Attribut taggen mussten wie [XsrfProtection]. Diese Lösung war für viele Jahre durchaus passabel für Roblox, dennoch war sie von Grund auf unsicher, denn sie baute darauf auf, dass die Entwickler immer daran dachten, jedem Endpoint einen bestimmten Code hinzuzufügen, was nicht besonders effizient war. Wir fügten auch ein Code-Analyse-Tool hinzu, dass fehlende Codes anzeigte.

Als wir 2015 begannen, mit dem RESTful-Web-API-Framework zu arbeiten, fügten wir automatischen XSRF-Schutz hinzu mit der Option, ihn für bestimmte Endpoints zu deaktivieren. Das hat auch super funktioniert, aber wir hatten immer noch das Problem, Schutz zu unseren existierenden Endpoints hinzuzufügen.

Unser Ziel:

  • Ein Methode zu finden, mit der XSRF-Schutz flächendeckend für alle existierenden Endpoints eingeführt werden kann.
  • Die Lösung durfte keine speziellen Anpassungen der jeweiligen Endpoints erfordern.
  • Die Lösung sollte minimale oder keine Auswirkungen auf die Endnutzer haben.

Eine der Herausforderungen dabei ist, dass wir all unsere Front-End-Frameworks unterstützten müssen.

  • React (Haupt-Framework, benutzt mit Axios für HTTP-Calls)
  • AngularJS
  • jQuery
  • ASP.Net MVC
  • ASP.Net WebForms
  • Native C++ Code (benutzt für HTTP-Calls vom Roblox-Game-Client)

Hier wird es ein bisschen knifflig. Einige unserer älteren Seiten auf der Website sind auf reguläre Form Browser-Posts angewiesen, wobei der Browser für den Request verantwortlich ist. Ein Browser-Post unterstützt nicht das Anhängen von Headern oder das erneute Versuchen von fehlgeschlagenen Requests, die in einem alten Token durchkommen.

Für Seiten, die ASP.Net WebForms benutzen, werden die gesamten Nutzdaten der POST-Requests durch ViewState bestätigt, also müssen wir lediglich den ViewStateUserKey so anpassen, dass er für jeden Benutzer einzigartig ist, um XSRF zu verhindern.

Für ASP.Net MVC-Endpoints müssen wir zwei Anwendungsfälle bedenken:

  • AJAX-Requests
  • Browser-Formular-Posts

Auf jeden MVC-Endpoint kann mit beiden Methoden zugegriffen werden, was bedeutet, dass wir unsere Backend-Bestätigung so anpassen müssen, dass sie den XSRF-Token sowohl im Header als auch dem Formularkörper finden.

ASP.Nets eingebauter Ansatz für Browser-Formular-Posts ist die Nutzung eines [ValidateAntiForgeryToken] Attributs. Es muss jedoch jedem Endpoint manuell hinzugefügt werden. Zudem unterstützt es keine AJAX-Requests, es sei denn die Requests finden auf der Seite statt, die den Fälschungspräventions-Token beinhaltet.

Teil 1: XSRF automatisch als Header anhängen

In usereren Core JavaScript Libraries registrieren wir einen Handler, um jeden Request zu verändern, bevor er abgesendet wird, und hängen den XSRF-Token als Header an, wenn der Request ein POST/PATCH/PUT/DELETE ist. Der Code ist so angelegt, dass er automatisch einen neuen Versuch startet, wenn der Request mit dem Status 403 „Token-Bestätigung fehlgeschlagen“ zurückkommt. Diese Abstraktion ermöglicht es dem JavaScript-Client-Code, sich nicht mit XSFR beschäftigen zu müssen.

Wir müssen diese Handler auf drei verschiedene Weisen registrieren, je nach dem JavaScript-Framework, das Endergebnis ist jedoch fast identisch.

  • React – axios.interceptors.request.use
  • AngularJS – httpProvider.interceptors.push
  • jQuery – $.ajaxPrefilter

Teil 2: Automatisch XSRF-Token in Formular-Posts injizieren

Da wir eine allgemeine Lösung wollten, die für alle Fälle funktioniert und keine speziellen Anpassungen für individuelle Seiten erfordert, entschlossen wir uns dazu, JavaScript zu schreiben, das alle Browser-Formular-Sendungen abfängt.

Das kann erreicht werden, indem man das Verhalten des HTMLFormElement.prototype.submit mit einer Funktion überschreibt, die ein <input name=”CsrfToken” value=”secret”> an das Formular anhängt, bevor es gesendet wird.

Was passiert, wenn der Benutzer eine Seite für ein paar Minuten nicht benutzt und dann versucht, ein Formular zu senden, nachdem der XSRF-Token schon abgelaufen ist? Für AJAX-Anfragen ist das gar kein Problem, wir können den Request einfach erneut senden. Bei Formular-Posts kontrolliert allerdings der Browser den Seitenverkehr und wir können dieses Szenario nicht besonders elegant bewältigen. Unsere Lösung ist es, ein Endpoint aufzuführen, das den XSRF-Token in den Response-Headern produziert hat bei einem Request vom Client. Wenn der Benutzer versucht, ein Formular abzusenden und wir bemerken, dass der Token anhand der ursprünglichen Ladezeit der Seite abgelaufen ist, brechen wir den Request ab und verlangen erneut den neuesten XSRF-Token, um sicherzustellen, dass er gültig ist. Erst wenn wir den neuesten Token erhalten haben, senden wir das Formular erneut.

Für unsere Backend-Implementierung haben wir ein XsrfValidationFilterAttribute Action-Filter-Attribut erstellt, das auf der Basisebene aller unserer Websites registriert ist. Dies läuft bevor jeder Endpoint ausgeführt wird und bestätigt, dass der XSRF-Token für alle daten-mutierenden Endpoints vorhanden ist (abhängig davon, ob die HTTP-Methode POST, PUT, PATCH oder DELETE ist).

Gewonnene Erkenntnisse

Vor dem Implementieren von neuen Sicherheitsfunktionen sollten Auswirkungen gemessen werden.

Das Hinzufügen von Endpoint-spezifischen Metriken, um zu sehen, welche URLs mit XSRF Probleme aufwiesen, war unverzichtbar bei der Problemlösung. Wir benutzen denselben Ansatz vor der Aktivierung des Content-Security-Programms.

Vorsicht beim Indexieren der JavaScript-Eigenschaften!

Wenn ein HTML-Element ein Input mit einem Namensattribut hat, nimmt dies Vorrang über Eigenschaften mit demselben Namen.

<form action=”endpoint-goes-here”>

<input name=”action” value=””>

</form>

Eine der Seiten auf unserer Website hatte ein Formular mit dem Namen ‚action‘, also las unser XSRF-Code mit dem Namen form.action irrtümlicherweise den Input-Wert anstelle der Formulareigenschaft!

Zum Glück entdeckten dies unsere Qualitätstester frühzeitig und die Änderung zu form.getAttribute(“action”) löste das Problem.


Weder die Roblox Corporation noch dieser Blog unterstützt jegliche anderen Unternehmen oder Dienste. Wir machen auch keine Garantien oder Versprechen in Hinsicht auf die Genauigkeit, Zuverlässigkeit und Vollständigkeit der Informationen in diesem Blog.

Dieser Blog wurde ursprünglich auf dem Roblox Tech Blog veröffentlicht.