Właściwie mógłbym nie pisać drugiej części do Znajdź błąd projektowy: wykorzystanie AJAX (część I), bo odpowiedź znalazła się w komentarzach. Mimo wszystko małe podsumowanie.
Znajdź błąd projektowy: wykorzystanie AJAX (część II) - rozwiązanie
Na czym polegał błąd w projekcie
Problem, na który chciałem zwrócić uwagę związany jest z DF3, co zauważył już Marcin. W opisie sytuacji nie ma nic na temat walidacji tego data flow, ponieważ taka walidacja jest często pomijana.
Co jest efektem braku walidacji? Efekt jest taki, że klient może dowolnie zmodyfikować wysyłane przez swoją przeglądarkę zapytanie AJAX, w związku z czym zapytanie, które trafia do webservice może być zupełnie inne, niż to, które przygotowała aplikacja. Jest to dokładnie to, o czym pisał Tomek w (2) w swoim komentarzu. Jak słusznie zaznaczył Kravietz w szczególności klient może pominąć etap komunikacji z aplikacją przed dostępem do webservice.
Trzeba zapamiętać, że dane przekraczające trust boundary należy traktować jako niezaufane. W tym scenariuszu jeśli webservice ma zwracać dane wyłącznie na podstawie przekazywanych za pośrednictwem klienta danych z aplikacji, trzeba te dane zabezpieczyć przed modyfikacją, lub przynajmniej spowodować, by każda ich modyfikacja była możliwa do wykrycia przez webservice (patrz: tamper-evident).
Potraktowanie klienta jako data store
Ciekawy komentarz umieścił Tomek, którego fragment pozwolę sobie zacytować:
(...) A good programming philosophy is... if you don't completely control the information store, you can't completely trust the information store. (...)
Patrząc na schemat po stronie klienta ciężko znaleźć data store, zresztą cały klient został zredukowany do external entity. Uwaga jednak jest trafna, choć trzeba nieco wysilić wyobraźnię.
Wyobraźmy sobie sytuację, w której klient przy jednym okienku płaci za zakup towaru, a przy innym odbiera go. Klient między okienkami przenosi informację za co zapłacił, czyli informację co powinno mu zostać wydane. Ta (hipotetyczna) kartka jest swoistym data store, który przechowuje informacje odnośnie tego, za co klient zapłacił i co powinno mu zostać wydane. W takim scenariuszu od razu "rzuca się w oczy" atak, polegający na modyfikacji (tampering) owej kartki po to, by dostać nie to, za co się zapłaciło.
Jak ta sytuacja mapuje się na prezentowany scenariusz? Pierwszym okienkiem jest aplikacja, która po sprawdzeniu pewnych warunków określa, czy i co klientowi powinno zostać zaprezentowane. Drugim okienkiem jest natomiast webservice, w którym realizowane jest zamówienie klienta, czyli odpowiednie dane są udostępniane. Rolę "kartki" pełni fragment kodu przesłany przez aplikację do klienta w DF2, który generuje żądanie (z odpowiednimi parametrami) DF3. Problem w tym, że zawartość tej "kartki" może zostać zmodyfikowana przez (atakującego) klienta.
W zasadzie sytuację taką można porównać na przykład do przekazywania istotnych parametrów w polach ukrytych, czy nawet bardziej ogólnie do CWE-642: External Control of Critical State Data.
Próba rozwiązania problemu poprzez wykorzystanie HMAC
Jednym z możliwych sposobów zabezpieczenia przed tego typu atakiem może być wykorzystanie MAC. Takie rozwiązanie stosowane jest na przykład dla zabezpieczenia ViewState w ASP.NET. Warto zauważyć, że zastosowanie HMAC dla ViewState nie zabezpiecza przed innym potencjalnym scenariuszem ataku, a mianowicie podmianą całej wartości, dlatego warto korzystać z ViewStateUserKey. Problem w tym, że rozwiązanie z (H)MAC nie bardzo sprawdza się w przypadku, gdy dane są prezentowane za pośrednictwem AJAX. Może nawet nie tyle "nie sprawdza się", co wymaga pewnej refleksji przed jej wykorzystaniem.
Typowa sytuacja, w której spotykałem się z wykorzystaniem AJAX, jest prezentowanie danych (być może będących rezultatem wyszukiwania) oraz:
- stronnicowanie,
- sortowanie,
W takim wypadku klient wybierając inne warunki sortowania lub kolejną/poprzednią stronę, jednocześnie generuje żądanie AJAX, które pobiera kolejny "fragment" zbioru danych według zadanych kryteriów. Zysk z wykorzystania AJAX jest taki, że do klienta przesyłane są jedynie potrzebne mu w danej chwili dane, nie jest ładowana ponownie cała strona, nie ma konieczności jej ponownego przesyłania z serwera.
Co w takim przypadku miałoby zostać objęte owym kodem (H)MAC? Na pewno nie całość danych określających parametry wywołania żądania AJAX, ponieważ użytkownik może chcieć zmienić część z nich (wspomniane sortowanie na przykład) i ma do tego pełne prawo. Niezmienna powinna pozostać ta część, która decyduje jakie dane użytkownikowi pokazać, w szczególności zaś to, co ogranicza dostęp do danych.
Przykładowo jeśli każdy rekord zawiera identyfikator właściciela i użytkownik powinien widzieć tylko swoje rekordy, to niezmienną częścią żądania powinna być ta część, w której informacja na temat identyfikatora użytkownika jest przekazywana. Trzeba ją jakoś zabezpieczyć, a wykorzystanie HMAC jest taką formą zabezpieczenia.
Przy okazji mały akcent humorystyczny: SQL Injection Vulnerability in Oracle WWV_FLOW_UTILITIES. Komuś się chyba zapomniało, że wyliczenie nowej sumy kontrolnej md5 na podstawie znanych danych (nowego, wybranego przez atakującego zapytania SQL), jest zadaniem dość trywialnym...
Alternatywne podejście do tematu
Można się zastanawiać, czy przekazywanie takich informacji (np. identyfikatora użytkownika) między aplikacją a webservice jest konieczne. Prawdopodobnie wiele zależy od zastosowanej architektury. Spotkałem się również z rozwiązaniem, gdzie klientowi jest zwracany identyfikator zbioru danych. W takim scenariuszu to aplikacja komunikuje się z webservice przygotowując odpowiedni zbiór danych dla klienta, na który następnie klient otrzymuje wskaźnik (otrzymuje identyfikator takiego zbioru danych) i odwołując się do webservice podaje ten identyfikator, oraz parametry określające sposób, w jaki dane te mają być prezentowane (np. ilość elementów na stronę, sortowanie).
Oczywiście można twierdzić, że kontrola dostępu do danych w tym wypadku jest realizowana poprzez security by obscurity i "wystarczy" odgadnąć odpowiedni identyfikator zbioru danych. Zarzut ten będzie zasadny jeśli identyfikatory te będą postaci 1, 2, 3, (...), 1942, 1943, (...). Jeśli jednak identyfikatory takie będą miały postać a401462b-2449-4ffa-926d-fb2af9049839, 4d214edf-f98b-4347-a1ac-d7998b3ed218, (...), 4e654440-1190-4a1b-8bfa-78d1c9f7953a, to stopień złożoności takiego zadania staje się co najmniej porównywalny z zadaniem odgadnięcia identyfikatora sesji.