429 Stimmen

Warum ist es üblich, CSRF-Präventions-Token in Cookies zu platziert?

Ich versuche, das gesamte Problem mit CSRF zu verstehen und angemessene Möglichkeiten zu finden, es zu verhindern. (Ressourcen, die ich gelesen habe, verstehe und zustimme: OWASP CSRF Prevention Cheat Sheet, Fragen zu CSRF)

Wie ich es verstehe, wird die Verwundbarkeit bei CSRF durch die Annahme eingeführt, dass (aus der Sicht des Webservers) ein gültiges Sitzungscookie in einer eingehenden HTTP-Anforderung die Wünsche eines authentifizierten Benutzers widerspiegelt. Aber alle Cookies für die Ursprungsdomäne werden magisch vom Browser an die Anforderung angehängt, daher kann der Server wirklich nur daraus schließen, dass die Anforderung von einem Browser stammt, der eine authentifizierte Sitzung hat; er kann nicht weiterhin annehmen, dass der Code, der in diesem Browser ausgeführt wird, oder ob er wirklich Benutzerwünsche widerspiegelt. Der Weg, dies zu verhindern, besteht darin, zusätzliche Authentifizierungsinformationen (das "CSRF-Token") in der Anforderung zu enthalten, die auf andere Weise als die automatische Cookieverwaltung des Browsers übertragen werden. Grob gesagt authentifiziert das Sitzungscookie also den Benutzer/Browser und das CSRF-Token authentifiziert den im Browser ausgeführten Code.

Also, wenn Sie ein Sitzungscookie verwenden, um Benutzer Ihrer Webanwendung zu authentifizieren, sollten Sie auch jedem Antwort eine CSRF-Token hinzufügen und bei jeder (mutationalen) Anforderung ein entsprechendes CSRF-Token verlangen. Das CSRF-Token legt dann eine Rundreise vom Server über den Browser zurück zum Server zurück, um dem Server zu beweisen, dass die Seite, die die Anforderung stellt, vom Server genehmigt wird (sogar von diesem erstellt wird).

Kommen wir zu meiner Frage, die sich um die spezifische Transportmethode für dieses CSRF-Token bei dieser Rundreise dreht.

Es scheint üblich zu sein (z. B. in AngularJS, Django, Rails), das CSRF-Token vom Server an den Client als Cookie zu senden (dh in einem Set-Cookie-Header) und dann Javascript im Client es aus dem Cookie herauszuziehen und als separaten XSRF-TOKEN-Header an den Server zurückzuschicken.

(Eine alternative Methode ist diejenige, die z. B. von Express empfohlen wird, bei der das vom Server generierte CSRF-Token in den Antworttext über Server-seitige Vorlagenexpansion aufgenommen wird, direkt an den Code/Markup angehängt wird, der es zurück an den Server senden wird, z. B. als verstecktes Formularfeld. Dieses Beispiel ist eher eine web 1.0-artige Methode, aber würde sich gut auf ein JS-lastiges Client übertragen lassen.)

Warum ist es so üblich, Set-Cookie als den nachgelagerten Transport für das CSRF-Token zu verwenden / warum ist dies eine gute Idee? Ich stelle mir vor, dass die Autoren all dieser Frameworks ihre Optionen sorgfältig abgewogen haben und das nicht falsch gemacht haben. Aber auf den ersten Blick scheint es unsinnig zu sein, Cookies zu verwenden, um eine Designbeschränkung bei Cookies zu umgehen. Tatsächlich würden Sie, wenn Sie Cookies als Transport bei der Rundreise verwenden würden (Set-Cookie: Header nachgelagert, damit der Server dem Browser das CSRF-Token mitteilt, und Cookie: Header aufsteigend, damit der Browser es dem Server zurücksendet), die Verwundbarkeit, die Sie zu beheben versuchen, wieder einführen.

Ich sehe ein, dass die oben genannten Frameworks keine Cookies für die gesamte Rundreise des CSRF-Tokens verwenden; sie verwenden Set-Cookie nachgelagert, dann etwas anderes (z. B. einen X-CSRF-Token-Header) aufsteigend, und das schließt die Verwundbarkeit aus. Aber selbst das Verwenden von Set-Cookie als nachgelagerten Transport ist potenziell irreführend und gefährlich; der Browser wird das CSRF-Token nun an jede Anforderung anhängen, einschließlich echter bösartiger XSRF-Anforderungen; im besten Fall macht das die Anforderung größer als nötig und im schlimmsten Fall könnte ein gut gemeines, aber fehlgeleitetes Stück Servercode tatsächlich versuchen, es zu verwenden, was wirklich schlecht wäre. Und weiterhin, da der eigentliche beabsichtigte Empfänger des CSRF-Tokens JavaScript auf der Clientseite ist, kann dieses Cookie nicht mit http-only geschützt werden. Das Senden des CSRF-Tokens nachgelagert in einem Set-Cookie-Header scheint also für mich ziemlich suboptimal zu sein.

115voto

Tongfa Punkte 1876

Die Verwendung eines Cookies zur Bereitstellung des CSRF-Tokens für den Client verhindert einen erfolgreichen Angriff, da der Angreifer den Wert des Cookies nicht lesen kann und es daher nicht dort platzieren kann, wo die serverseitige CSRF-Validierung es erfordert.

Der Angreifer wird in der Lage sein, eine Anfrage an den Server mit sowohl dem Authentifizierungs-Token-Cookie als auch dem CSRF-Cookie in den Anfrageheadern zu senden. Aber der Server sucht nicht nach dem CSRF-Token als Cookie in den Anfrageheadern, sondern im Payload der Anfrage. Und selbst wenn der Angreifer wüsste, wo er das CSRF-Token im Payload platzieren müsste, müsste er seinen Wert lesen, um es dort zu platzieren. Aber die Cross-Origin-Richtlinie des Browsers verhindert das Lesen jedes Cookie-Werts von der Zielwebsite.

Dieselbe Logik gilt nicht für das Authentifizierungs-Token-Cookie, da der Server es in den Anfrageheadern erwartet und der Angreifer nichts Besonderes tun muss, um es dort zu platzieren.

12voto

metamatt Punkte 13029

Meine beste Vermutung zur Antwort: Betrachten Sie diese 3 Optionen, um das CSRF-Token vom Server auf den Browser zu bringen.

  1. Im Anforderungskörper (nicht im HTTP-Header).
  2. In einem benutzerdefinierten HTTP-Header, nicht Set-Cookie.
  3. Als Cookie in einem Set-Cookie-Header.

Ich denke, die erste Option, der Anforderungskörper (wie im Express-Tutorial, das ich in der Frage verlinkt habe), ist nicht so gut geeignet für eine Vielzahl von Situationen; nicht jeder generiert jede HTTP-Antwort dynamisch; wo Sie das Token in der generierten Antwort platzieren müssen, kann stark variieren (in einem versteckten Formulareingabefeld; in einem JS-Codefragment oder einer von anderem JS-Code zugänglichen Variablen; vielleicht sogar in einer URL, obwohl dies im Allgemeinen ein schlechter Ort für CSRF-Token ist). Obwohl mit Anpassungen machbar, ist #1 also ein schwieriger Ort für einen Einheitsansatz.

Der zweite, benutzerdefinierte Header, ist attraktiv, funktioniert aber tatsächlich nicht, weil JS die Header für einen von ihm aufgerufenen XHR-Aufruf abrufen kann, aber nicht die Header für die geladene Seite.

Das lässt uns mit der dritten Option, einem Cookie, das in einem Set-Cookie-Header übertragen wird, als Ansatz zurück, der in allen Situationen einfach zu verwenden ist (jeder Server kann pro Anforderung Cookie-Header setzen, und es spielt keine Rolle, welche Art von Daten sich im Anforderungskörper befindet). Trotz seiner Nachteile war dies daher die einfachste Methode für Frameworks, umfassend umzusetzen.

2voto

Stephan van Hoof Punkte 203

Abgesehen vom Session-Cookie (das ist so etwas wie Standard) möchte ich keine zusätzlichen Cookies verwenden.

Ich habe eine Lösung gefunden, die für mich funktioniert, wenn ich eine Single-Page-Webanwendung (SPA) mit vielen AJAX-Anfragen baue. Hinweis: Ich verwende serverseitig Java und clientseitig JQuery, aber keine Magie, deshalb denke ich, dass dieses Prinzip in allen gängigen Programmiersprachen implementiert werden kann.

Meine Lösung ohne zusätzliche Cookies ist einfach:

Clientseite

Speichern Sie den CSRF-Token, der vom Server nach einem erfolgreichen Login zurückgegeben wird, in einer globalen Variablen (wenn Sie Web Storage anstelle einer globalen verwenden wollen, ist das natürlich auch in Ordnung). Weisen Sie JQuery an, bei jedem AJAX-Aufruf einen X-CSRF-TOKEN-Header zu übergeben.

Die Hauptseite "index" enthält diesen JavaScript-Schnipsel:

// Initialisiere die globale Variable CSRF_TOKEN mit einem leeren String.
// Diese Variable wird nach einem erfolgreichen Login gesetzt
window.CSRF_TOKEN = '';

// Der bereitgestellte Rückruf an .ajaxSend() wird aufgerufen, bevor eine Ajax-Anfrage gesendet wird
$( document ).ajaxSend( function( event, jqXHR ) {
    jqXHR.setRequestHeader('X-CSRF-TOKEN', window.CSRF_TOKEN);
}); 

Serverseite

Nach erfolgreichem Login erstellen Sie einen zufälligen (und ausreichend langen) CSRF-Token, speichern diesen in der serverseitigen Sitzung und geben ihn an den Client zurück. Filtern Sie bestimmte (empfindliche) eingehende Anfragen, indem Sie den Wert des X-CSRF-TOKEN-Headers mit dem in der Sitzung gespeicherten Wert vergleichen: diese sollten übereinstimmen.

Empfindliche AJAX-Aufrufe (POST-Formulardaten und GET-JSON-Daten) und der serverseitige Filter, der sie abfängt, befinden sich unter einem Pfad /dataservice/*. Login-Anfragen dürfen den Filter nicht erreichen, daher befinden sie sich auf einem anderen Pfad. Anfragen für HTML, CSS, JS und Bildressourcen befinden sich ebenfalls nicht unter dem Pfad /dataservice/* und werden daher nicht gefiltert. Diese enthalten nichts Geheimes und können keinen Schaden anrichten, daher ist das in Ordnung.

@WebFilter(urlPatterns = {"/dataservice/*"})
...
String sessionCSRFToken = req.getSession().getAttribute("CSRFToken") != null ? (String) req.getSession().getAttribute("CSRFToken") : null;
if (sessionCSRFToken == null || req.getHeader("X-CSRF-TOKEN") == null || !req.getHeader("X-CSRF-TOKEN").equals(sessionCSRFToken)) {
    resp.sendError(401);
} else
    chain.doFilter(request, response);
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X