Limitowanie klienta

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:

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.

Oryginał tego wpisu dostępny jest pod adresem Limitowanie klienta

Autor: Paweł Goleń