Ponieważ zainteresowanie drugą częścią zadania (Bootcamp IIa: ciąg dalszy zadania) spadło, a ja mam kilka minut czasu, opiszę o co w nim chodziło. Jest to prawie rozwiązanie zadania. Prawie, bo rozwiązanie zadania dostępne jest w formie wizualnej: Bootcamp: lesson02a - hint/spoiler.
Bootcamp IIa: rozwiązanie (prawie)
Zadanie dotyczyło kontroli dostępu, ale jego główną częścią była kryptografia. Bardzo źle użyta kryptografia. Najpierw szybkie wyjaśnienie - co kryptografia robi przy kontroli dostępu.
Wszystko, co przekracza granicę zaufania trzeba traktować jako niezaufane, ponieważ może zostać zmodyfikowane. W szczególności fakt, że użytkownikowi wypisaliśmy pewne rzeczy na stronie, na przykład konkretne identyfikatory obiektów, wcale nie znaczy, że tylko takie identyfikatory użytkownik do nas prześle. Jednym z pomysłów na rozwiązanie/ograniczenie tego problemu jest szyfrowanie. Pomysł ten oparty jest na założeniu, że użytkownik nie jest w stanie zmodyfikować zaszyfrowanych danych. Jest to założenie błędne, ponieważ szyfrowanie, co do zasady, nie chroni integralności danych (patrz też: Authenticated encryption).
Można wyróżnić dwa podstawowe typy szyfrów: szyfry strumieniowe i szyfry blokowe. Różnica jest taka, że szyfr blokowy operuje na blokach (np. na bloku o wielkości 16 bajtów), szyfr strumieniowy - na poszczególnych bajtach (w zasadzie to nawet na bitach). W rezultacie modyfikacja jednego bajtu w tekście tajnym (zaszyfrowanym) powoduje:
- modyfikację jednego bajtu w tekście jawnym w przypadku szyfru strumieniowego,
- modyfikację co najmniej całego bloku w tekście jawnym w przypadku szyfru blokowego,
W obu przypadkach możliwe jest generowanie "w ciemno" pewnych danych, przepuszczanie ich przez czarną skrzynkę (proces deszyfrowania) i oczekiwanie, że za którymś razem skonstruuje się taką wiadomość, że po rozszyfrowaniu da ona oczekiwany tekst jawny. To jest na pewno wykonalne, jedyna kwestia - jak długo to zajmie. Pod tym względem szyfry blokowe i szyfry strumieniowe różnią się dość znacznie...
W przykładzie wykorzystany został szyfr strumieniowy. Konkretnie RC4, choć to akurat nie jest szczególnie kluczowa informacja. Szyfry strumieniowe mają jeszcze jedną ważną cechę - bardzo trudno jest je wykorzystać poprawnie. Wielokrotnie przekonał się o tym Microsoft i było to na tyle bolesne, że szyfry strumieniowe trafiły na czarną listę: Banned Crypto and the SDL.
Szyfrowanie w algorytmie RC4 opiera się na operacji XOR tekstu jawnego z kluczem (konkretnie ze strumieniem pseudolosowych bajtów generowanych przez algorytm na podstawie klucza). Dokładnie tak samo działa odszyfrowanie. XOR (właściwie One-Time Pad) jest szyfrem idealnym, pod warunkiem, że zostanie poprawnie wykorzystany. W szczególności jednym z problemów jest Known-plaintext attack. Czy w tym przypadku dysponujemy tekstem jawnym? Tak, zawartość parametru id jest wypisywana w kodzie strony w komentarzu. Efektywnie oznacza to tyle: koniec zabawy.
Dlaczego tak? Z bardzo prostego powodu:
- tekst tajny przekazywany jest w parametrze id,
- tekst jawny wypisywany jest w kodzie strony,
- wykonując operację XOR między tekstem jawnym i tekstem tajnym uzyskujemy klucz (keystream),
- mając klucz (keystream), możemy zaszyfrować dowolną wiadomość,
W przypadku podpowiedzi przekazałem tekst tajny, który składał się z samych 0x00. Po wykonaniu operacji XOR uzyskałem... klucz (keystream), a konkretnie tak długi jego fragment, jak długi był tekst tajny. Pozostało zaszyfrować to, co akurat miałem ochotę, by serwer sobie odszyfrował...
Całość zadania można było również rozwiązać przy pomocy brute force. Bez większego problemu można było przygotować algorytm, który pozwoliłby przygotować tekst tajny o dowolnej długości po rozszyfrowaniu dający zadany tekst jawny. Algorytm ten wymagałby maksymalnie 256 żądań do serwera, niezależnie od długości tekstu. Ktoś chętny do opisania? :)
wiadomości jest dostępny w postaci jawnej, ale że jest możliwość szyfrowania
tym samym strumieniem klucza(tzn. używając tego samego ziarna) różnych wiadomości,
co jest podstawowym błędem w przypadku szyfrów strumieniowych.
Jeśli będziemy mieli za każdym razem inne ziarno, to w dobrym szyfrze strumieniowym
KPA nie jest możliwe (tzn nie da się przewidzieć strumienia klucza na podstawie
jego fragmentu).
Myślę, że aplikację można poprawić używając np. losowego ziarna (per request),
przechowywanego po stronie serwera i zmapowanego z identyfikatorem (oczywiście potrzeba generatora o
wystarczającej entropii i lepszego algorytmu niż RC4, http://www.ecrypt.eu.org/stream/announcements.html).
W przypadku algorytmu blokowego byłoby (niewiele?) łatwiej. Klucz mógłby być generowany
na sesję, ale przydałby się losowy wektor iv dla każdego identyfikatora generowany per request
(bo użytkownik może stracić uprawnienia do wiadomości).
W obu przypadkach użytkownik ciągle mogły wysłać wybrany szyfrogram identyfikatora, ale
prawdopodobieństwo że trafi byłoby za małe. Ale najlepszy byłby chyba HMAC (klucze generowane per request i trzymane na serwerze).
Kryptografia w powyższym przykładzie została kompletnie źle użyta - mając tekst jawny i postać kryptogramu wystarczy spędzić względny kwant czasu na znalezienie klucza/algorytmu. Ale takie kwiatki niestety często się spotyka
Fajne miejsce do poczytanie, dopiero odkryte a już się podoba! pozdr. D
Jeśli nie zostaną zastosowane dodatkowe mechanizmy (np. MAC), to samo zaszyfrowanie danych nie zabezpiecza ich integralności. Atakujący zawsze może zmodyfikować tekst zaszyfrowany co oczywiście spowoduje również modyfikację tekstu odszyfrowanego. Co więcej - czasami atakujący może modyfikować tekst tajny w taki sposób, że ta modyfikacja ma przewidywalny wpływ na tekst rozszyfrowany (np. w trybach CBC, CTR czy w przypadku szyfrów strumienowych).