Otrzymałem ostatnio zapytanie odnośnie przechowywania danych sesji po stronie klienta. Było ono związane z drugim z przygotowanych przeze mnie wyzwań. Jednym z istotnych elementów tego zadania była analiza danych przechowywanych po stronie klienta, co wyjaśniam w opisie implementacji sesyjności w przykładowej aplikacji. Pytanie - czy dane sesji można bezpiecznie przechowywać po stronie klienta?
Sesja po stronie użytkownika
Wprowadzenie
Tu trzeba dodać, że pytanie dotyczyło scenariusza, w którym przechowywanie danych sesji po stronie klienta można uznać za uzasadnione, choć jak zawsze ryzykowne (patrz: CWE-642: External Control of Critical State Data). Chodziło o rozproszone serwisy, w których poszczególne żądania klienta mogą trafić do różnych serwerów, być może rozproszonych geograficznie. W takim przypadku przechowanie danych sesji po stronie serwera mogłoby być kłopotliwe. Przy okazji warto zauważyć, że przy takiej architekturze serwer(y) będą miały po swojej stronie praktycznie zerową wiedzę o klientach. Wiedza o sesji będzie odtwarzana przed serwer przed obsługą żądania na podstawie otrzymanych od klienta danych i zapominana natychmiast po zakończeniu obsługi żądania.
Czy dane sesji mogą być bezpiecznie przechowywane po stronie klienta? To dobry moment by przypomnieć moje starsze wpisy dotyczące kilku zagadnień, które do odpowiedzi na to pytanie mogą być przydatne:
Dlaczego użyć właśnie podejścia STRIDE-per-element? Bo, moim zdaniem, jest to dość efektywne narzędzie do identyfikacji zabezpieczeń, które powinny być zaimplementowane. Określenia, przed jakimi zagrożeniami powinniśmy się chronić.
Diagram DFD
Spróbujmy narysować diagram DFD dotyczący przechowywania sesji po stronie klienta. Wydaje mi się, że diagram taki może wyglądać mniej więcej w sposób następujący:

Na diagramie tym mamy jeden proces, jeden data store i dwa data flow. Serwer (proces/aplikacja) zwraca do klienta stronę z osadzonymi w niej danymi sesji. Klient przesyła do serwera żądanie razem z wcześniej otrzymanymi od serwera danymi sesji. Dane sesji przechowywane są po stronie klienta w ukrytym polu formularza, pole to pełni rolę widocznego na wykresie data store. Dokładnie w ten sposób zaimplementowany jest mechanizm ViewState w ASP.NET czy JSF, w ten sposób zaimplementowany został również mechanizm sesji w przywoływanym już przeze mnie przykładzie.
Trzeba zauważyć, że data store znajduje się poza trust boundary patrząc z perspektywy procesu. Ze STRIDE natomiast wynika, że dla data store zagrożenia, którym powinniśmy przeciwdziałać, to:
- Tampering,
- Repudiation,
- Information Disclosure,
- Denial of Service,
Przy czym repudiation pojawiające się i znikające dla obiektu data store w różnych wersjach matrycy tym razem będzie wygodniej zignorować, natomiast denial of service z rozważań wykluczę. Pozostaje więc do rozważenia tampering oraz information disclosure.
Dla data flow należy uwzględnić tampering, information disclosure oraz denial of service. Temat data flow tu jednak pominę. Dane sesji są przekazywane między serwerem i klientem dokładnie w taki sam sposób, jak "normalne" dane (request, response) w typowej aplikacji internetowej. Tu ochrona w postaci zabezpieczenia komunikacji z wykorzystaniem protokołu SSL jest wystarczające. Dodatkowo oczywiście wszystkie dane, które przekraczają trust boundary, muszą być traktowane jako niezaufane i weryfikowane/walidowane przed użyciem. Tak jest również i w omawianym przypadku.
Tampering i Information Disclosure: co i jak
Jak w tym scenariuszu należy rozumieć tampering? Skoro dane znajdują się poza trust boundary w środowisku całkowicie kontrolowanym przez użytkownika (atakującego), narażone są na modyfikację. Proces (aplikacja) zapisując stan sesji powinien mieć pewność, że po odtworzeniu stan ten będzie identyczny z zapisanym. Trzeba więc zadbać o integralność danych. Integralność danych jest jednym z trzech elementów triady CIA (poufność, integralność, dostępność), mechanizmu służące zachowaniu integralności są znane, nie trzeba wymyślać nowych. W tym wypadku wystarczy wykorzystanie mechanizmu HMAC. HMAC, ponieważ chodzi nie tylko o integralność danych (wówczas teoretycznie mogłaby wystarczyć "zwykła" suma kontrolna lub hash typu SHA-1), ale również ich autentyczność.
Oddzielną kwestią jest temat poufności danych przechowywanych w sesji. Można zastanawiać się, czy dane te rzeczywiście powinny być tajne. Myślę, że w scenariuszu, w którym wszystkie informacje o stanie sesji przechowywane są po stronie klienta, szyfrowanie tych danych jest uzasadnione. Po prostu informacje zawarte w sesji mogą ujawnić użytkownikowi (atakującemu) sposób funkcjonowania aplikacji i pomóc w skonstruowaniu skutecznego ataku. Nie należy tego zadania ułatwiać i dane warto utajnić. Tu również można skorzystać z gotowych rozwiązań, przecież ludzie zaczęli wymyślać sposoby na utajnianie informacji już bardzo, bardzo dawno temu. Z różnym skutkiem.
Sprawa zabezpieczenia przechowywania danych po stronie klienta wydaje się prosta:
- Tampering -> HMAC,
- Information Disclosure -> szyfrowanie danych,
W pewnym sensie jest rzeczywiście prosta, wiadomo co trzeba zrobić. Pozostaje pytanie - jak to zrobić. Trzeba zastanowić się między innymi nad następującymi zagadnieniami:
- jakiego klucza/kluczy użyć,
- czy różne sesje powinny korzystać z tego samego klucza,
- czy wszystkie "stany sesji" w obrębie sesji szyfrować tym samym kluczem,
- jakich algorytmów użyć i jakich trybów użyć,
Wydaje mi się, że można zastosować opisane poniżej podejście, ale lojalnie uprzedzam, że w 100% tego pewny nie jestem. Część z propozycji jest zgodna z pomysłem zawartym w pytaniu przez Tomka.
Klucze można otrzymywać na podstawie części tajnej (znanej serwerowi) i jawnej generowanej na początku sesji i składowanej razem z danymi sesji. W zasadzie etap ten można powtarzać przy każdym kolejnym kroku (zapisaniu danych sesji). Dokładnie w ten sposób generowane są klucze przez mechanizm DPAPI w Windows. W ten sposób można uzyskiwać oddzielny klucz do szyfrowania i oddzielny do funkcji HMAC, aczkolwiek nie jestem przekonany, że użycie oddzielnych kluczy daje jaką rzeczywistą wartość dodaną.
Jeśli z jakichś powodów jeden klucz ma być wykorzystany więcej niż jeden raz, należy wybrać taki algorytm szyfrowania, który w takim przypadku jest bezpieczny. Na przykład nie należy używać XOR (przypomnę: Jak NIE używać XOR) czy RC4 (choćby dlatego: Don’t Use Office RC4 Encryption. Really. Just don’t do it.), a i w przypadku innych algorytmów też można sobie zrobić krzywdę: Why Isn't My Encryption.. Encrypting?
Tu warto zauważyć, że zarówno ASP.NET jak i JSF pozwala na szyfrowanie ViewState (ASP.NET View State Overview, Security configuration for Myfaces Core 1.1.8, 1.2.9, 2.0.1 and later). A skoro ponownie te dwie nazwy się pojawiły, trzeba przypomnieć o Padding Oracle Attacks (patrz też: Wyrocznia w ASP.NET), a i ataki czasowe trzeba traktować poważnie.
Czy można bezpiecznie przechowywać sesję po stronie klienta
Odpowiedź na pytanie czy dane sesji można bezpiecznie przechowywać po stronie klienta w zasadzie brzmi: tak. Stosując mechanizmy zapewniające integralność, autentyczność i poufność przechowywanych danych, można je bezpiecznie przechowywać po stronie klienta. Ale z tego, że dane można przechowywać bezpiecznie wcale nie wynika, że aplikacja korzystająca z takiego mechanizmu będzie bezpieczna. Choćby dlatego: ASP.NET __VIEWSTATE crypto validation prone to replay attacks, ale do tego tematu jeszcze prawdopodobnie wrócę.
A jakie jest Wasze zdanie zarówno o przechowywaniu danych sesji po stronie klienta, jak również o sposobach zabezpieczenia danych sesji przechowywanych w ten sposób? Jakie scenariusze ataku w tym przypadku dostrzegacie?
Chociaż nie bo to wymaga pamiętania po stronie serwera numeru sekwencji czyli pamiętania danych na serwerze, czyli wracamy do "starego sposobu" przechowywania sesji. Można zamiast tego zrobić coś w oparciu o token czasowy, ale zawsze zostaje "okienko" dla powtórnego wysłania danych.
Chyba nie ma zupełnie bezpiecznego dla aplikacji, moim zdaniem, sposobu na przechowywanie danych sesji całkowicie po stronie klienta, bez udziału serwera, jakiś element musi do weryfikacji być poza zasięgiem klienta, co powoduje że lepiej moim zdaniem jest wszystko (poza tokenem sesji) przechowywać po stronie serwera. Koszt i komplikacja implementacji jest taki sam.
Dzięĸi za rozjaśnienie zagadnienia
Pozdrawiam
Tomek