30-12-2018 / Od 0 do pentestera

Cross-Site Websocket Hijacking

Jeszcze nie tak dawno, aby na stronie internetowej treści pojawiały się w czasie rzeczywistym, należało to niejako symulować.

Czyli z poziomu JavaScript co kilka sekund wysyłać żądanie do serwera i pobierać najnowsze treści.

Im częściej wysyłaliśmy zapytania, tym szybciej użytkownik dostawał odpowiedź.

Te czasy są już za nami. Teraz do tego celu wykorzystuje się websockety, które pozwalają na dwukierunkową komunikację pomiędzy klientem i serwerem w czasie rzeczywistym.

Jak działa websocket
https://www.pubnub.com/learn/glossary/what-is-websocket/

Ale jakie pułapki mogą na nas czekać, jeżeli będziemy chcieli zaimplementować tą funkcjonalność w naszym serwisie?

Dzisiaj o mało popularnym ataku: Cross-Site Websocket Hijacking.

Popatrzmy na przykładową implementacja banalnego websocketu w Java.

Implementacja websocketu

Z racji prostoty przykładu obsługuję tu tylko wiadomości przychodzące.

Jeżeli coś pojawi się na websokecie, odsyłam tą treść do użytkownika dodając do niej tekst: tajny socket.

Równocześnie mamy bardzo prosty kod w JavaScript, który próbuje się połączyć z endpointem i, jeśli to nastąpi - wysłać do niego prostą wiadomość hej.

Kod w JavaScript

Dodatkowo nasłuchujemy wszystkich komunikatów od serwera i wyświetlamy je w postaci alertów - tak aby były łatwo widoczne.

Zobaczmy jak to działa w praktyce.

Po odświeżeniu strony otrzymujemy komunikat: Tajny socket: hej.

Jak zatem działa taka komunikacja? Prześledźmy ją w Burpie.

Na początku nasza strona wysyła żądanie http lub https do serwera, w którym informuje, że chce przejść na websocket.

Żądanie HTTP

W odpowiedzi serwer zwraca kod 101 i od tego momentu komunikacja odbywa się już nie na podstawie protokołu HTTP, ale ramek websocketów.

Odpowiedź na żądanie HTTP

Gdzie zatem tkwi dzisiejszy błąd?

Ramki websocket

Aby maksymalnie uprościć przykład - nie jest w nim widoczna żadna metoda autoryzacji użytkownika.

Sam protokół również nie definiuje jak serwer ma sprawdzić czy dana osoba ma uprawnienia do danego zasobu.

Dlatego też najczęściej wykorzystuje się do tego celu ciasteczka.

Stwórzmy zatem przykładowe ciasteczko z przykładową treścią i zobaczmy nasz przykład jeszcze raz.

Widzimy, że w naszym żądaniu przekazane została wartość ciasteczka.

Ciasteczko w żądaniu

Załóżmy, że na jego podstawie serwer rozpoznał użytkownika i zezwolił na dostęp.

Szkopuł w tym, że przeglądarka automatycznie dokleja pasujące ciasteczko podczas wykonywania żądania.

I nie ma dla niej znaczenia, skąd to żądanie pochodzi.

Znamy tą funkcjonalność - twórca dowolnej strony może bowiem zawrzeć na niej przycisk Facebooka i oglądając tą stronę Facebook wie, że jesteśmy zalogowani, ponieważ przeglądarka automatycznie wysłała ciasteczko do serwera.

Przycisk like

Równocześnie twórca strony nie wie nic o tym ciasteczku - tak działają nowoczesne przeglądarki.

Coś co ze strony użyteczności może być fajnym rozwiązaniem - ze strony bezpieczeństwa może być niebezpieczeństwem.

Stwórzmy bowiem stronę atakującego, dostępną na zladomena.

Tutaj umieszczamy dokładnie taki sam kod JavaScript jak poprzednio.

Ten kod znowu zadziałał.

Jak to możliwe?

W kodzie JavaScript odnosimy się do dobradomena.

Jak wiemy, podczas inicjalizacji połączenia z websocketem najpierw wysyłane jest żądanie HTTP.

Przeglądarka widząc więc dobradomena dołączyła do niej pasujące ciasteczko.

Serwer sprawdził ciasteczko i zwrócił odpowiedni socket.

Tym samym zladomena może teraz odczytywać oraz zapisywać dowolne dane przy użyciu tego mechanizmu.

Zauważmy, że atakujący nie musiał posiadać żadnego loginu i hasła - wystarczyło jedynie, że atakowana osoba była wcześniej zalogowana na dobradomena i jej ciasteczko było dalej prawidłowe i znajdowało się w pamięci przeglądarki.

Nagłówek origin

Jak zatem uchronić się przed tym atakiem?

Przeglądarka oprócz dodawania do żądań ciasteczek, dodaje również nagłówek origin, w którym zawiera nazwę domeny, od której pochodzi dane zapytanie.

Jeżeli więc staramy się połączyć z socketem używając zladomena, to właśnie ona pojawi się w tym nagłówku.

Każda przeglądarka jest zobowiązana do dołączania tego nagłówka automatycznie.

Możemy zatem sprawdzać jego treść po stronie serwera i weryfikować czy żądanie pochodzi z naszej witryny, czy też z witryny zewnętrznej.

Wtedy nawet jeżeli ciasteczko jest prawidłowe, a żądanie pochodzi z obcej domeny - będziemy je w stanie rozpoznać i odrzucić żądanie.

Implementacja nie jest skomplikowana.

Implementacja

Tworzymy dodatkową klasę Configurator, w której implementujemy funkcję checkorigin.

Sprawdzamy tutaj wartość nagłówka origin - z nazwą naszej dobrej domeny.

Jeżeli się zgadzają - zezwalamy na dostęp.

Sprawdźmy nasz przykład jeszcze raz.

Najpierw na prawidłowej stronie. Wszystko dalej działa prawidłowo.

Teraz zladomena.

Jak widać nie otrzymaliśmy alert.

Jeżeli przyjrzymy się bliżej - zauważymy, że tym razem podczas inicjalizacji połączenia serwer zwrócił błąd 403.

Błąd 403

Tym samym pomimo wysłania prawidłowego ciasteczka - serwer odrzucił nasze żądanie, a przeglądarka nie otrzymała dostępu do websocketa.

Icon made by Freepik, Eucalyp, Webalys, prettycons, monkik, Flat-icons-com from www.flaticon.com