Dlaczego często umieszcza się w plikach cookie tokeny zapobiegające CSRF?

284

Próbuję zrozumieć cały problem z CSRF i odpowiednie sposoby, aby temu zapobiec. (Zasoby, które przeczytałem, zrozumiałem i zgadzam się z: Arkuszem zapobiegania OWASP CSRF , Pytania dotyczące CSRF .)

Jak rozumiem, luka wokół CSRF wynika z założenia, że ​​(z punktu widzenia serwera) prawidłowy plik cookie sesji w przychodzącym żądaniu HTTP odzwierciedla życzenia uwierzytelnionego użytkownika. Ale wszystkie pliki cookie dla domeny pochodzenia są magicznie dołączone do żądania przez przeglądarkę, więc tak naprawdę cały serwer może wnioskować z obecności prawidłowego pliku cookie sesji w żądaniu, że żądanie pochodzi z przeglądarki, która ma uwierzytelnioną sesję; nie może dalej zakładać niczego o kodziedziałający w tej przeglądarce lub czy rzeczywiście odzwierciedla życzenia użytkowników. Aby temu zapobiec, należy dołączyć do żądania dodatkowe informacje uwierzytelniające („token CSRF”), przenoszone w inny sposób niż automatyczna obsługa plików cookie w przeglądarce. Mówiąc luźniej, plik cookie sesji uwierzytelnia użytkownika / przeglądarkę, a token CSRF uwierzytelnia kod działający w przeglądarce.

Krótko mówiąc, jeśli używasz sesyjnego pliku cookie do uwierzytelniania użytkowników swojej aplikacji internetowej, powinieneś również dodać token CSRF do każdej odpowiedzi i wymagać pasującego tokenu CSRF w każdym (mutującym) żądaniu. Token CSRF wykonuje następnie objazd z serwera do przeglądarki z powrotem na serwer, udowadniając serwerowi, że strona wysyłająca żądanie jest zatwierdzona (wygenerowana przez, nawet) ten serwer.

Na moje pytanie, które dotyczy konkretnej metody transportu użytej dla tego tokenu CSRF w tej rundzie.

Wydaje się powszechne (np. W AngularJS , Django , Rails ) wysyłanie tokena CSRF z serwera do klienta jako pliku cookie (tj. W nagłówku Set-Cookie), a następnie JavaScript w kliencie zeskrobuje go z pliku cookie i dołącza jako osobny nagłówek XSRF-TOKEN, aby wysłać go z powrotem na serwer.

(Alternatywną metodą jest metoda zalecana np. Przez Express , gdzie token CSRF wygenerowany przez serwer jest zawarty w treści odpowiedzi poprzez rozwinięcie szablonu po stronie serwera, dołączony bezpośrednio do kodu / znaczników, które dostarczą go z powrotem do serwera, np. jako ukryte dane wejściowe. Ten przykład jest bardziej internetowym sposobem robienia rzeczy, ale uogólniałby się na klienta bardziej obciążonego JS).

Dlaczego tak często używa się Set-Cookie jako dalszego transportu tokena CSRF / dlaczego to dobry pomysł? Wyobrażam sobie, że autorzy wszystkich tych frameworków uważnie rozważali swoje opcje i nie zrozumieli tego źle. Ale na pierwszy rzut oka używanie plików cookie do obejścia tego, co jest zasadniczo ograniczeniem projektowym plików cookie, wydaje się głupie. W rzeczywistości, jeśli użyłeś plików cookie jako transportu w obie strony (Set-Cookie: nagłówek w dół dla serwera, aby poinformować przeglądarkę o tokenie CSRF, a Cookie: nagłówek w górę, aby przeglądarka zwróciła go na serwer), ponownie wprowadziłbyś lukę próbują to naprawić.

Zdaję sobie sprawę z tego, że powyższe ramy nie używają plików cookie przez całą podróż do tokena CSRF; wykorzystują Set-Cookie w dół, a następnie coś innego (np. nagłówek Token X-CSRF) w górę, a to eliminuje lukę. Ale nawet stosowanie Set-Cookie jako dalszego transportu jest potencjalnie mylące i niebezpieczne; przeglądarka będzie teraz dołączać token CSRF do każdego żądania, w tym autentyczne złośliwe żądania XSRF; co najwyżej sprawia, że ​​żądanie jest większe, niż powinno być, a w najgorszym przypadku jakiś sensowny, ale źle wprowadzony fragment kodu serwera może faktycznie spróbować go użyć, co byłoby naprawdę złe. Ponadto, ponieważ faktycznym zamierzonym odbiorcą tokenu CSRF jest JavaScript po stronie klienta, oznacza to, że ten plik cookie nie może być chroniony tylko za pomocą protokołu HTTP.

metamatt
źródło
Świetne pytanie trafia we właściwe miejsce.
kta

Odpowiedzi:

263

Dobrym powodem, który poruszyłeś, jest to, że po otrzymaniu pliku cookie CSRF jest on następnie dostępny do użycia w całej aplikacji w skrypcie klienta do użycia zarówno w zwykłych formularzach, jak i AJAX POST. Będzie to miało sens w aplikacji obsługującej JavaScript, takiej jak ta używana przez AngularJS (użycie AngularJS nie wymaga, aby aplikacja była aplikacją jednostronicową, więc byłoby użyteczne tam, gdzie stan musi przepływać między różnymi żądaniami stron, gdzie wartość CSRF normalnie nie może pozostać w przeglądarce).

Rozważ następujące scenariusze i procesy w typowej aplikacji dla niektórych zalet i wad każdego opisanego podejścia. Są one oparte na sygnaturze tokenu synchronizatora .

Poproś o podejście do ciała

  1. Użytkownik pomyślnie się loguje.
  2. Serwer wydaje plik cookie uwierzytelniania.
  3. Użytkownik klika, aby przejść do formularza.
  4. Jeśli jeszcze nie został wygenerowany dla tej sesji, serwer generuje token CSRF, przechowuje go w sesji użytkownika i wysyła do ukrytego pola.
  5. Użytkownik przesyła formularz.
  6. Serwer sprawdza, czy ukryte pole pasuje do tokena przechowywanego w sesji.

Zalety:

  • Prosty do wdrożenia.
  • Współpracuje z AJAX.
  • Działa z formularzami.
  • Cookie może być w rzeczywistości tylko HTTP .

Niedogodności:

  • Wszystkie formularze muszą wyświetlać ukryte pole w HTML.
  • Wszelkie POST AJAX muszą również zawierać wartość.
  • Strona musi z góry wiedzieć, że wymaga tokena CSRF, aby mogła uwzględnić go w treści strony, dlatego wszystkie strony muszą zawierać gdzieś wartość tokena, co może sprawić, że wdrożenie go w dużej witrynie będzie czasochłonne.

Niestandardowy nagłówek HTTP (pobieranie danych)

  1. Użytkownik pomyślnie się loguje.
  2. Serwer wydaje plik cookie uwierzytelniania.
  3. Użytkownik klika, aby przejść do formularza.
  4. Strona ładuje się w przeglądarce, następnie wysyłane jest żądanie AJAX w celu pobrania tokena CSRF.
  5. Serwer generuje token CSRF (jeśli nie został jeszcze wygenerowany dla sesji), przechowuje go w sesji użytkownika i wysyła do nagłówka.
  6. Użytkownik przesyła formularz (token jest wysyłany za pośrednictwem ukrytego pola).
  7. Serwer sprawdza, czy ukryte pole pasuje do tokena przechowywanego w sesji.

Zalety:

Niedogodności:

  • Nie działa bez żądania AJAX, aby uzyskać wartość nagłówka.
  • Wszystkie formularze muszą mieć wartość dodaną do swojego HTML dynamicznie.
  • Wszelkie POST AJAX muszą również zawierać wartość.
  • Strona musi najpierw złożyć żądanie AJAX, aby uzyskać token CSRF, więc będzie to oznaczało dodatkową podróż w obie strony.
  • Równie dobrze może po prostu wypisać token na stronę, która zapisałaby dodatkowe żądanie.

Niestandardowy nagłówek HTTP (upstream)

  1. Użytkownik pomyślnie się loguje.
  2. Serwer wydaje plik cookie uwierzytelniania.
  3. Użytkownik klika, aby przejść do formularza.
  4. Jeśli jeszcze nie został wygenerowany dla tej sesji, serwer generuje token CSRF, przechowuje go w sesji użytkownika i wysyła gdzieś w treści strony.
  5. Użytkownik przesyła formularz przez AJAX (token jest wysyłany przez nagłówek).
  6. Serwer sprawdza niestandardowy nagłówek pasujący do tokena przechowywanego w sesji.

Zalety:

Niedogodności:

  • Nie działa z formularzami.
  • Wszystkie POST AJAX muszą zawierać nagłówek.

Niestandardowy nagłówek HTTP (upstream i downstream)

  1. Użytkownik pomyślnie się loguje.
  2. Serwer wydaje plik cookie uwierzytelniania.
  3. Użytkownik klika, aby przejść do formularza.
  4. Strona ładuje się w przeglądarce, następnie wysyłane jest żądanie AJAX w celu pobrania tokena CSRF.
  5. Serwer generuje token CSRF (jeśli nie został jeszcze wygenerowany dla sesji), przechowuje go w sesji użytkownika i wysyła do nagłówka.
  6. Użytkownik przesyła formularz przez AJAX (token jest wysyłany przez nagłówek).
  7. Serwer sprawdza niestandardowy nagłówek pasujący do tokena przechowywanego w sesji.

Zalety:

Niedogodności:

  • Nie działa z formularzami.
  • Wszystkie POST AJAX muszą również zawierać wartość.
  • Strona musi najpierw złożyć żądanie AJAX, aby uzyskać token CRSF, więc będzie to oznaczało dodatkową podróż w obie strony.

Zestaw ciasteczek

  1. Użytkownik pomyślnie się loguje.
  2. Serwer wydaje plik cookie uwierzytelniania.
  3. Użytkownik klika, aby przejść do formularza.
  4. Serwer generuje token CSRF, przechowuje go w sesji użytkownika i wysyła do pliku cookie.
  5. Użytkownik przesyła formularz za pośrednictwem AJAX lub formularza HTML.
  6. Serwer sprawdza, czy nagłówek niestandardowy (lub ukryte pole formularza) pasuje do tokena przechowywanego w sesji.
  7. Pliki cookie są dostępne w przeglądarce do użycia w dodatkowych AJAX i formularzach żądań bez dodatkowych żądań do serwera w celu pobrania tokena CSRF.

Zalety:

  • Prosty do wdrożenia.
  • Współpracuje z AJAX.
  • Działa z formularzami.
  • Niekoniecznie wymaga żądania AJAX, aby uzyskać wartość pliku cookie. Każde żądanie HTTP może je odzyskać i może być dołączone do wszystkich formularzy / żądań AJAX przez JavaScript.
  • Po pobraniu tokena CSRF, ponieważ jest on przechowywany w pliku cookie, wartość można ponownie wykorzystać bez dodatkowych żądań.

Niedogodności:

  • Wszystkie formularze muszą mieć wartość dodaną do swojego HTML dynamicznie.
  • Wszelkie POST AJAX muszą również zawierać wartość.
  • Plik cookie zostanie przesłany dla każdego żądania (tj. Wszystkich GET dla obrazów, CSS, JS itp., Które nie są zaangażowane w proces CSRF), zwiększając rozmiar żądania.
  • Plik cookie nie może być wyłącznie HTTP .

Dlatego podejście do plików cookie jest dość dynamiczne, oferując łatwy sposób na uzyskanie wartości pliku cookie (dowolnego żądania HTTP) i jego użycie (JS może automatycznie dodać wartość do dowolnego formularza i może być stosowany w żądaniach AJAX jako nagłówek lub jako wartość formularza). Po otrzymaniu tokenu CSRF dla sesji nie ma potrzeby jego ponownego generowania, ponieważ osoba atakująca wykorzystująca exploit CSRF nie ma metody odzyskania tego tokena. Jeśli złośliwy użytkownik spróbuje odczytać token CSRF użytkownika za pomocą dowolnej z powyższych metod, zapobiegną temu zasady dotyczące tego samego pochodzenia . Jeśli złośliwy użytkownik próbuje odzyskać stronę tokena CSRF po stronie serwera (np. Za pośrednictwemcurl), to token nie zostanie powiązany z tym samym kontem użytkownika, ponieważ w żądaniu brak będzie pliku cookie sesji uwierzytelnienia ofiary (byłby to atakujący - dlatego nie będzie powiązany ze stroną serwera z sesją ofiary).

Oprócz wzorca tokena synchronizującego istnieje również plik cookie podwójnego przesyłaniaMetoda zapobiegania CSRF, która oczywiście wykorzystuje pliki cookie do przechowywania pewnego rodzaju tokenu CSRF. Jest to łatwiejsze do wdrożenia, ponieważ nie wymaga żadnego stanu po stronie serwera dla tokena CSRF. Token CSRF może w rzeczywistości być standardowym plikiem cookie uwierzytelniania podczas korzystania z tej metody, a ta wartość jest przesyłana za pośrednictwem plików cookie jak zwykle wraz z żądaniem, ale wartość jest również powtarzana w ukrytym polu lub nagłówku, którego atakujący nie może replikować jako przede wszystkim nie mogą odczytać wartości. Zaleca się jednak wybranie innego pliku cookie, innego niż plik cookie służący do uwierzytelnienia, aby można było zabezpieczyć plik cookie służący do uwierzytelniania, oznaczając go jako HttpOnly. Jest to kolejny częsty powód, dla którego warto zapobiegać CSRF przy użyciu metody opartej na plikach cookie.

SilverlightFox
źródło
7
Nie jestem pewien, czy rozumiem, w jaki sposób można bezpiecznie wykonać żądanie AJAX w celu pobrania tokena CSRF (krok 4 w obu sekcjach „niestandardowy nagłówek: przesyłanie dalej”); ponieważ jest to osobne żądanie, serwer nie wie, od kogo pochodzi; skąd wiadomo, że bezpiecznie jest ujawnić token CSRF? Wydaje mi się, że jeśli nie możesz pobrać tokena z początkowego ładowania strony, tracisz (co niestety powoduje, że niestandardowy nagłówek odpowiedzi poniżej jest niestabilny).
metamatt
6
Ponieważ fałszerz nie będzie miał pliku cookie sesji. Mogą mieć własny plik cookie sesji, ale ponieważ token CSRF jest powiązany z sesją, ich token CSRF nie będzie pasował do tokena ofiary.
SilverlightFox,
32
W moim rozumieniu ataku CSRF fałszerz ma mój plik cookie sesji. Cóż, tak naprawdę nie widzą ciasteczka, ale mają możliwość dostarczenia go w swoich sfałszowanych żądaniach, ponieważ żądania pochodzą z mojej przeglądarki, a moja przeglądarka dostarcza mój plik cookie sesji. Z punktu widzenia serwera sam plik cookie sesji nie może odróżnić uzasadnionego żądania od fałszywego żądania. W rzeczywistości jest to atak, któremu próbujemy zapobiec. BTW dziękuję za twoją cierpliwość w omawianiu tego tematu, szczególnie jeśli jestem zdezorientowany.
metamatt
8
Mają możliwość dostarczenia pliku cookie uwierzytelniania, ale nie mogą odczytać odpowiedzi zawierającej token CSRF.
SilverlightFox,
8
@metamatt Przepraszam za nekro, ale zrobię to dla ludzi, którzy błądzą. W moim rozumieniu atakujący zazwyczaj nie ma dostępu do odpowiedzi. CSRF służy przede wszystkim do wywoływania skutków ubocznych , a nie do bezpośredniego gromadzenia danych. Na przykład skrypt ataku CSRF może zmusić uprzywilejowanego użytkownika do eskalacji uprawnień atakującego, wyłączyć ustawienie zabezpieczeń lub zmusić zalogowanego użytkownika paypal do wysłania przelewu na określony adres e-mail. W żadnym z tych przypadków atakujący nie dba o odpowiedź, która jest nadal wysyłana do przeglądarki ofiary; tylko wynik ataku.
jonathanbruder
61

Użycie pliku cookie w celu dostarczenia tokena CSRF do klienta nie pozwala na udany atak, ponieważ osoba atakująca nie może odczytać wartości pliku cookie, a zatem nie może umieścić go tam, gdzie wymaga tego weryfikacja CSRF po stronie serwera.

Osoba atakująca będzie mogła wywołać żądanie do serwera, używając zarówno pliku cookie tokenu uwierzytelniania, jak i pliku cookie CSRF w nagłówkach żądania. Ale serwer nie szuka tokena CSRF jako pliku cookie w nagłówkach żądania, szuka w ładunku żądania. I nawet jeśli atakujący wie, gdzie umieścić token CSRF w ładunku, musiałby odczytać jego wartość, aby go tam umieścić. Ale zasady dotyczące różnych źródeł w przeglądarce uniemożliwiają odczytanie wartości plików cookie z docelowej witryny.

Ta sama logika nie dotyczy pliku cookie tokenu uwierzytelniania, ponieważ serwer oczekuje go w nagłówkach żądania, a atakujący nie musi robić nic specjalnego, aby go tam umieścić.

Tongfa
źródło
Z pewnością jednak atakujący nie musi czytać pliku cookie. Mogą po prostu wstawić obraz na zaatakowanej stronie, o src='bank.com/transfer?to=hacker&amount=1000którą poprosi przeglądarka, wraz z powiązanymi plikami cookie dla tej witryny ( bank.com)?
developius
2
CSRF służy do sprawdzania poprawności użytkownika po stronie klienta, a nie do ogólnej ochrony witryny przed zagrożeniami po stronie serwera, jak sugerujesz.
Tongfa
2
@developius wysłanie pliku cookie nie wystarcza do zapewnienia ochrony CSRF. Plik cookie zawiera token csrf przesłany przez serwer. Legalny klient musi odczytać token csrf z pliku cookie, a następnie przekazać go gdzieś w żądaniu, takim jak nagłówek lub ładunek. Ochrona CSRF sprawdza, czy wartość w pliku cookie odpowiada wartości w żądaniu, w przeciwnym razie żądanie zostanie odrzucone. Dlatego osoba atakująca musi odczytać plik cookie.
Will M.
1
Ta odpowiedź była bardzo istotna na pytanie pierwotnego plakatu i była bardzo jasna. +1 Dziękuję.
java-addict301
@Tongfa - dzięki, pomogło mi to lepiej zrozumieć. Czy mam rację, zakładając, że Token CSRF NIE powinien być umieszczony w nagłówku? to musi być gdzieś w ciele?
zerohedge
10

Moje najlepsze przypuszczenie, co do odpowiedzi: Rozważ te 3 opcje, w jaki sposób pobrać token CSRF z serwera do przeglądarki.

  1. W treści żądania (nie w nagłówku HTTP).
  2. W niestandardowym nagłówku HTTP, nie w Set-Cookie.
  3. Jako plik cookie w nagłówku Set-Cookie.

Wydaje mi się, że pierwszy, „ciało żądania” (chociaż zademonstrowany w tutorialu Express, do którego włączyłem pytanie ), nie jest tak przenośny w wielu różnych sytuacjach; nie każdy generuje każdą odpowiedź HTTP dynamicznie; gdzie w końcu musisz wstawić token do generowanej odpowiedzi, może się znacznie różnić (w postaci ukrytej danych wejściowych; fragmentu kodu JS lub zmiennej dostępnej za pomocą innego kodu JS; może nawet w adresie URL, choć wydaje się to ogólnie złe miejsce umieścić tokeny CSRF). Tak więc, choć wykonalne z pewnymi dostosowaniami, # 1 jest trudnym miejscem do przyjęcia uniwersalnego podejścia.

Drugi, niestandardowy nagłówek, jest atrakcyjny, ale w rzeczywistości nie działa, ponieważ chociaż JS może pobrać nagłówki dla XHR, które wywołał, nie może pobrać nagłówków dla strony, z której został załadowany .

To pozostawia trzeci, plik cookie przenoszony przez nagłówek Set-Cookie, jako podejście, które jest łatwe w użyciu we wszystkich sytuacjach (każdy serwer będzie mógł ustawić nagłówki plików cookie na żądanie i nie ma znaczenia, jakiego rodzaju dane znajdują się w treści żądania). Mimo swoich wad był to najłatwiejszy sposób na szerokie wdrożenie ram.

metamatt
źródło
7
Być może stwierdzam, że to oczywiste, to znaczy, że plik cookie nie może być httponly poprawny?
Photon
1
tylko dla żądań ajax (gdzie JS musi znać wartość pliku cookie csrf, aby wysłać go ponownie przy następnym żądaniu w drugim kanale (jako dane formularza lub nagłówek)). Nie ma powodu, aby wymagać, aby token csrf był HttpOnly, jeśli plik cookie sesji jest już HttpOnly (w celu ochrony przed XSS), ponieważ token csrf sam w sobie nie jest wartościowy bez skojarzonej sesji.
cowbert
2

Poza sesyjnym plikiem cookie (który jest rodzajem standardu), nie chcę używać dodatkowych plików cookie.

Znalazłem rozwiązanie, które działa dla mnie przy tworzeniu aplikacji jednostronicowej (SPA) z wieloma żądaniami AJAX. Uwaga: używam JQuery po stronie serwera i JQuery po stronie klienta, ale nie ma magicznych rzeczy, więc myślę, że tę zasadę można wdrożyć we wszystkich popularnych językach programowania.

Moje rozwiązanie bez dodatkowych plików cookie jest proste:

Strona klienta

Przechowuj token CSRF, który jest zwracany przez serwer po udanym logowaniu w zmiennej globalnej (jeśli chcesz użyć magazynu internetowego zamiast globalnego, to w porządku). Poinstruuj JQuery, aby w każdym wywołaniu AJAX podawał nagłówek X-CSRF-TOKEN.

Główna strona „indeksu” zawiera ten fragment kodu JavaScript:

// 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);
}); 

Po stronie serwera

Przy kolejnym logowaniu utwórz losowy (i wystarczająco długi) token CSRF, zapisz go w sesji po stronie serwera i zwróć klientowi. Filtruj określone (wrażliwe) przychodzące żądania, porównując wartość nagłówka X-CSRF-TOKEN z wartością zapisaną w sesji: powinny się zgadzać.

Wrażliwe wywołania AJAX (dane formularza POST i GET JSON-data) oraz przechwytywanie ich przez filtr po stronie serwera znajdują się w ścieżce / dataservice / *. Żądania logowania nie mogą trafić w filtr, więc są na innej ścieżce. Żądania dotyczące HTML, CSS, JS i zasobów graficznych również nie znajdują się na ścieżce / dataservice / *, dlatego nie są filtrowane. Nie zawierają one żadnych tajemnic i nie mogą wyrządzić krzywdy, więc jest w porządku.

@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);
}   
Stephan van Hoof
źródło
Myślę, że chcesz CSRF na żądanie logowania. Wygląda na to, że używasz tokena CSRF również jako tokena sesji logowania. Działa również tak, aby mieć je jako osobne tokeny, a następnie można użyć CSRF na dowolnym punkcie końcowym, niezależnie od tego, czy użytkownik jest zalogowany, czy nie.
Tongfa