it-swarm.com.de

Warum ist es üblich, CSRF-Präventionsmarker in Cookies zu setzen?

Ich versuche, das gesamte Problem mit CSRF zu verstehen und geeignete Wege zu finden, um es zu verhindern. (Ressourcen, die ich gelesen habe, verstehe und stimme zu: OWASP CSRF Prevention-Informationsblatt , Fragen zu CSRF .)

Soweit ich es verstehe, wird die Sicherheitsanfälligkeit in Bezug auf CSRF durch die Annahme eingeführt, dass (aus Sicht des Webservers) ein gültiger Sitzungscookie in einer eingehenden HTTP-Anforderung die Wünsche eines authentifizierten Benutzers widerspiegelt. Alle Cookies für die Origin-Domäne werden jedoch vom Browser auf magische Weise an die Anfrage angehängt, sodass wirklich alle Server aus dem Vorhandensein eines gültigen Sitzungscookies in einer Anfrage schließen können, dass die Anfrage von einem Browser kommt, der eine authentifizierte Sitzung hat. Es kann nicht weiter davon ausgegangen werden, dass code in diesem Browser ausgeführt wird oder ob es wirklich die Wünsche der Benutzer widerspiegelt. Um dies zu verhindern, können Sie zusätzliche Authentifizierungsinformationen (das "CSRF-Token") in die Anforderung aufnehmen, die auf andere Weise als durch die automatische Cookie-Verarbeitung des Browsers übermittelt werden. Kurz gesagt, das Sitzungs-Cookie authentifiziert den Benutzer/Browser und das CSRF-Token authentifiziert den Code, der im Browser ausgeführt wird.

Wenn Sie also ein Session-Cookie zur Authentifizierung von Benutzern Ihrer Webanwendung verwenden, sollten Sie jeder Antwort ein CSRF-Token hinzufügen. In jeder (mutierenden) Anforderung ist ein entsprechendes CSRF-Token erforderlich. Das CSRF-Token macht dann einen Roundtrip von Server zu Browser zurück zum Server, um dem Server zu zeigen, dass die Seite, von der die Anforderung stammt, von diesem Server genehmigt wird (von diesem generiert wird).

Weiter zu meiner Frage, die sich auf die spezifische Transportmethode bezieht, die für dieses CSRF-Token auf dieser Rundreise verwendet wird.

Es scheint üblich (z. B. in AngularJS , Django , Rails ), das CSRF - Token vom Server an den Client als Cookie (dh in einem Set - Cookie - Header) zu senden, und anschließend Javascript zu verwenden Im Client kratzen Sie es aus dem Cookie und hängen es als separaten XSRF-TOKEN-Header an, um es an den Server zu senden.

(Eine alternative Methode ist die von z. B. Express empfohlene Methode, bei der das vom Server generierte CSRF - Token über eine serverseitige Vorlagenerweiterung in den Antworttext eingeschlossen wird und direkt an den Code/Markup angehängt wird, an den es zurückgegeben wird B. als verborgene Formulareingabe. Dieses Beispiel ist eine mehr Web 1.0-artige Vorgehensweise, würde aber gut auf einen JS-schweren Client verallgemeinern.)

Warum ist es so üblich, Set-Cookie als nachgelagerten Transport für das CSRF-Token zu verwenden/warum ist dies eine gute Idee? Ich kann mir vorstellen, dass die Autoren all dieser Frameworks ihre Optionen sorgfältig geprüft und das nicht falsch verstanden haben. Auf den ersten Blick scheint die Verwendung von Cookies zur Umgehung dessen, was im Wesentlichen eine Einschränkung beim Design von Cookies ist, dumm. Wenn Sie Cookies als Roundtrip-Transport verwenden (Set-Cookie: Header Downstream für den Server, um dem Browser das CSRF-Token mitzuteilen, und Cookie: Header Upstream, damit der Browser den Server an den Server zurücksenden kann), würden Sie die Sicherheitsanfälligkeit wieder herstellen versuchen zu beheben.

Mir ist klar, dass die oben genannten Frameworks keine Cookies für den gesamten Roundtrip für das CSRF-Token verwenden. Sie verwenden Set-Cookie Downstream, dann etwas anderes (z. B. einen X-CSRF-Token-Header), und dies schließt die Sicherheitsanfälligkeit aus. Aber auch die Verwendung von Set-Cookie als nachgelagerten Transport ist potenziell irreführend und gefährlich. Der Browser hängt nun das CSRF-Token an jede Anforderung an, einschließlich böswilliger XSRF-Anforderungen. Im besten Fall wird die Anforderung dadurch größer, als sie sein muss, und im schlimmsten Fall könnte ein gut gemeinter, aber fehlgeleiteter Servercode tatsächlich versuchen, sie zu verwenden, was wirklich schlecht wäre. Da der eigentliche vorgesehene Empfänger des CSRF-Token clientseitiges Javascript ist, bedeutet dies, dass dieses Cookie nicht mit nur http geschützt werden kann. Das Senden des CSRF-Tokens in einem Set-Cookie-Header stromabwärts erscheint mir daher eher suboptimal.

206
metamatt

Ein guter Grund, auf den Sie angesprochen haben, ist, dass das CSRF-Cookie nach Erhalt für die gesamte Anwendung im Clientskript zur Verwendung in regulären Formularen und in AJAX POSTs verfügbar ist. Dies ist in einer JavaScript-starken Anwendung wie der von AngularJS sinnvoll (für die Verwendung von AngularJS ist es nicht erforderlich, dass die Anwendung eine Anwendung mit einer einzelnen Seite ist. Daher ist es hilfreich, wenn der Status zwischen verschiedenen Seitenanforderungen mit dem CSRF-Wert wechseln muss kann normalerweise nicht im Browser verbleiben).

Berücksichtigen Sie die folgenden Szenarien und Prozesse in einer typischen Anwendung für einige Vor- und Nachteile der von Ihnen beschriebenen Ansätze. Diese basieren auf dem Synchronizer Token Pattern .

Körperansatz anfordern

  1. Der Benutzer meldet sich erfolgreich an.
  2. Server gibt Auth-Cookie aus.
  3. Der Benutzer klickt, um zu einem Formular zu navigieren.
  4. Wenn dies für diese Sitzung noch nicht generiert wurde, generiert der Server ein CSRF-Token, speichert es für die Benutzersitzung und gibt es in einem ausgeblendeten Feld aus.
  5. Der Benutzer sendet das Formular.
  6. Der Server überprüft, ob das ausgeblendete Feld dem gespeicherten Sitzungs-Token entspricht.

Vorteile:

  • Einfach zu implementieren.
  • Arbeitet mit AJAX.
  • Arbeitet mit Formularen.
  • Cookie kann tatsächlich Nur HTTP sein.

Nachteile:

  • Alle Formulare müssen das ausgeblendete Feld in HTML ausgeben.
  • Alle AJAX POSTs müssen auch den Wert enthalten.
  • Die Seite muss im Voraus wissen, dass das CSRF-Token erforderlich ist, damit es in den Seiteninhalt aufgenommen werden kann. Daher müssen alle Seiten irgendwo den Token-Wert enthalten, was die Implementierung für eine große Site zeitaufwändig machen kann.

Benutzerdefinierter HTTP-Header (Downstream)

  1. Der Benutzer meldet sich erfolgreich an.
  2. Server gibt Auth-Cookie aus.
  3. Der Benutzer klickt, um zu einem Formular zu navigieren.
  4. Die Seite wird im Browser geladen. Anschließend wird eine AJAX -Anforderung zum Abrufen des CSRF-Tokens gesendet.
  5. Der Server generiert ein CSRF-Token (sofern es nicht bereits für eine Sitzung generiert wurde), speichert es für die Benutzersitzung und gibt es in einem Header aus.
  6. Der Benutzer sendet das Formular (das Token wird über ein verstecktes Feld gesendet).
  7. Der Server überprüft, ob das ausgeblendete Feld dem gespeicherten Sitzungs-Token entspricht.

Vorteile:

  • Arbeitet mit AJAX.
  • Cookie kann Nur HTTP sein.

Nachteile:

  • Funktioniert nicht ohne eine AJAX -Anforderung zum Abrufen des Header-Werts.
  • Alle Formulare müssen den Wert haben, der dynamisch zu ihrem HTML hinzugefügt wird.
  • Alle AJAX POSTs müssen auch den Wert enthalten.
  • Die Seite muss zuerst eine AJAX -Anforderung stellen, um das CSRF-Token zu erhalten. Dies bedeutet also jedes Mal einen zusätzlichen Roundtrip.
  • Könnte auch einfach das Token auf der Seite ausgegeben haben, die die zusätzliche Anforderung speichern würde.

Benutzerdefinierter HTTP-Header (Upstream)

  1. Der Benutzer meldet sich erfolgreich an.
  2. Server gibt Auth-Cookie aus.
  3. Der Benutzer klickt, um zu einem Formular zu navigieren.
  4. Wenn dies für diese Sitzung noch nicht generiert wurde, generiert der Server ein CSRF-Token, speichert es für die Benutzersitzung und gibt es irgendwo im Seiteninhalt aus.
  5. Der Benutzer sendet das Formular über AJAX (Token wird über den Header gesendet).
  6. Der Server überprüft, ob der benutzerdefinierte Header mit dem gespeicherten Sitzungstoken übereinstimmt.

Vorteile:

  • Arbeitet mit AJAX.
  • Cookie kann Nur HTTP sein.

Nachteile:

  • Funktioniert nicht mit Formularen.
  • Alle AJAX POSTs müssen den Header enthalten.

Benutzerdefinierter HTTP-Header (Upstream & Downstream)

  1. Der Benutzer meldet sich erfolgreich an.
  2. Server gibt Auth-Cookie aus.
  3. Der Benutzer klickt, um zu einem Formular zu navigieren.
  4. Die Seite wird im Browser geladen. Anschließend wird eine AJAX -Anforderung zum Abrufen des CSRF-Tokens gesendet.
  5. Der Server generiert ein CSRF-Token (sofern es nicht bereits für eine Sitzung generiert wurde), speichert es für die Benutzersitzung und gibt es in einem Header aus.
  6. Der Benutzer sendet das Formular über AJAX (Token wird über den Header gesendet).
  7. Der Server überprüft, ob der benutzerdefinierte Header mit dem gespeicherten Sitzungstoken übereinstimmt.

Vorteile:

  • Arbeitet mit AJAX.
  • Cookie kann Nur HTTP sein.

Nachteile:

  • Funktioniert nicht mit Formularen.
  • Alle AJAX POSTs müssen auch den Wert enthalten.
  • Die Seite muss zuerst eine AJAX -Anforderung stellen, um das CRSF-Token zu erhalten. Dies bedeutet also jedes Mal einen zusätzlichen Roundtrip.

Set-Cookie

  1. Der Benutzer meldet sich erfolgreich an.
  2. Server gibt Auth-Cookie aus.
  3. Der Benutzer klickt, um zu einem Formular zu navigieren.
  4. Der Server generiert ein CSRF-Token, speichert es für die Benutzersitzung und gibt es in einem Cookie aus.
  5. Der Benutzer sendet das Formular über AJAX oder über ein HTML-Formular.
  6. Der Server überprüft, ob der benutzerdefinierte Header (oder das ausgeblendete Formularfeld) mit dem gespeicherten Sitzungstoken übereinstimmt.
  7. Cookie ist im Browser für die Verwendung in zusätzlichen AJAX und Formularanforderungen ohne zusätzliche Anforderungen an den Server zum Abrufen des CSRF-Tokens verfügbar.

Vorteile:

  • Einfach zu implementieren.
  • Arbeitet mit AJAX.
  • Arbeitet mit Formularen.
  • Erfordert nicht unbedingt eine AJAX -Anforderung, um den Cookie-Wert abzurufen. Jede HTTP-Anfrage kann sie abrufen und über JavaScript an alle Formulare/AJAX-Anfragen angehängt werden.
  • Sobald das CSRF-Token abgerufen wurde, da es in einem Cookie gespeichert ist, kann der Wert ohne zusätzliche Anforderungen wiederverwendet werden.

Nachteile:

  • Alle Formulare müssen den Wert haben, der dynamisch zu ihrem HTML hinzugefügt wird.
  • Alle AJAX POSTs müssen auch den Wert enthalten.
  • Das Cookie wird für jede Anforderung gesendet (d. H. Für alle GETs für Bilder, CSS, JS usw., die nicht am CSRF-Prozess beteiligt sind), wobei die Anforderungsgröße erhöht wird.
  • Cookie kann nicht Nur HTTP sein.

Daher ist der Cookie-Ansatz ziemlich dynamisch und bietet eine einfache Möglichkeit, den Cookie-Wert (jede HTTP-Anfrage) abzurufen und zu verwenden (JS kann den Wert automatisch zu jedem Formular hinzufügen und er kann auch in AJAX-Anfragen verwendet werden) als Überschrift oder als Formularwert). Sobald das CSRF-Token für die Sitzung empfangen wurde, muss es nicht erneut generiert werden, da ein Angreifer, der einen CSRF-Exploit verwendet, keine Methode zum Abrufen dieses Tokens hat. Wenn ein böswilliger Benutzer versucht, das CSRF-Token des Benutzers mit einer der oben genannten Methoden zu lesen, wird dies durch die Same Origin Policy verhindert. Wenn ein böswilliger Benutzer versucht, die CSRF-Tokenserverseite abzurufen (z. B. über curl), wird dieses Token nicht demselben Benutzerkonto zugeordnet, mit dem das Authentifizierungssitzungscookie des Opfers in der Anforderung fehlt (dies wäre das Angreifer - daher wird es der Sitzung des Opfers nicht serverseitig zugeordnet).

Neben dem Synchronizer Token Pattern gibt es auch die Double Submit Cookie CSRF Prevention-Methode, die natürlich Cookies verwendet, um eine Art CSRF-Token zu speichern. Dies ist einfacher zu implementieren, da für das CSRF-Token kein serverseitiger Status erforderlich ist. Tatsächlich könnte das CSRF-Token das Standardauthentifizierungscookie sein, wenn diese Methode verwendet wird, und dieser Wert wird wie bei der Anforderung üblich über Cookies übermittelt. Der Wert wird jedoch auch in einem ausgeblendeten Feld oder in einem Header wiederholt, unter dem ein Angreifer nicht replizieren kann Sie können den Wert überhaupt nicht lesen. Es wird jedoch empfohlen, ein anderes Cookie als das Authentifizierungscookie zu wählen, damit das Authentifizierungscookie durch Markieren von HttpOnly gesichert werden kann. Dies ist also ein weiterer häufiger Grund, warum Sie CSRF-Prävention mithilfe einer Cookie-basierten Methode finden.

223
SilverlightFox

Die Verwendung eines Cookies zum Bereitstellen des CSRF-Token für den Client lässt keinen erfolgreichen Angriff zu, da der Angreifer den Wert des Cookies nicht lesen kann und ihn daher nicht an die Stelle setzen kann, an der die serverseitige CSRF-Überprüfung dies erfordert.

Der Angreifer kann eine Anforderung an den Server senden, wobei sowohl das Authentifizierungs-Token-Cookie als auch das CSRF-Cookie in den Anforderungsheadern enthalten ist. Der Server sucht jedoch nicht nach dem CSRF-Token als Cookie in den Anforderungsheadern. Er sucht in der Nutzlast der Anforderung. Und selbst wenn der Angreifer weiß, wo er den CSRF-Token in der Nutzlast ablegen muss, muss er seinen Wert lesen, um ihn dort abzulegen. Die Cross-Origin-Richtlinie des Browsers verhindert jedoch das Lesen von Cookie-Werten von der Ziel-Website.

Dieselbe Logik gilt nicht für das Auth-Token-Cookie, da der Server dies in den Request-Headern erwartet und der Angreifer keine besonderen Vorkehrungen treffen muss, um ihn dort abzulegen.

32
Tongfa

Meine beste Vermutung zur Antwort: Betrachten Sie diese 3 Optionen, um das CSRF-Token vom Server zum Browser zu übertragen.

  1. Im Anforderungstext (kein HTTP-Header).
  2. In einem benutzerdefinierten HTTP-Header nicht Set-Cookie.
  3. Als Cookie in einem Set-Cookie-Header.

Ich denke, der erste Antrag, Anforderungshauptteil (obwohl von dem Express-Tutorial, das ich in der Frage verlinkt habe ), ist nicht so tragbar für eine Vielzahl von Situationen; Nicht jeder generiert jede HTTP-Antwort dynamisch. Wenn Sie das Token letztendlich in die generierte Antwort einfügen müssen, kann dies sehr unterschiedlich sein (in einer verborgenen Form, in einem Fragment von JS-Code oder in einer Variablen, auf die anderer JS-Code zugreifen kann) CSRF-Token setzen). Obwohl # 1 mit einigen Anpassungen durchführbar ist, ist # 1 ein schwieriger Ort, um eine Einheitslösung zu finden.

Der zweite, der benutzerdefinierte Header, ist attraktiv, funktioniert aber nicht, da JS kann zwar die Header für ein XHR aufrufen, das es aufgerufen hat, aber es kann keine Header für die geladene Seite erhalten .

Der dritte, ein Cookie, der von einem Set-Cookie-Header getragen wird, bleibt in allen Situationen einfach zu verwenden (jeder Server kann Cookie-Header pro Anfrage setzen, und es spielt keine Rolle, welche Art von Cookie es ist) Daten befinden sich im Anforderungshauptteil). Trotz seiner Nachteile war dies die einfachste Methode für Frameworks, die breit implementiert werden konnte.

8
metamatt

Abgesehen von dem Sitzungs-Cookie (der eine Art Standard ist), möchte ich keine zusätzlichen Cookies verwenden. 

Ich habe eine Lösung gefunden, die für mich beim Erstellen einer Single Page Web Application (SPA) mit vielen AJAX -Anfragen funktioniert. Hinweis: Ich verwende serverseitiges Java und clientseitiges JQuery, aber keine magischen Dinge. Ich denke, dieses Prinzip kann in allen gängigen Programmiersprachen implementiert werden.

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

Client-Seite

Speichern Sie das CSRF-Token, das nach einer erfolgreichen Anmeldung vom Server zurückgegeben wird, in einer globalen Variablen (wenn Sie Web-Speicher statt eines globalen Speichers verwenden möchten, der natürlich gut ist). Weisen Sie JQuery an, in jedem AJAX - Aufruf einen X-CSRF-TOKEN-Header anzugeben.

Die Hauptseite "Index" enthält dieses JavaScript-Snippet:

// Intialize global variable CSRF_TOKEN to empty sting. 
// This variable is set after a succesful login
window.CSRF_TOKEN = '';

// the supplied callback to .ajaxSend() is called before an Ajax request is sent
$( document ).ajaxSend( function( event, jqXHR ) {
  jqXHR.setRequestHeader('X-CSRF-TOKEN', window.CSRF_TOKEN);
});

Serverseite

Erstellen Sie bei erfolgreicher Anmeldung ein zufälliges (und lang genug) CSRF-Token, speichern Sie es in der serverseitigen Sitzung und geben Sie es an den Client zurück. Filtern Sie bestimmte (vertrauliche) eingehende Anforderungen, indem Sie den X-CSRF-TOKEN-Header-Wert 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/*. Anmeldeanforderungen dürfen den Filter nicht treffen, daher befinden sich diese auf einem anderen Pfad. Anforderungen für HTML-, CSS-, JS- und Bildressourcen befinden sich ebenfalls nicht im Pfad/dataservice/* und werden daher nicht gefiltert. Diese enthalten nichts Geheimnisvolles und können nichts schaden, daher ist dies 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);
}
0