Bezpieczeństwo aplikacji internetowych – cz. 2. Broken Authentication & Sensitive Data Exposure

OWASP Broken Authentication & Sensitive Data Exposure

Po krótkiej przerwie wracam do kontynuacji serii związanej z OWASP Top 10. Dzisiaj na warsztat biorę dwa punkty z listy: Broken Authentication i Sensitive Data Exposure. Te dwie grupy podatności mają wspólne elementy, stąd decyzja o połączeniu ich opisu w jeden artykuł.

Wygoda ponad bezpieczeństwo 

W dzisiejszych czasach część serwisów wymaga założenia konta, aby w pełni móc korzystać z usług, np. portale społecznościowe, sklepy, gry przeglądarkowe itp. To tworzy nowe wektory ataku dla cyberprzestępców. Twórca serwisu musi brać pod uwagę m.in. taki czynnik jak nieuwaga użytkownika, który z wygody zapisuje hasło na kartce znajdującej się w widocznym miejscu lub używa jednego hasła do wszystkich serwisów zamiast np. użyć managera haseł. Projektant czy programista tworzący portal musi być gotowy na taką ewentualność. Jednak to nie jedyny czynnik, na który powinien zwrócić uwagę twórca serwisu. Atakujący mogą wykorzystać szereg różnych luk. Ich opisy wraz z dobrymi praktykami, które pomogą zapobiec atakom, znajdziecie poniżej.

A2-Broken Authentication

Jest to grupa podatności powiązana z błędami logowania, rejestracji oraz sesji. Luki tej kategorii mogą powodować wycieki haseł, kluczy, tokenów sesyjnych. Może to mieć wpływ na powstawanie różnych szkód, w zależności od rodzaju wycieku. Jeżeli wyciekną dane umożliwiające zalogowanie do konta zwykłego użytkownika, to zazwyczaj tylko jego dane są zagrożone, ale jeżeli wyciek dotyczy kont osób z dużymi uprawnieniami np. administratorów, to szkody mogą być o wiele większe.

Błędy, błędy, błędy… 

Poniżej można znaleźć przykłady błędów, które należą do tej grupy, wraz z zaleceniami, jak się przed nimi zabezpieczać.

Brak ograniczonej liczby prób logowania

Brak ograniczenia liczby prób logowania dla jednego konta może pozwolić napastnikowi wykonać atak typu brute force z użyciem dostępnych w Internecie słowników, które zawierają hasła z wycieków oraz najczęściej używane kombinacje typu admin, 123456 itp. Serwis powinien zawierać mechanizm blokujący logowanie dla danego konta np. na godzinę w przypadku, gdy wystąpiło kilka nieudanych prób logowania (zwykle 3). W niektórych przypadkach, np. w bankowości internetowej w przypadku kilku błędnych prób logowania, wymagana jest dodatkowa forma uwierzytelnienia. 

Brak ograniczenia rejestracji nowych kont z jednego adresu IP w krótkim czasie

Brak ograniczenia w tworzeniu nowych kont może spowodować, że nasz serwis zostanie np. zaspamowany. Warto wprowadzić ograniczenie w tworzeniu kont np. z użyciem mechanizmu captcha. Ten mechanizm może utrudnić spamerowi działanie, ale nie zapewnia 100% skuteczności. W sieci można znaleźć materiały, które opisują, jak przygotować skrypty rozpoznające litery na obrazku i umożliwiające ominięcie sprawdzania. Być może lepszym rozwiązaniem jest zablokowanie możliwości założenia większej liczby kont w krótkim czasie z jednego adresu IP.

Przechowywanie haseł w plain text

Niektóre serwisy przechowują hasła w bazie danych w postaci czystego tekstu. Jest to bardzo zła praktyka. W przypadku skompromitowania takiej bazy napastnik ma konta wszystkich użytkowników jak na dłoni. Wszystkie hasła powinny być zaszyfrowane lub przechowywane w postaci haszy, czyli ciągu znaków wygenerowanego przez odpowiednia funkcje skrótu. Za pomocą funkcji skrótu generujemy wartość o stałym rozmiarze. Funkcja ta powinna posiadać następujące cechy:

  • jednokierunkowość -> na podstawie wartości wygenerowanej przez funkcje skrótu nie można wykonać odwrócenia i zwrócenia oryginalnie podanych na wejściu danych
  • odporność na kolizje -> ciężko znaleźć dwie lub więcej wartości, dla których generowania jest taka sama wartość hasza
  • nieprzewidywalność -> wartość zwracana przez funkcję haszującą zmienia się w sposób losowy razem ze zmianą danych na wejściu. Innymi słowy, nie jesteśmy w stanie znaleźć wzorca, na podstawie którego moglibyśmy wywnioskować, co zostanie wygenerowane przez funkcje skrótu
  • deterministyczność -> dla tych samych danych zawsze dostaniemy ten sam ciąg

Dobrze jest też dodać do hasła sól, czyli losowy dla każdego użytkownika ciąg znaków, który utrudni napastnikowi odgadnięcie hasła. 

Wyobraźmy sobie scenariusz, w którym trzech użytkowników ma takie samo hasło, np. słowo password. 

Hash wygenerowany dla słowa password z użyciem algorytmu SHA256, bez użycia soli ma następującą postać:

5E884898DA28047151D0E56F8DC6292773603D0D6AABBDD62A11EF721D1542D8

W przypadku kompromitacji bazy atakujący zobaczy trzy takie same wartości hasz, co powie mu, że ci użytkownicy używają tego samego hasła.

A teraz dla porównania ta sama sytuacja, ale dla każdego z użytkowników mamy losową sól, której wartości niech będą kolejnym numerami użytkowników tj. 1, 2, 3.

Użytkownik 1
hasło bez soli: 
5E884898DA28047151D0E56F8DC6292773603D0D6AABBDD62A11EF721D1542D8

hasło z solą o wartości 1:
0B14D501A594442A01C6859541BCB3E8164D183D32937B851835442F69D5C94E

Użytkownik 2
hasło bez soli: 
5E884898DA28047151D0E56F8DC6292773603D0D6AABBDD62A11EF721D1542D8

hasło z solą o wartości 2:
6CF615D5BCAAC778352A8F1F3360D23F02F34EC182E259897FD6CE485D7870D4

Użytkownik 3
hasło bez soli: 
5E884898DA28047151D0E56F8DC6292773603D0D6AABBDD62A11EF721D1542D8

hasło z solą o wartości 3:
5906AC361A137E2D286465CD6588EBB5AC3F5AE955001100BC41577C3D751764

Hasła wygenerowane z użyciem soli różnią się, co utrudnia napastnikowi rozpoznanie, że użytkownicy mają takie samo hasło.

Na stronie GitHub można znaleźć niechlubną listę serwisów, które przechowują hasła w plain text. Pamiętajmy również, że nie tylko baza danych powinna zawierać zaszyfrowane / haszowane wartości. Również hasła pojawiające się w logach lub backupach powinny być tak samo zabezpieczone.

Brak mechanizmu wykrywania słabych haseł przy rejestracji, ograniczenie długości haseł itp.

Nie tylko przechowywanie hasła jest ważne, ale również to, jakie ono jest. Hasło podawane przy rejestracji powinno być odpowiednio silne, mieć co najmniej 8 znaków, zawierać duże i małe litery, cyfry oraz znaki specjalne. Warto zaimplementować swój mechanizm lub użyć gotowego do sprawdzania, czy podane hasło nie jest jednym z popularnych, prostych, mało bezpiecznych haseł typu admin, 123456, qwerty itd. Dodatkowo niektórzy twórcy serwisów wprowadzają ograniczenie maksymalnej długości hasła, co według mnie jest nieskuteczne. Moim zdaniem, chociaż może to trochę niepopularna opinia, odpowiednio długie hasło np. jakieś rymujące się zdanie jest lepsze niż np. krótki abstrakcyjny ciąg znaków typu T3st0w3. Dlaczego? Po pierwsze łatwiej je zapamiętać, po drugie dłużej trwa jego złamanie metodą brute force, czasem może być to niemożliwe w sensownym dla człowieka czasie. 

W sieci można znaleźć strony, gdzie istnieje możliwość przetestowania siły swojego hasła. Osobiście nie polecam korzystania z nich, ze względu na to, że nie mamy pewności, czy takie hasła nie są potem gdzieś zapisywane.

Brak dwuskładnikowego uwierzytelniania

Tak jak wspomniałem we wstępie, dwuskładnikowe uwierzytelnianie zapewnia nam bezpieczeństwo konta w przypadku, gdy hasło i login zostaną skompromitowane. Może to zostać zrealizowane z użyciem dodatkowej aplikacji, maila lub SMS-a. 

ID sesji w URL-u

Czasem tokeny sesyjne są umieszczane w linku. Nie jest to do końca dobre rozwiązanie. Wyobraźmy sobie przypadek, gdy mamy źle skonfigurowane ustawienia nagłówka Referrer-Policy. Taki link znajdzie się w nagłówku Referer i może zostać zapisany w logach strony, na którą przechodzimy. Najlepiej unikać umieszczania ID sesji w URL-u. Warto też pamiętać o odpowiednim skonfigurowanym ustawieniu nagłówka Refferer-Policy. Więcej na temat tego nagłówka można znaleźć na stronie Mozilla.

Pozostawione konta deweloperskie

Czasem przez nieuwagę na środowiska produkcyjne przedostają się konta stworzone w celach testowych. Często konta te mają proste hasła i loginy typu test:test, admin:admin itp. Jeżeli posiadają one dodatkowo zwiększone uprawnienia, to dają atakującym bardzo duże pole do popisu. Tutaj najważniejsze jest zadbanie o to, żeby nie przeoczyć takiego konta, a także o stosowanie ściśle wyizolowanych środowisk testowych.

Źle skonfigurowane ciasteczka

Źle skonfigurowane ciasteczka mogą spowodować wyciek tokenów sesyjnych, przez co atakujący będzie w stanie przejąć sesje użytkownika. 

Kilka rad,  jak powinny wyglądać prawidłowo skonfigurowane ciasteczka:

  • ustawienie flagi secure -> ciasteczko z taką flagą będzie doklejone do requestu do serwera tylko wtedy, kiedy przeglądarka uzna, że połączenie z serwerem jest szyfrowane
  • ustawienie flagi HTTPOnly -> z ciasteczka oznaczonego taką flagą nie będzie można odczytać zawartości z użyciem javascriptu. Zabezpiecza ona przed atakami XSS, które mogłyby spowodować odczytanie zawartości ciasteczek sesyjnych, co w efekcie umożliwiłoby napastnikowi przejęcie sesji użytkownika
  • ustawienie flagi SameSite -> flaga ta przyjmuje dwie wartości: strict i lax. Wartość strict uniemożliwia przesłanie ciasteczka, jeżeli request pochodzi z innej domeny niż ta, dla której zostało ono utworzone. Wartość lex jest mniej restrykcyjna. Jeżeli request zostanie przesłany jedną z bezpiecznych metod HTTP (GET, HEAD, TRACE) oraz nastąpi zmiana domeny w pasku adresu przeglądarki (top-level-navigation), wtedy ciasteczko zostanie dołączone do takiego zapytania.

Jeżeli chcecie wiedzieć więcej na temat bezpieczeństwa ciasteczek, to podrzucam kilka artykułów na ten temat:

What all Developers need to know about: Cookie Security
Flaga cookies SameSite – jak działa i przed czym zapewnia ochronę?
Jak w prosty sposób zwiększyć bezpieczeństwo aplikacji webowej

Osoby, które chcą bardziej zagłębić się w tematy związane z tą grupą podatności, zachęcam do zapoznania się z materiałami, które można znaleźć na stronie OWASP

A3 Sensitive Data Exposure

Na pozycji numer trzy znalazła się grupa podatności powiązana z błędami, które powodują wycieki danych wrażliwych, takich jak np. dane osobowe, dane kart kredytowych, skany dowodów, dokumentacja medyczna pacjentów. Poniżej znajdziecie opis kilku takich luk.

Brak szyfrowanego połączenia:

Brak szyfrowania połączenia może skutkować przejęciem danych przesyłanych pomiędzy klientem a serwerem. Dane przesyłane zwykłym HTTP są przesyłane plain textem bez żadnego zabezpieczenia. Całe szczęście obecnie większość portali używa protokołu HTTPS do szyfrowania połączenia. Kiedyś nie było to standardem, co tłumaczono zwiększonym narzutem obliczeniowym oraz kosztami certyfikatów. Czasem ograniczano się do wprowadzania HTTPS tylko dla stron logowania. Obecnie mamy coraz lepsze komputery, a certyfikaty można generować za darmo. Także nowoczesne przeglądarki informują użytkownika, gdy do połączenia używany jest zwykły protokół HTTP.

Nieuważne kopiowanie fragmentów kodu z Internetu

Myślę, że każdy programista spotkał się z portalem Stack Overflow, na którym można znaleźć rozwiązania wielu programistycznych problemów. Trzeba jednak uważnie przyglądać się znalezionym tam rozwiązaniom i zwracać uwagę na fragmenty kodu, które stamtąd kopiujemy. Może się okazać, że skopiujemy fragment z gotowymi kluczami lub tokenami sesyjnymi, które ktoś umieścił w swoim rozwiązaniu. Takie dane nie powinny być ogólnodostępne.

Słabo zabezpieczone dane w bazie danych

Tak naprawdę wiążę się to z punktem wymienionym wyżej. Dane wrażliwe również powinny być w miarę możliwości odpowiednio zabezpieczone w bazie. Dodatkowo należy zwrócić uwagę na to, jakich algorytmów używamy do haszowania oraz szyfrowania. Warto zrobić research, czy nasze wartości hasz nie są za krótkie, a algorytmy do szyfrowania nie mają znanych, łatwych do odtworzenia podatności. 

Błędna konfiguracja ciasteczek

Bardziej szczegółowy opis na temat bezpieczeństwa i prawidłowej konfiguracji ciasteczek można znaleźć wyżej. Jednak nie bez powodu temat ten został opisany w kolejnej grupie. Ciasteczka mogą nie tylko służyć do przechowywania sesji użytkownika, ale mogą również zawierać dane wrażliwe, które atakujący jest w stanie przechwycić np. w wyniku ataku XSS.

Insecure Direct Object Reference (IDOR)

Jest to podatność umożliwiająca napastnikowi dostęp do zasobu strony, do którego nie powinien mieć dostępu bez specjalnych uprawnień. Wyobraźmy sobie taką sytuację. Mamy portal, w którym użytkownicy przechowują pliki. Strona prowadząca do dokumentów użytkownika wygląda w następujący sposób:

https://example-docs-page.com/documents?user_id=1234

Pod takim adresem dostajemy dokumenty dla użytkownika o ID 1234. A co by się stało, gdybyśmy spróbowali zmienić ten parametr np. na wartość 1235? Adres strony wyglądałby wtedy w taki sposób:

https://example-docs-page.com/documents?user_id=1235

W przypadku wystąpienia podatności IDOR dostalibyśmy dokumenty przypisane właśnie takiemu użytkownikowi. Potencjalny atakujący w przypadku odkrycia takiej podatności mógłby posłużyć się skryptem i spróbować dostać się do zasobów wielu innych użytkowników.

To tylko kilka błędów związanych z tą grupą podatności. Zainteresowanych kolejny raz odsyłam do materiałów udostępnionych przez OWASP, które można znaleźć na stronie OWASP.

Podsumowanie

Mogłoby się wydawać, że nie można napisać wiele na temat tych dwóch grup podatności, jednak jak widać, wektorów ataku jest sporo. Wycieki haseł, wrażliwych danych personalnych mogą być powodem utraty dobrego wizerunku firmy, spadkiem zaufania klientów, a czasem nawet bankructwa. Tak jak wspomniałem wyżej, twórcy serwisów muszą bronić się nie tylko bezpośrednio przed atakującymi, ale czasem także przed nieuwagą użytkowników. Niestety walka jest trochę nierówna, osoby po „jasnej” stronie muszą pamiętać o zabezpieczeniu swojego oprogramowania pod każdym względem. Atakującemu wystarczy tylko jedna podatność, żeby przejąć kontrolę nad aplikacją. Całe szczęście twórcy mają z czego czerpać wiedzę na temat dobrych praktyk m.in. dzięki takim inicjatywom jak OWASP.

Paweł Kaleciński  – Full Stack Developer w Onwelo. Zainteresowany nowinkami technicznymi, bezpieczeństwem aplikacji internetowych i muzyką rockową.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *