W odniesieniu do aplikacji internetowych wiele razy podkreślałem, że przeglądarka realizuje wyłącznie GUI. Nie można w niej (w sensie w części aplikacji pracującej w przeglądarce) implementować żadnych mechanizmów bezpieczeństwa, ponieważ przeglądarka pracuje w środowisku całkowicie kontrolowanym przez (potencjalnego) intruza, więc wszystkie zaimplementowane w ten sposób mechanizmy zabezpieczeń można obejść. Jeszcze ciekawsze efekty daje przeniesienie modelu typowej aplikacji internetowej do sytuacji, gdy dostępny jest gruby klient.
Niespodzianka - wcale nie muszę korzystać z (waszego) klienta
O czym będzie
Kilka razy miałem okazję testować grubego klienta. Całość aplikacji była stworzona w modelu klient - serwer, komunikacja między (grubym) klientem i serwerem była realizowana za pomocą usług WS (web service).
Co właściwie testować w takich przypadkach
Jak do takich testów się zabrać? Warto zauważyć, że patrząc na całość systemu, aplikacja kliencka służy do interakcji z użytkownikiem, realizuje interfejs użytkownika. Dokładnie tak, jak część aplikacji internetowej uruchamiana w przeglądarce. I podobnie jak w przypadku aplikacji internetowej, tak i w omawianym przypadku część realizującą GUI można swobodnie zignorować, zarówno przy testach, jak i przy ataku.
Jak wygląda interakcja między elementami systemu
Komunikacja między aplikacją a serwerem odbywa się za pomocą WS, czyli aplikacja wywołuje określone metody (usługi) udostępniane przez serwer. Istotne jest więc sprawdzenie samego interfejsu, czyli usługi WS. Sprawdzenie to w praktyce sprowadza się do sprawdzenia tego co można osiągnąć wywołując udostępnione przez WS usługi. Opis API jest często bardzo łatwy do zdobycia, wiele (jeśli nie wszystkie) usług WS udostępnia swój opis w postaci WSDL, dzięki czemu otrzymuje się listę dostępnych usług (funkcji) oraz przyjmowanych przez nie parametrów. Nic nie stoi na przeszkodzie, by w oparciu o takie informacje napisać własnego klienta.
Własny klient - można, ale nie trzeba
Pisanie własnego klienta nie jest krokiem niezbędnym, jest to tylko możliwość. A piszę o niej tylko po to, by podkreślić prosty fakt, że oficjalnie udostępniony klient wcale nie musi być jedyną aplikacją, która z WS korzysta. Tak jak w przypadku aplikacji internetowych nie można zakładać, że klient wykona tylko takie operacje (i w takiej kolejności) na jaką pozwala GUI, tak i tu nie można zakładać, że za pomocą aplikacji klienta jakieś zabezpieczenia uda się wymusić. Wszystkie zabezpieczenia muszą być implementowane po stronie serwera, w tym wypadku po stronie usługi WS. Niestety, fakt ten często pozostaje niezauważany przez architektów i programistów, w efekcie czego wszystkie mechanizmy bezpieczeństwa implementowane są w aplikacji klienckiej, która pracuje w środowisku kontrolowanym przez atakującego, i która, w szczególności, może zostać zastąpiona przez zupełnie inną aplikację. Taka architektura jest po prostu błędna. Założenie, że przy pomocy klienta (w sensie aplikacji klienckiej), nad którym atakujący ma pełną kontrolę, uda się zaimplementować i wymusić mechanizmy bezpieczeństwa, jest z gruntu błędne (mała dygresja: w sumie z tego samego powodu stworzenie skutecznego zabezpieczenia antypirackiego nie jest zadaniem trywialnym).
Dwa schematy DFD
Czasami trudno spojrzeć całościowo na problem, warto wówczas wspomóc się rysunkiem. W tym wypadku pomocne będą dwa dość ogólne diagramy DFD.
Najpierw pierwszy:
Zwracam uwagę na przebieg trust boundary (czerwona przerywana linia). Znajduje się ona pomiędzy aplikacją (grubym klientem), a serwerem udostępniającym usługę. W takim wypadku wszelkie data flow (na schematach oznaczone jako strzałki) trzeba traktować jako niezaufane, w dodatku objęte następującymi zagrożeniami:
- tampering,
- information disclosure,
- denial of service,
Tampering w tym wypadku może oznaczać modyfikację komunikacji między aplikacją a usługą. Można też podstawić własnego klienta, który pożądane metody w sposób oczekiwany przez atakującego będzie wywoływał. Przy okazji warto zauważyć, że obiekt aplikacji uwzględniony na pierwszym schemacie jest podatny między innymi na spoofing, czyli w praktyce na podstawienie własnej aplikacji zamiast tej "oryginalnej". Można spróbować implementować pewne mechanizmy wzajemnego uwierzytelnienia między klientem i serwerem, ale to również jest zadanie karkołomne (tu nie mogę się powstrzymać, dobry przykład złego przykładu: Ipswitch WhatsUp Professional Crafted Header Authentication Bypass). Zresztą obiekt aplikacji (process) jest objęty wszystkimi typami zagrożeń określonymi w STRIDE. Zagrożenia (threats) są znane, ale realnie patrząc implementacja skutecznych zabezpieczeń (countermeasures) jest mało realna.
Z tego powodu warto spojrzeć na taką aplikację w nieco inny sposób, który prezentuję na drugim schemacie:
Jest to spojrzenie ze strony usługi WS. Aplikacja kliencka na tym schemacie nie występuje, została ona połączona z klientem (człowiekiem) w jeden zewnętrzny dla systemu element (external entity). Ten schemat jest w zasadzie identyczny ze schematem DFD dla typowej aplikacji internetowej i nie jest to przypadkiem. Dlatego projektując i implementując web service najlepiej nie robić żadnych założeń odnośnie aplikacji klienta, jej nie ma. Wszystkie udostępniane przez web service metody mogą być wywoływane w dowolnej kolejności, z dowolnymi parametrami, a sama usługa musi bronić się sama.
Problemy ze stanem (sesje w WS)
Mam wrażenie, że w kilku wypadkach podatności (związane głównie z błędami kontroli dostępu do funkcji i danych) wynikały ze sposobu implementacji sesji w WS, a właściwie ze sposobu ich symulacji przez programistów. Wyglądało to na przykład tak, że po uwierzytelnieniu się do aplikacji użytkownik w odpowiedzi otrzymywał pewien token, który następnie był przekazywany jako jeden z parametrów wywołania kolejnych funkcji (lepsze to, niż wywoływanie każdej funkcji z nazwą użytkownika i hasłem, a i takie przypadki się zdarzają). Na podstawie tego tokenu system przy wywołaniu określonej funkcji sprawdzał, czy dany użytkownik ma prawo ją wywołać. Teoretycznie koncepcja akceptowalna, ale jej bezpieczeństwo zależy głównie od tego czy token ów jest trudny do przejęcia lub odgadnięcia, a z tym to już bywa różnie. Lepszym rozwiązaniem (przynajmniej w przypadku .NET) i mniej podatnym na błędy implementacji (mniej trzeba dodać "od siebie"), jest wykorzystanie znanych z aplikacji internetowych sesji: Using ASP.NET Session State in a Web Service.
Małe uzupełnienie do I co z tego, że jest SQLi w Płatniku? Napisałem, że błędy są już w architekturze programu, więc błędy w implementacji nie robią już tak dużego wrażenia. Warto przyjrzeć się temu tematowi, bo sposób działania programu Płatnik jest bard
Przesłany: May 11, 21:19
...od razu afera: Brute-forcing ksiąg wieczystych. Problem w tym, że Księgi Wieczyste są jawne i każdy może je przeglądać. Ja wiem, że to jest szok, gdy nagle znajdzie się swoje dane i to w dodatku dość dokładne, ale te dane były już powszechnie dostępne
Przesłany: Jun 18, 19:28