Dobra, ten komentarz powinien być w sumie jako pierwszy, ale wolałem się najpierw upewnić co do kilku rzeczy… Opiszę, jak to faktycznie działa i co z tego wynika (Google może wiedzieć, kto kiedy odwiedził wybraną stronę/strony, nawet bez stosowania jakichś wyuzdanych heurystyk czy dodatkowych informacji - moja początkowa ocena sytuacji była zdecydowanie zbyt ostrożna).
Twój opis stanowi duże uproszczenie, na tyle duże, że jest w zasadzie błędny. Stąd doszedłeś do błędnego wniosku, że “N jest duże” (przypuszczam, że Twój tok rozumowania był następujący: 32-bitowy prefiks pozwala zakodować “tylko” 2^32 = 4294967296 różnych stanów; jest to duża liczba, ale URLi w całym Internecie jest jeszcze więcej, stąd można założyć istnienie kolizji, tzn. identycznych 32-bitowych prefiksów sumy SHA256 wyliczonych z zupełnie różnych URLi; stąd rzekoma niemożność Google’a do ustalenia, jaki adres został odwiedzony).
Zwróć uwagę, że gdyby to działało dokładnie tak, jak opisałeś, to nie byłoby możliwe np. wygodne zablokowanie całej domeny (trzeba by było enumeratywnie określić wszystkie strony (URLe) w tej domenie, co nie byłoby zbyt wygodne, wydajne ani skuteczne…). Dlatego cały ten mechanizm jest bardziej skomplikowany.
(Szczegóły i nazwy plików/metod/itp. są podane dla źródeł FF3.0, FF3.0.1, FF3.0.2 i FF3.0.3, prawdopodobnie też późniejszych…)
Podczas regularnych połączeń z Google są ściągane (i zapisywane w bazie w pliku urlclassifier3.sqlite w katalogu profilu) 4-bajtowe prefiksy hashy SHA256, zgadza się, ale niekoniecznie jeden prefiks per blokowana strona.
W tabeli moz_classifier są dwie kolumny (pola każdego rekordu) z częściowymi hashami: domain i partial_data. W przypadku, kiedy Google chce zablokować całą domenę, jest przysyłany tylko hash dla kolumny domain, a partial_data pozostaje puste. Przeglądarka podczas wejścia na dowolną stronę wylicza hash z domeny (a dokładniej - z dwóch i trzech jej głównych składowych, szczegóły w pliku nsUrlClassifierDBService.cpp w metodzie nsUrlClassifierDBServiceWorker::DoLookup()) i sprawdza, czy 4-bajtowy prefiks jest w lokalnej bazie danych (w kolumnie domain). Jeśli jest (pomijam tu dodatkowe mniej istotne warunki) - wysyła żądanie o pełny hash SHA256, wyliczony z domeny, przeglądarka go odbiera i porównuje z tym wyliczonym. Jeśli są identyczne - pokazuje zamiast strony odpowiedni komunikat (ostrzeżenie o phishingu lub malware; który pokazać decyduje wartość table_id dla danego rekordu).
Z kolei jeśli Google chce zablokować (lub monitorować odwiedziny) tylko część strony (tzn. URLa) lub dokładny cały URL, wtedy, oprócz kolumny domain, zaczyna grać rolę także kolumna partial_data: przesłane wcześniej dane wypełniają dwoma prefiksami hashy obie kolumny: domain i partial_data. Początek tego co się dzieje, jest taki sam, jak przy pustym partial_data, co opisałem wyżej, ale w przypadku pasującego prefiksu domeny jest jeszcze dodatkowo sprawdzany i porównywany z wartością partial_data prefiks hasha wyliczony z całego oraz częściowego URLa (co dokładnie oznacza “częściowego”, nie będę się tutaj rozpisywał; przeczytaj specyfikację albo, nawet lepiej, źródła: kod metody nsUrlClassifierDBServiceWorker::GetLookupFragments(); dość powiedzieć, że maksymalnie możliwych jest kilkadziesiąt kombinacji(*), co pozwala na dużą elastyczność w zakresie możliwości określenia tego, co dokładnie zablokować). Jeśli prefiks z partial_data jest identyczny z prefiksem któregoś z wyliczonych hashy, to jest wysyłane (pomijam tu dodatkowe mniej istotne warunki) żądanie po cały hash i jeśli pełne hashe (ten pasujący wyliczony i ten właśnie odebrany) są identyczne, to przeglądarka pokazuje zamiast strony ostrzeżenie.
Tyle, jeśli chodzi o działanie tego ustrojstwa. Teraz czas na wnioski.
Najważniejszy jest taki, że w drugim przypadku (pełne domain i partial_data) jest ekstremalnie niskie prawdopodobieństwo (praktycznie równe 0), żeby wystąpiło żądanie o pełny hash z powodu odwiedzin innej strony niż ta określona przez dostawcę danych (tzn. Google). Dlaczego? Albowiem żądanie o pełny hash występuje tylko wtedy, kiedy są spełnione dwa (w dodatku powiązane ze sobą) warunki: pasujący prefiks hasha domeny i równocześnie pasujący prefiks hasha całego URLa (lub jego fragmentu). Skutek jest taki, że Google może wiedzieć, jaka strona była odwiedzona. Dlatego właśnie FF wysyła więcej niż jeden hash przy żądaniach o pełny hash — tyle tylko, że, jak wspomniałem w bodajże moim pierwszym komentarzu, dodatkowe hashe nie są w pełni losowo wybierane i serwer może określić, który to hash faktycznie spowodował wysłanie żądania o pełne hashe.
Zdanie z Twojego opisu: “(…) hashowanie jest procesem nieodwracalnym, tj. z URL-a można zrobić hash, ale z hasha nie można zrobić URL-a” jest prawdziwe dla nas, ale nie dla Google. Dlaczego? Albowiem my faktycznie nie mamy możliwości przekształcenia wszystkich prefiksów w URLe/domeny (chyba, że zastosujemy metodę brute-force, co z oczywistych względów (np. ma ktoś listę wszystkich domen i URLi w Internecie?) jest praktycznie niewykonalne). Z drugiej strony serwer (Google) najpierw musiał wyliczyć te prefiksy (bo on je w końcu wcześniej przysłał), a zatem mógł zachować też odwzorowanie “prefiks” -> “URL/domena”, co pozwala mu - po otrzymaniu prefiksu - znaleźć właściwy URL/domenę. (I nie, nie ma tutaj wieloznaczności, albowiem prefiksu z żądania o pełny hash nie należy traktować jako coś, co wybiera któryś z N prefiksów hashy z wszystkich URLi w Internecie, ale raczej jako indeks w posiadanej przez serwer tabeli z przypisaniami “prefiks” -> “URL”, gdzie prefiksy to prefiksy wysłane wcześniej przeglądarce.)
Drugi istotny wniosek (który wynika nawet z Twojego uproszczonego opisu) jest taki, że wysłanie żądania o pełny hash wcale nie musi wiązać się z pokazywaniem ostrzeżenia użytkownikowi - przeglądarka podejmuje decyzję o pokazaniu bądź nie tego ostrzeżenia już po wysłaniu żądania o pełny hash i odebraniu odpowiedzi. A serwer może odpowiedzieć jakkolwiek; w szczególności może zwrócić hashe nie pasujące do żądanego albo nie zwrócić nawet żadnych pełnych hashy, skutkiem czego strona się normalnie wczyta, pełny hash nie zostanie w cache’u i użytkownik nie będzie miał pojęcia, że przed momentem miała miejsce komunikacja z serwerami Google. Specyfikacja nawet wprost przewiduje taki przypadek (pusta odpowiedź i kod 204) — uzasadnia to tym, że w międzyczasie (tzn. między ostatnią aktualizacją lokalnej bazy i wysłaniem żądania o pełny hash) mógł zostać skasowany dany prefiks (prefiksy) w bazie utrzymywanej po stronie serwera.
Biorąc pod uwagę wszystko powyższe trzeba przyznać, że Google jest naprawdę genialne (co nie powinno dziwić, bo oni są znani z zatrudniania dużej ilości naprawdę łebskich ludzi, których odpowiednio motywują do pracy) — udało im się zaimplementować potencjalny spyware w popularnej przeglądarce open-source i tylko od nich zależy, czy i w jaki sposób to wykorzystają (tzn. jakimi danymi “nakarmią” Firefoksa). Dodatkowo mają też oczywiście możliwość dowolnego blokowania dowolnych stron (dowolnym użytkownikom, bo mogą teoretycznie wysyłać zupełnie różne dane różnym użytkownikom(**)).
Warto też nadmienić, że w aktualnej wersji FF3 opisane sprawdzanie hashy (i ewentualne wysyłanie żądania o pełne hashe w razie dopasowania) jest przeprowadzane dla każdego odwiedzanego adresu, ale jest planowane także uruchamianie tego mechanizmu dodatkowo dla każdego zasobu w odwiedzonej stronie (tj. np. dołączonych skryptów JavaScript, plików CSS, osadzonych aplikacji we Flashu, być może też obrazków itd. itp.). Propozycja wyszła od Google, ustawiona jest już w Bugzilli flaga “blocking-firefox3.1+”, więc pewnie będzie tak to funkcjonowało w następnej stabilnej gałęzi FF.(***) (Patche w bugu 453723 wprowadzają pewne optymalizacje, które mają spowodować mniejszą liczbę wykonywanych operacji dla wcześniej sprawdzonych, “czystych” adresów; można to traktować jako przygotowania do rozwiązania kwestii sprawdzania wszystkich zasobów ze strony…)
Mam nadzieję, że cały powyższy wywód przekonał Cię co do tego, że Mozilla słusznie pisze, że Google ma (stosunkowo prostą) możliwość znalezienia odwiedzanych URLi na podstawie wysłanego prefiksu hasha. Pytaj, jeśli coś jest niejasne.
Na zakończenie pozwolę sobie, po raz ostatni, na uwagę odnośnie Twojego tekstu: w świetle powyższego Twój opis jest mocno niekompletny. Dlatego, jeśli już tak się bronisz przed wycofaniem IMO wątpliwego tekstu “Firefox wysyła dane o użytkownikach do Google - nieprawda”, to chociaż popraw (albo nakieruj czytelnika na ten komentarz) tekst dotyczący opisu działania tego całego ustrojstwa.
Pozdrawiam (jakaś wersja tego teksu (docelowo mocno poszerzona) ukaże się też na mojej stronie).
—
Przypisy:
(*) Ilość sprawdzanych kombinacji zależy od tego, z ilu komponentów składa się URL. Im więcej poddomen i katalogów w ścieżce, tym więcej będzie sprawdzanych kombinacji. Np. duża ilość kombinacji byłaby dla przykładowego URLa: poddomena1.poddomena2.poddomena3.poddomena4.poddomena.domena.com/sciezka/z/wieloma/kata/logami/plik?param=wart, a mała (właściwie tylko cały URL) dla np.: domena.com/plik.html.
(**) Google może stuprocentowo pewnie określić użytkownika (profil przeglądarki) na podstawie unikalnego identyfikatora w ciasteczku, które jest domyślnie przesyłane zarówno z żądaniami o aktualizację lokalnej bazy, jak i z żądaniami o pełny hash.
(***) W Google Chrome prawdopodobnie już tak jest teraz - w źródłach Chrome są pliki związane ze sprytnym mechanizmem o nazwie “bloom filter”, o którym wspomniał też Ian Fette w swoim komentarzu w Bugzilli.
15 Comments (Follow with RSS)