Częstym problemem jest zabezpieczenie aplikacji/systemu przed klientem, który chce z tego systemu, lub jakiejś jego funkcji, korzystać zbyt często. Potrzeba takiego ograniczenia może wynikać z różnych przyczyn. Może to być na przykład zabezpieczenie przed masową enumeracją danych, ale równie dobrze powód może być bardziej trywialny - ochrona przed wyczerpaniem zasobów. Może byc na przykład tak, że "zwykłe" wyszukiwanie jest kosztowne dla serwera i złośliwy klient może wykorzystać ten fakt do wykonania ataku DoS.
Jak się przed taką sytuacją bronić? Jednym z pomysłów może być uczynienie wywołania takiej metody dużo bardziej kosztownym dla klienta, niż jej obsługa dla serwera. W jaki sposób to zrobić? Na przykład wykorzystując kryptografię.
Zacznijmy od kryptografii asymetrycznej i algorytmu RSA. A konkretnie od wydajności tego algorytmu dla różnych długości kluczy:
sign verify sign/s verify/s
rsa 512 bits 0.000333s 0.000030s 3002.4 33883.0
rsa 1024 bits 0.001794s 0.000092s 557.6 10869.0
rsa 2048 bits 0.011064s 0.000330s 90.4 3033.8
rsa 4096 bits 0.079932s 0.001323s 12.5 755.9
Jak widać koszt operacji podpisu jest wyraźnie wyższy, niż weryfikacji tego podpisu. Limitowanie klienta może odbywać się więc przez wymaganie podpisu RSA przy wywołaniu danej funkcji. Oczywiście przy każdym wywołaniu podpis musi być inny, tak by za każdym razem klient musiał składać go na nowo.
Takie rozwiązanie jednak ma również pewne wady. Chodzi między innymi o to, że asymetryczna jest nie tylko praca przy podpisie/weryfikacji podpisu, ale również liczba klientów i serwerów. Klientów jest zwykle więcej, więc ten mechanizm może być bronią obusieczną. Działa jeśli atakujący jest jeden. Jeśli atakujących jest wielu, mogą oni wykorzystać to zabezpieczenie przed DoS do wykonania (D)DoS.
Można spróbować też nieco innego podejścia. Inspiracją może być algorytm wymiany klucza pod wiele mówiącą nazwą Merkle's Puzzles.
W naszym przypadku serwer może przygotować paczkę, na przykład 100 puzzli, w sposób następujący:
- wylosuj seed,
- wylosuj liczbę r z pewnego zakresu,
- wylicz key jako hmac(seed, r),
- wylicz puzzle jako hash(key),
Po stronie serwera zapisana jest tabela z indeksem i kluczem, do klienta przesyłana jest natomiast paczka z indeksem, seed i puzzle.
Każde wywołanie metody z serwera klient musi "podpisać" HMAC przy pomocy klucza otrzymanego od serwera. Każde wywołanie musi być podpisane innym kluczem (klucz raz użyty jest unieważniany). Podpis (MAC) może obejmować np. serializowane parametry wywołania metody. Jeśli jest nieprawidłowy, serwer odrzuca wywołanie już na etapie wstępnego przetwarzania, zanim zacznie wykonywać po swojej stronie "kosztowny" kod.
Skąd klient będzie miał klucz? Musi rozwiązać puzzle. Rozwiązanie nie jest trudne, wystarczy przejść przez wszystkie możliwe wartości r, powtórzyć sposób generowania klucza wykonywany przez serwer, wykonać funkcją hash na otrzymanym kluczu i jeśli zgadza się z puzzle, klient znalazł klucz i może wywołać metodę serwera.
Serwer może dynamicznie dostosowywać ilość pracy, którą musi wykonać klient. Jest ona uzależniona od zakresu, z jakiego pochodzi r. Jeśli klient rozwiązuje puzzle zbyt szybko, serwer po prostu zwiększa ten zakres w kolejnej paczce puzzli wysłanych do klienta. Przy okazji warto zauważyć, że zarówno operacja hmac i hash są z założenia operacjami mało kosztownymi i nie obciążają serwera w sposób istotny. Obciążenie po stronie klienta wynika natomiast z ilości powtórzeń, które musi on wykonać w celu ustalenia prawidłowego klucza.
- sleep kosztuje serwer zasoby,
- sleep nie kosztuje klienta nic (poza drobnym opóźnieniem między złożeniem zapytania a otrzymaniem odpowiedzi),
W przypadku DDoS to chyba tylko mozna probowac dogadac sie z operatorem.
Przykładowo może być tak, że operacja uwierzytelnienia wymaga odwołania do kilku systemów (np. realizowanych przez SOA), pobrania sporej ilości danych (np. historii operacji), albo wielu operacji na bazie danych (np. aktualizacja różnych rekordów). Czasami okazuje się, że te operacje powiązane są z wąskim gardłem w systemie i nawet JEDEN atakujący jest w stanie doprowadzić do jego zablokowania. Albo inaczej - wąskim gardłem jest np. szyna SOA, a te proste operacje w łatwy sposób mogą doprowadzić do jej przeciążenia i zablokowania lub poważnej degradacji wydajności WSZYSTKICH systemów z niej korzystających, również tych kompletnie bez związku z atakowaną aplikacją i niewystawionych do sieci.
W takiej sytuacji load balancer nic nie da, bo sam serwer wyrabia, nie wyrabia natomiast środowisko, z którego aplikacja korzysta i niespecjalnie jest co w tym przypadku balansować. Również WAF pomoże tutaj w ograniczonym stopniu. Może próbować limitować ilość żądań od jednego klienta, ale w tym wypadku (hipotetyczna kosztowna operacja uwierzytelnienia) klient nie jest uwierzytelniony, więc może to co najwyżej robić po IP, a zmiana tego IP przez atakującego nie jest żadnym problemem.
Limitowanie "globalne" (czyli maksymalna ilość dopuszczalnych żądań od WSZYSTKICH użytkowników) również nie jest dobrym rozwiązaniem. Co prawda prawdopodobnie rozwiąże to problem wąskiego gardła, ale stwarza kolejny potencjalny sposób ataku DoS - wygenerowanie dużej liczby żądań przez co "prawidłowe" żądania od "uczciwych" klientów nie są przetwarzane przez serwer. Broniąc się przed zatkaniem wąskiego gardła zostało stworzone kolejne wąskie gardło, które po prostu zapycha się wcześniej.
Jeśli natomiast w jakiś sposób zwiększymy koszt wywołania takiej metody, atak będzie trudniejszy do przeprowadzenia. Po prostu atakujący do jego przeprowadzenia będzie potrzebował większych zasobów. Będzie oczywiście w stanie je zdobyć, ale stopień skomplikowania ataku wtedy rośnie.
Znasz może jakąś przykładową implementację?
Pozdrawiam
Tomek
Jeśli chodzi o wykorzystanie takiego podejścia jako ochronę przed spamem, to mam wątpliwości. Na pewno spowodowałoby to znaczne zwiększenie "kosztu" wysłania jednej wiadomości przez co zmniejszyłoby opłacalność tego procederu. Poza tym ktoś musiałby te boty zaprogramować do obsługi tych "puzzli". Nie jest to nie do zrobienia, ale komuś musiałoby się chcieć to zrobić. Może rzeczywiście na jakiś czas miałoby to szansę działać. Ale co z botami, które "korzystają" z przeglądarki? Wracamy do punktu wyjścia - jedynym kosztem w tym wypadku jest czas potrzebny na rozwiązanie puzzla.
Natknałem się na kilka implementacji, które wstawiały różnego rodzaju znaczniki/wymuszały obliczenia via JavaScript (+obsfucusowały kod za to odpowiedzialny) i z tego co piszą były dość skuteczne.
Nie wiem jak z takim botem który "korzysta" z przeglądarki, ale równie dobrze można reCAPTCHA'ę podłożyć człowiekowi do rozwiązania.
Myślę, że dopóki są "łatwe" cele ataku, to bot odpuści, chyba, że wartością jest tak jak w przypadku Joggera PageRank 5 strony.
Hmm, masz gdzieś ten kod wystawiony?