Widzę, że temat identyfikatorów pośrednich nie spotkał się z większym zainteresowaniem, a szkoda. Ilość błędów kontroli dostępu do danych, z którymi się spotkałem w trakcie testów penetracyjnych aplikacji internetowych jest duża. Praktycznie za każdym razem wynikają one właśnie z użycia identyfikatorów globalnych (ponownie patrz: Insecure Direct Object Reference).
Identyfikatory globalne i pośrednie II
Jestem nudny i jeszcze raz krótko napiszę o całej koncepcji
Wykorzystanie warstwy pośredniczącej, która dokonuje mapowania identyfikatorów globalnych (rzeczywistych) na identyfikatory prezentowane w warstwie prezentacji jest stosunkowo prostym i jednocześnie bardzo skutecznym sposobem zmniejszenia szansy na wystąpienie tego typu problemów. Obrazuje to poniższy schemat:
Na schemacie tym widać dwóch użytkowników User A oraz User B. Dla każdego z tych użytkowników aplikacja tworzy zbiór mapować pośrednich (na przykład w obrębie sesji każdego użytkownika). By uzyskać dostęp do określonego obiektu użytkownik User A musi przejść przez warstwę pośrednią i zaprezentować identyfikator (pośredni) obiektu. Może go dowolnie modyfikować w stosunku do tego co zostało wypisane na stronie. Jeśli ktoś ma co do tego wątpliwości, polecam Bootcamp: absolutne podstawy. By podkreślić ten fakt na schemacie zaznaczyłem Trust Boundary. By uzyskać jednak dostęp do obiektu o identyfikatorze rzeczywistym (globalnym) 321 musiałby "pokonać" funkcję dokonującą translacji. Jest to dość trudne. Nie napiszę, że niemożliwe, bo inwencja ludzka nie ma granic (i chodzi mi tu bardziej o programistów niż włamywaczy). Użytkownik User A może więc dowolnie modyfikować wartość identyfikatora pośredniego, ale doprowadzi go to wyłącznie do:
- dostępu do własnych danych (jeśli w zbiorze istnieje odpowiednie mapowanie),
- błędu aplikacji (jeśli odpowiedniego mapowania nie ma),
W przypadku, gdy wystąpi błąd aplikacji, z czystym sumieniem można zniszczyć sesję użytkownika i wezwać kawalerię. Odwołanie z identyfikatorem pośrednim oznacza, że ktoś coś kombinuje. Może to oznaczać również, że jest błąd w aplikacji, ale wówczas też taka brutalna akcja ma swoje uzasadnienie - łatwiej taki błąd wyłapać.
Tu pozwolę sobie na małą dygresję. Realizacja "awaryjnego" wylogowania w takim przypadku nie powinna polegać na przekierowaniu użytkownika na adres akcji Wyloguj, tylko na natychmiastowym, brutalnym zniszczeniu sesji. Dlaczego? Bo "włamywacz" wcale nie musi być kulturalny i za przekierowaniem na akcję Wyloguj nie musi podążyć...
Trzeba pamiętać o ograniczeniach
Choć zastosowanie warstwy pośredniej ma bardzo dobry stosunek kosztów (nakład na jej implementację) do zysku (zwiększenie bezpieczeństwa), to nie zawsze jej wykorzystanie jest uzasadnione lub możliwe. Nie zawsze również wykorzystanie warstwy pośredniej rozwiąże wszystkie problemy. Kilka wybranych przykładów poniżej.
Zasoby publicznie dostępne
W omawianym przykładzie dane dostępne w "głównym" zbiorze były dostępne po uwierzytelnieniu. Co więcej zbiór danych dostępnych dla użytkownika User A był rozłączny ze zbiorem danych dostępnych z użytkownikiem User B. Nie zawsze jednak taka sytuacja zachodzi. Prostym przykładem jest forum. Forum może być dostępne publicznie (bez uwierzytelnienia). Co więcej właścicielowi forum może zależeć, by było ono indeksowane przez wyszukiwarki. Wówczas istotne jest, by ta sama treść była dostępna zawsze pod tym samym adresem. W tym przypadku zastosowanie identyfikatorów lokalnych (pośrednich) nieco mija się z celem, a z punktu SEO jest strzałem z łuku w kolano. Trudno tu również mówić o braku kontroli dostępu do danych, skoro dane są publicznie dostępne. Istotna może być jednak kontrola dostępu do funkcji, a konkretnie kontrola tego kto (jaki użytkownik) może zrobić co (funkcja) na czym (konkretne dane). I tak w płynny sposób można przejść do kolejnego punktu...
Kontrola dostępu do funkcji
Można ogólnie powiedzieć, że w aplikacji są dane oraz funkcje. W szczególności są funkcje, które użytkownik może wykonać na danych. A może to być przykładowo wyświetlenie, edycja czy usunięcie. W zależności od uprawnień użytkownika, może on mieć prawo do przeglądania danych (w szczególności dane mogą być publicznie dostępne), przeglądania i modyfikacji czy przeglądania, modyfikacji i usunięcia danych.
Tu znów mała dygresja. Z doświadczenia (które zaczyna mi ciążyć i fatalnie odbijać się na mojej psychice przy testach kolejnej aplikacji z takimi samymi podatnościami) wiem, że usunięcie podatności związanych z kontrolą dostępu do danych (i funkcji), nie jest proste. Może być na przykład tak, że w pierwszym podejściu kontroli dostępu do danych i funkcji nie ma wcale. W drugim jest nieco lepiej, ale wciąż nie jest idealnie. W kolejnym może się wydawać, że jest już dobrze, a tu się okazuje, że cudzych danych nie można co prawda przeglądać czy edytować, ale można usunąć...
W takim przypadku zastosowanie identyfikatorów pośrednich nie rozwiąże problemu (samo przez się), chyba, że wprowadzone zostaną oddzielne identyfikatory pośrednie dla każdej akcji (funkcji), którą użytkownik na konkretnych danych może wykonać.
Sekcja zwłok (forensic)
Jak Przemek słusznie zauważył, używanie identyfikatorów pośrednich może utrudnić analizę powłamaniową. Przy braku odpowiedniego logowania niemożliwe może być ustalenie kto do czego właściwie uzyskał dostęp. A to dlatego, że różne dane w różnym czasie mogą mieć ten sam identyfikator. Lub odwrotnie - te same dane w różnym czasie mogą mieć różne identyfikatory.
CSRF
To jest odpowiedź na zadane przeze mnie pytanie. Zastosowanie identyfikatorów pośrednich (np. w formie 0, 1, 2, 3, ...) ułatwia atak Cross Site Request Forgery. Nie da się ukryć, że odgadnięcie identyfikatora globalnego w "intensywnie wykorzystywanej aplikacji" może być problematyczne. Zastosowanie identyfikatorów pośrednich we wspomnianej formie zadanie to zdecydowanie ułatwia. Oczywiście można zamiast tej formy wykorzystywać identyfikatory losowe. Można też problem CSRF rozwiązać poprawnie i wykorzystać odpowiednio losowe tokeny.
I na zakończenie
Na pewien czas temat identyfikatorów globalnych porzucę. Dlaczego są ZŁE i jak można je wyeliminować pisałem na tyle wiele razy, że muszę już przestać, bo zacznę się powtarzać.
Pytanie dodatkowe nie związane z tematem: kiedy (prawdopodobnie) powstał ten post? Co za tym przemawia?
Jakiś czas temu pisząc o identyfikatorach globalnych i pośrednich zadałem pytanie całkiem niezwiązane z tematem: Pytanie dodatkowe nie związane z tematem: kiedy (prawdopodobnie) powstał ten post? Co za tym przemawia? Temat chyba nikogo nie zainteres
Przesłany: Nov 13, 17:45