Raz na jakiś czas spotykam się z mechanizmem, którego przeznaczenia nie rozumiem. Albo inaczej - nie jestem w stanie zrozumieć, dlaczego ktoś myśli, że ten mechanizm będzie działać. Na przykład ładnych już kilka lat temu w trakcie pracy nad projektem pewnej aplikacji jej dostawca zaproponował, by wprowadzić dodatkową warstwę szyfrowania przy przesyłaniu formularzy na serwer. Połączenie z serwerem miało być oczywiście realizowane standardowo przy pomocy SSL, natomiast jeszcze sama aplikacja po stronie klienta miała szyfrować dane przed ich wysłaniem do serwera. Miało to być zabezpieczenie przed atakiem typu man-in-the-middle. Ostatecznie funkcja ta nie została zaimplementowana, i dobrze.
Lubię psuć
Uzasadnienie tej funkcji było takie, że jeżeli atakujący przeprowadzi atak man-in-the-middle, nadal nie będzie mógł czytać przesyłanych danych. Problem w tym, że założenie to było błędne, a przynajmniej nie było realnej możliwości jego realizacji.
Ubierzmy na chwilę czarny kapelusik z napisem "psuj" i zastanówmy się co jest z tym pomysłem nie tak, dlaczego jest on mało realny.
Pierwsze podstawowe pytanie to w jaki sposób dane będą szyfrowane oraz w jaki sposób ustalony będzie klucz szyfrowania. Przyjmijmy, że:
- szyfrowanie z wykorzystaniem algorytmu AES,
- klucz szyfrowania ustalany przy pomocy jakiegoś key exchange,
Załóżmy chwilowo, że szyfrowanie nie zostało zepsute. Możemy być natomiast prawie pewni, że będzie problem z wymianą klucza. Dlaczego? Dlatego, że w większości wypadków protokoły key exchange są bezpieczne, jeśli ograniczymy atakującego do podglądania ruchu między nadawcą i odbiorcą. Nie radzą sobie jednak z atakami aktywnymi. W efekcie można być praktycznie pewnym, że atakujący będzie w stanie przeprowadzić kolejny atak man-in-the-middle i uzyskać dostęp (czytać, modyfikować) przesyłane do serwera dane.
Oczywiście pojawiła się próba ratowania tego pomysłu. Serwer miał mieć swój klucz publiczny, klient miał generować klucz sesyjny (do szyfru symetrycznego), szyfrować ten klucz z użyciem klucza publicznego serwera i wysyłać go do serwera. Nie trudno zobaczyć, że takie podejście również nie jest bezpieczne, a atak man-in-the-middle nadal był możliwy. Dlaczego? A skąd klient ma wiedzieć, czy klucz publiczny, z którego pomocą szyfruje klucz sesyjny rzeczywiście jest kluczem serwera, a nie kluczem atakującego? Skąd klient ten klucz miał brać?
Na "bezpieczne" dostarczenie klucza publicznego serwera były dwa pomysły. Klucz miał być osadzony w kodzie HTML (lub JavaScript) ewentualnie osadzony miał być jego fingerprint i klient miał weryfikować jego poprawność przed użyciem otrzymanego od serwera klucza. Jest tylko jedno małe "ale". Przecież nad kanałem, którym pliki są przesyłane do klienta atakujący miał całkowitą kontrolę. Mógł dowolnie podmienić klucz lub jego fingerprint i atak na to dodatkowe szyfrowanie był nadal możliwy.
Jednym z ciekawszych pomysłów było użycie klucza publicznego serwera z certyfikatu, którym serwer przedstawił się przeglądarce. Załóżmy przez chwilę, że istnieje techniczna możliwość dostępu do tego klucza z poziomu JavaScript (jednym z kluczowych założeń w projektowaniu tej aplikacji był brak wymagania dodatkowych komponentów typu plugin czy ActiveX). Czy to rozwiązałoby problem? Oczywiście, że nie. Przypominam, że atakujący wykonał już skuteczny atak man-in-the-middle na SSL, w przeglądarce ofiary widać klucz publiczny atakującego, do którego atakujący oczywiście posiada stosowny klucz prywatny.
Kilka tego typu iteracji pokazało wyraźnie, że ten dodatkowy mechanizm, gdyby był wprowadzony, nie stanowiłby żadnej przeszkody dla atakującego. Przynajmniej patrząc na całość z czysto technicznej perspektywy. Perspektyw (punktów widzenia) jest jednak więcej.
Można wyróżnić dwie grupy ataków (nie mówię, że to wszystkie możliwe typy, ale te typy są istotne w tym kontekście):
- atak masowy,
- atak targetowany,
W pierwszym przypadku atakującym zależy na osiągnięciu ich celu. Na przykład ich celem jest ukraść pieniądze, zdecydowanie mniej istotne jest z ich punktu widzenia to, komu te pieniądze ukradną. Oczywiście chcą osiągnąć jak największe ROI, więc będą atakować w taki sposób, by skuteczność ataku była jak największa. Z czystej statystyki wynika więc, że dobrym celem ataku są klienci tych banków, które klientów mają najwięcej. Po prostu wówczas istnieje największa szansa, że:
- wśród potencjalnych ofiar znajdzie się spora liczba klientów takich banków,
- część z tych potencjalnych ofiar rzeczywiście uda się okraść,
Załóżmy (przykładowo, nie należy zwracać uwagi na konkretne wartości), że atak ma 10% skuteczności. To znaczy, że jeśli ofiara rzeczywiście jest klientem banku X, to mamy 10% szans na to, że uda nam się wyprowadzić pieniądze z konta. Załóżmy też, że wartość ta jest taka sama dla każdego banku (nie jest, ale w tej chwili tak załóżmy). Jeśli wśród potencjalnych ofiar znajdzie się 100 klientów banku X i 3 klientów banku Y, to łatwo policzyć, że na sukces w przypadku banku Y nie bardzo jest co liczyć. Skoro tak, to nie ma sensu przygotowywać ataku na bank Y, bo chyba, że nie różni się on w istotny sposób od ataku na bank X (klasyczny przykład na to, że monokultura jest zła). W takim scenariuszu opisany wcześniej mechanizm dodatkowego szyfrowania komunikacji z serwerem, może być przydatny. To coś na zasadzie ucieczki przed niedźwiedziem. Nie musimy być szybsi niż niedźwiedź, musimy być szybsi, niż najwolniejsza osoba z grupy :)
W przypadku ataków targetowanych motywacje atakującego są inne. Chodzi mu o osiągnięcie konkretnego celu i o konkretną ofiarę, lub grupę ofiar. W tym scenariuszu wartość opisanego zabezpieczenia dodatkowego, jest bliska zeru.
Na końcu warto podjąć racjonalną decyzję, czy implementacja takiej dodatkowej funkcji jest uzasadniona. Może lepiej przeznaczyć te pieniądze na inny mechanizm bezpieczeństwa, który będzie skuteczniejszy? A może to dodatkowe zabezpieczenie jest budowane w miejscu, którego atakujący i tak by nie atakował?