Subscribe RSS

Polskie blogi specjalistów IT / Microsoft

agregator blogów
  • O usłudze
  • ziembor.pl/blog/
  • Gdzie szukam?
    • wss.pl
    • ITBlogs
    • Jogger Techblog
    • dobreprogramy
  • Inne agreagatory
    • zine.net.pl/TechBlogs
    • itblogs.pl/agregat/

Author: Paweł Goleń


XOR i co z niego wynika

2012-05-15 Posted by Paweł Goleń under Polskie blogi IT

Dziś dla odmiany coś na temat kryptografii. Konkretnie trochę o XOR, jego własnościach i co z tych własności wynika.

Najpierw o XOR

Najpierw bardzo szybkie i raczej oczywiste w swojej treści wprowadzenie do tego, co to jest xor i jak działa. Co to jest XOR? To operacja, którą najlepiej w języku potocznym opisać jako albo to, albo to. W przypadku operowania na bitach wygląda to tak:

A   B   A xor B
---------------
1   1   0
1   0   1
0   1   1
0   0   0

Ponadto operacja XOR:

  • jest przemienna: A xor B == B xor A
  • jest łączna: (A xor B) xor C == A xor (B xor C)
  • istnieje element neutralny: A xor 0 == A
  • istnieje element odwrotny: A xor A == 0

Załóżmy teraz, że wykorzystujemy operację XOR do szyfrowania (np. one-time pad), to z tych własności wynika między innymi to, że:

  • jeśli znamy wiadomość i mamy jej postać zaszyfrowaną, możemy odzyskać klucz,
  • możemy modyfikować zaszyfrowaną wiadomość lub jej fragment,
  • jeśli ten sam klucz zostanie wykorzystany do zaszyfrowania wielu wiadomości, jesteśmy w stanie go odtworzyć,

W zasadzie wszystkie stwierdzenia powinny być oczywiste. Ale na wszelki wypadek popatrzmy z czego to wynika. W pierwszym przypadku mamy następującą sytuację:

m xor k = c
m xor k = c | xor m
(m xor k) xor m = c xor m
(m xor m) xor k = c xor m
0 xor k = c xor m
k = c xor m

Jest to oczywiste, ale na wszelki wypadek wyjaśniam. Dysponujemy na wejściu m oraz c, naszym celem jest odzyskanie klucza k. Wartość c uzyskuje się poprzez operację m xor k. Jeśli obie strony poddamy operacji xor m, otrzymamy k.

Drugą sytuację można przedstawić w sposób następujący:

m1 xor k = c1
c2 = c1 xor m2
c2 xor k = (c1 xor m2) xor k = ((m1 xor k) xor m2) xor k = k xor k xor m1 xor m2 = 0 xor m1 xor m2 = m1 xor m2

Przekładając to na język bardziej zrozumiały, mamy następującą sytuację: jeśli na zaszyfrowanej przy pomocy XOR wiadomości (tu c1 wykonamy operację XOR z pewną wiadomością m2 i w ten sposób uzyskamy c2, to c2 po rozszyfrowaniu będzie miało wartość m1 xor m2.

Operując na liczbach załóżmy, że:

m1 = 4
k = 9
c = m1 xor k = 4 xor 9 = 13

Co zrobić, by uzyskać wartość c1 taką, by po wykonaniu operacji xor k uzyskać m2 o wartości 6. Zakładamy oczywiście, że nie znamy wartości k i nie chcemy jej wyliczać (choć możemy to zrobić w trywialny sposób, skoro znamy wartości m1 oraz c).

Można to zrobić w sposób następujący:

m2 = 6
m3 = m2 xor m1
c1 = c xor m3
c1 = c xor m3 = m1 xor k xor m3 = m1 xor m1 xor m2 xor k = 0 xor m2 xor k = m2 xor k
c1 xor k = m2 xor k xor k = m2

Czyli wystarczy najpierw wykonać operację XOR między m1 i m2, czyli między aktualnie „zaszyfrowaną” wiadomością i wiadomością, którą chcemy uzyskać po rozszyfrowaniu, a następnie wykonać operację xor między zaszyfrowanym tekstem c.

Na potwierdzenie:

In [41]: (13 ^ (4 ^ 6)) ^ 9
Out[41]: 6

Co z ostatnią sytuacją, czyli możliwością ustalenia klucza który został wykorzystany do zaszyfrowania wielu wiadomości? Nie znamy żadnej z tych wiadomości, ale możemy zakładać, że mają one określoną strukturę, w szczególności, że np. mogą zawierać tylko określony zakres znaków. Jak możemy odzyskać klucz?

Posłużmy się tutaj następującym przykładem. Załóżmy, że dysponujemy następującymi wartościami:

[2, 1, 0, 7, 6, 5, 4, 11, 10]

Wiemy o nich tyle, że powstały one w wyniku operacji i xor k i to, że i należy do przedziału [1;9]. W takim przypadku nie pozostaje nic innego, jak sprawdzić, jakie są możliwe wartości k, które przeprowadzą kolejne elementy powyższego zbioru do tego przedziału. Ponieważ nic nie wiemy na temat k, poszukajmy go w następujący sposób:

c = i xor k
k = c xor i

Czyli znając wartość c przechodzimy przez wszystkie dopuszczalne wartości i (prawidłowe wartości po rozszyfrowaniu) i znajdujemy klucze, które do takich wartości prowadzą:

In [48]: [2^i for i in range(1,10)]
Out[48]: [3, 0, 1, 6, 7, 4, 5, 10, 11]

In [49]: [1^i for i in range(1,10)]
Out[49]: [0, 3, 2, 5, 4, 7, 6, 9, 8]

In [50]: [7^i for i in range(1,10)]
Out[50]: [6, 5, 4, 3, 2, 1, 0, 15, 14]

In [57]: [6^i for i in range(1,10)]
Out[57]: [7, 4, 5, 2, 3, 0, 1, 14, 15]

(...)

Jak widać na tych przykładach ilość dopuszczalnych wartości k jest ograniczona. Ponieważ wszystkie wartości zostały „zaszyfrowane” z użyciem jednego klucza (k), musi on występować we wszystkich przypadkach. Tu na przykładach warunek ten spełniają tylko następujące wartości:

[0, 3, 4, 5]

Po sprawdzeniu pozostałych wartości z „przechwyconego” zbioru (w tym przykładzie sprawdziłem tylko 2, 1, 7 i 6), ten zbiór zostałby zawężony prawdopodobnie do tylko jednego elementu.

I co z tego wynika

A teraz trochę odnośnie tego co z powyższych własności XOR może wynikać. Trochę jako ciekawostka, a trochę jako przykład odnośnie tego, dlaczego nie należy używać samego szyfrowania.

Można przypuszczać, że jeśli atakujący zobaczy zaszyfrowaną wiadomość c, nie będzie w stanie wygenerować wiadomości c1, która po rozszyfrowaniu będzie różniła się od D(c) w oczekiwany przez niego sposób. To rzeczywiści jest prawdą w niektórych przypadkach. Tu pokażę dla przykładu dwa przypadki, w których prawdą to nie jest. Konkretnie pokaże takie sytuacje, w których atakujący, który zna m i c = E(k, m) będzie w stanie wygenerować c1 takie, że m1 = D(k, m1) będzie różniło się od m w oczekiwany przez atakującego sposób.

Jeśli ktoś będzie twierdził, że takie przypadki nie występują w naturze, spieszę donieść, że jak najbardziej – występują. Coraz częściej szyfrowanie jest wykorzystywane w celu zapewnienia integralności danych i w wielu przypadkach takie rozwiązanie kompletnie nie sprawdza się w takim zastosowaniu.

Modyfikacja pierwszego bloku w trybie CBC

Jednym z trybów pracy szyfrów blokowych jest tryb Cipher-block chaining (CBC).

W tym przypadku pierwszy blok jest szyfrowany z użyciem wektora IV. Każdy kolejny blok w roli wektora IV wykorzystuje zaszyfrowaną postać poprzedniego bloku. Wektor IV jest wysyłany razem z wiadomością.

Samo szyfrowanie wygląda w ten sposób, że każdy blok m wiadomości jest najpierw poddawany operacji XOR z postacią zaszyfrowaną poprzedniego bloku (lub z IV w przypadku pierwszego bloku), a następnie szyfrowany. W przypadku rozszyfrowania jest odwrotnie. Najpierw blok jest odszyfrowywany, a następnie wykonywana jest operacja XOR. W wyniku otrzymywana jest postać jawna zaszyfrowanego tekstu.

Problem z tym podejściem jest taki, że atakujący może zmodyfikować w trywialny sposób pierwszy blok tekstu jawnego nie wpływając przy tym w żaden sposób na rezultat deszyfrowania kolejnych bloków. Jak może to zrobić? Wystarczy, że zmodyfikuje wektor IV w taki sam sposób, jak pokazywałem to dla zwykłego OTP.

Przykład (z wykorzystaniem PyCrypto):

In [63]: key = os.urandom(16)

In [64]: iv = os.urandom(16)

In [67]: aes = AES.new(key, AES.MODE_CBC, iv)

In [68]: m = "0000000000000000"

In [69]: c = aes.encrypt(m)

In [70]: iv.encode('hex')
Out[70]: '91457e3364034d7eb2648ee720cccd5b'

In [71]: c.encode('hex')
Out[71]: 'f0cf50bf2f748996e6f047313643c660'

W ramach ćwiczenia zmieńmy w takim razie IV w taki sposób, by wiadomość c po rozszyfrowaniu zaczynała się od słów TEST, a pozostała część niech pozostanie bez zmian.

XOR między początkiem wiadomości m i tą wartością, którą chcemy uzyskać, jest następujący:

In [78]: [ord(i)^ord(j) for i,j in zip("0000","TEST")]
Out[78]: [100, 117, 99, 100]

Wygenerujmy nowy IV:

In [92]: x = [100, 117, 99, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
In [93]: iv = '91457e3364034d7eb2648ee720cccd5b'.decode('hex')

In [94]: "".join([chr(i^ord(j)) for i,j in zip(x,iv)]).encode('hex')
Out[94]: 'f5301d5764034d7eb2648ee720cccd5b'

I teraz sprawdźmy, czy poszło dobrze:

In [118]: aes = AES.new(key, AES.MODE_CBC, 'f5301d5764034d7eb2648ee720cccd5b'.decode('hex'))

In [119]: aes.decrypt('f0cf50bf2f748996e6f047313643c660'.decode('hex'))
Out[119]: 'TEST000000000000'

Jak widać po rozszyfrowaniu otrzymany został tekst zmodyfikowany, zgodnie z moimi oczekiwaniami. To po raz kolejny dowód na to, że szyfrowanie nie zapewnia integralności danych, chyba, że użyta została Authenticated Encryption.

Tryb CTR

Jeśli ktoś chciałby zamiast trybu CBC wykorzystać tryb CTR, musi przygotować się na jeszcze większą niespodziankę. W tym przypadku musi być świadomy tego, że:

  • atakujący może dowolnie zmodyfikować treść zaszyfrowanej wiadomości,
  • może odszyfrować wiadomości, jeśli nonce jest wykorzystany wielokrotnie,

Dlaczego jest to możliwe? Wynika to z trybu pracy tego szyfru. Działa on w oparciu o klucz, nonce oraz licznik. Szyfrowanie wygląda w ten sposób, że dla kolejnych bloków tekstu jawnego liczony jest keystream jako E(klucz, nonce || licznik), a następnie jest wykonywana operacja XOR z tekstem jawnym. Rozszyfrowanie wygląda w dokładnie ten sam sposób.

Mamy więc tak naprawdę sytuację, w której szyfrowanie wygląda tak:

ks xor m

Gdzie ks jest generowany przez szyfr blokowy, a m jest wiadomością. Czyli praktycznie dokładnie to samo, co w przypadku omawianego na samym początku przypadku z XOR.

By taki tryb był bezpieczny nonce musi być unikalny dla każdej wiadomości. Wtedy generowany ks jest również unikalny dla wiadomości i w związku z tym odszyfrowanie wiadomości po zebraniu ich większej ilości nie będzie możliwe. Możliwa będzie natomiast wciąż modyfikacja tekstu tajnego w taki sposób, by po rozszyfrowaniu różnił się od oryginalnego tekstu w sposób oczekiwany przez atakującego. Kolejny przykład/dowód na to, że samo szyfrowanie nie wystarcza do zagwarantowania integralności danych.

Potencjalne XSSy w ASP.NET

2012-05-12 Posted by Paweł Goleń under Polskie blogi IT

Ten temat po części już poruszałem we wpisie Niekonsekwencje w ASP.NET, ale postanowiłem do niego wrócić. Część rzeczy się powtórzy, więc ewentualne uczucie Deja vu jest uzasadnione. Chodzi o to, w jakich przypadkach ASP.NET sam zastosuje odpowiedni encoding, a w jakim programista musi sam o to zadbać.

Tym razem sprawdziłem zachowanie ASP.NET dla następujących kontrolek:

  • Button
  • CheckBox
  • DropDownList
  • HiddenField
  • HyperLink
  • Image
  • ImageButton
  • Label
  • LinkButton
  • ListBox
  • Literal
  • TextBox
  • RadioButton
  • Table (i pośrednio TableRow i TableCell)

Ograniczę się tutaj do pokazania przykładów, w których mamy do czynienia z potencjalnym XSS. Nie twierdzę, że przetestowałem każdy potencjalny scenariusz użycia tych kontrolek, kilka podstawowych scenariuszy to rzucenie okiem jednak chyba objęło.

Kontrolki

Najpierw kilka przypadków, w których wymienione kontrolki mają potencjalny problem z XSS.

Control.ID

Pierwszy potencjalny XSS to Control.ID (i oczywiście inne kontrolki, które po Control dziedziczą). Wygląda to tak (najpierw w kodzie strony):

Button1.ID = value;

A potem w generowanym HTML (fragment):

<input type="submit" name="pmq'"<>" value="pmq&#39;&quot;&lt;>" id="pmq'"<>"

Jak widać wartości atrybutów name oraz id, które są w ten sposób ustawiane, nie są prawidłowo encodowane. Skutkiem jest XSS.

W tym przypadku sytuacja jest o tyle śmieszna, że zgodnie z dokumentacją ta wartość jest nieprawidłowa:

Only combinations of alphanumeric characters and the underscore character ( _ ) are valid values for this property. Including spaces or other invalid characters will cause an ASP.NET page parser error.

AttributeCollection.Add

Ten problem występuje w przypadku wielu elementów/kontrolek. Jeśli dodajemy atrybuty korzystając z funkcji Add, to mamy potencjalny problem:

public void Add(
	string key,
	string value
)

Jak się okazuje, wartość value jest kodowana automatycznie, wartość key już nie. Właściwie w tym miejscu trudno mówić o tym, że nazwa atrybutu powinna być kodowana, lepszym rozwiązaniem byłoby chyba odrzucanie nieprawidłowych nazw. Ponownie fragmenty kodu:

Button1.Attributes.Clear();
Button1.Attributes.Add(value, "foo");
Button1.Attributes.Add("foo", value);

I wygenerowany kod HTML (fragment):

pmq'"<>="foo" foo="pmq&#39;&quot;&lt;>" />

Moim zdaniem takie zachowanie należy uznać za błąd w ASP.NET. W specyfikacji HTML istnieją ograniczenia odnośnie tego, jakie nazwy są prawidłowe dla atrybutów, w kodzie powinno być więc sprawdzane, czy przekazana w parametrze key wartość spełnia te reguły, a jeśli nie – nie powinna ona być wykorzystana.

CheckBox.ID oraz CheckBox.Text i RadioButton.ID oraz RadioButton.Text

W tym przypadku problem ujawnia się w następującym fragmencie wygenerowanego kodu HTML:

<label for="pmq'"<>">pmq'"<></label>

W tym przypadku chodzi o dwie rzeczy. Po pierwsze kodowana nie jest wartość atrybutu for, po drugie wartość label (która przychodzi z CheckBox.Text) nie jest kodowana. W efekcie dwa dodatkowe potencjalne miejsca na XSS.

Pierwsze miejsce, atrybut for jest, moim zdaniem, ewidentnym błędem w ASP.NET, który wynika z wcześniej pokazywanego problemu z atrybutami id oraz name. W przypadku tagu label w atrybucie for podaje się identyfikator/nazwę tagu, dla którego ten opis jest przeznaczony. Prezentowany tutaj payload nie jest właściwą wartością id/name, więc nie powinna się w tym kontekście pojawić. A jeśli już, to powinna być zakodowana w sposób właściwy dla kontekstu, czyli dla atrybutu HTML.

Drugie miejsce to wartość (treść) opisu (labela) dla kontrolki. Jest ona wypisywana bez encodingu. Można dyskutować, czy to dobrze, czy źle. Teoretycznie ktoś mógłby chcieć wypisać w tym miejscu HTML po to, by konkretny label rzucał się bardziej w oczy. Z drugiej strony pojawia się niekonsekwencja w zachowaniu w porównaniu choćby do TextBox.Text, gdzie dane w(y)pisywane w ten sposób są kodowane w sposób prawidłowy dla kontekstu i siłą rzeczy XSS nie ma.

Identycznie jak CheckBox zachowuje się również RadioButton.

HyperLink.Target

To też jest sprawa, o której pisałem poprzednio. Jeśli z poziomu kodu ustawimy atrybut target w sposób następujący:

HyperLink1.Target = value;

To możemy skończyć z następującym fragmentem kodu HTML:

 target="pmq'"<>">

Dane są wypisywane w kontekście atrybutu i nie są one kodowane w sposób prawidłowy. Szczerze mówiąc nie rozumiem dlaczego akurat jest to zrobione w ten sposób. Z mojego punktu widzenia jest to błąd w ASP.NET.

HyperLink.NavigateUrl

W tym przypadku dane wypisywane są z zastosowaniem odpowiedniego encodingu, ale jeśli atakujący kontroluje całość tego parametru, może on wstawić tam na przykład taką wartość:

javascript:alert('xss');

Tu jednak trudno mówić o błędzie w samym ASP.NET, jest to sprawa, o której pamiętać powinien programista. Jeśli w takiej kontrolce wypisujemy dane pochodzące od użytkownika, trzeba bardzo dokładnie sprawdzać, czy są to dane dozwolone. Nie jest to zadanie trywialne, z góry ostrzegam.

Label.Text, LinkButton.Text, HyperLink.Text, LinkButton.Text, Literal.Text

We wszystkich wymienionych tutaj przypadkach dane w(y)pisane przez Text pojawiają się w kontekście HTML bez encodingu.

Można dyskutować, czy jest to błąd, czy raczej daje to programiście elastyczność w zakresie tego, co będzie w stanie wypisać. Moim zdaniem w przypadku kontrolek:

  • Label
  • LinkButton
  • HyperLink
  • LinkButton

można byłoby z powodzeniem stosować domyślnie właściwe kodowanie.

Kontrolka Literal natomiast z powodzeniem mogłaby być wykorzystywana w sytuacji, w której ktoś musiałby wypisać bezpośrednio jakiś kod HTML. Ta kontrolka poza tym, co ma ustawione w Text nie wypisuje do HTML nic innego. Tu domyślny brak encodingu jestem w stanie zrozumieć.

Table.Caption, TableCell.Text

W tym przypadku również dane wypisywane są w kontekście HTML bez jakiegokolwiek encodingu. Znów można dyskutować, czy jest to zachowanie pożądane, czy raczej domyślnie powinien być stosowany encoding.

Table.BackImageUrl

Na koniec ciekawostka. Istnieje możliwość ustawienia obrazu tła dla tabeli. W kodzie HTML wygląda to tak (tym razem payload wyglądał następująco:

pmq'"<>()

Kod wstawiający tło:

Table1.BackImageUrl = value;

I wynikowy kod HTML:

style="background-image:url(pmq&#39;&quot;&lt;>());">

Co to powoduje? Można modyfikować tą drogą styl tabeli, a i być może jeszcze jakiś wektor na XSS przez CSS zadziała.

Co z ValidateRequest?

Jak wiadomo istnieje coś takiego, jak ValidateRequest. Jest to podejście oparte na czarnej liście, sprawdza czy po znaku < nie pojawia się litera, lub jeden z kilku innych zakazanych znaków. W efekcie nawet jeśli dane są wypisywane w kontekście HTML bez encodingu, to prawdopodobnie ValidateRequest powstrzyma atak.

Z wymienionych wcześniej miejsc, ValidateRequest prawdopodobnie (prawdopodobnie, bo są sytuacje, w których ValidateRequest można obejść) powstrzyma XSS w przypadku:

  • CheckBox.Text
  • RadioButton.Text
  • Label.Text
  • LinkButton.Text
  • HyperLink.Text
  • LinkButton.Text
  • Literal.Text
  • Table.Caption
  • TableCell.Text

Mechanizm ValidateRequest jest jednak kompletnie bezradny/bezużyteczny w przypadkach:

  • Control.ID
  • AttributeCollection.Add
  • HyperLink.Target
  • HyperLink.NavigateUrl
  • Table.BackImageUrl

Jak się bronić

Ponownie odsyłam do Microsoft Web Protection Library i funkcji:

  • Encoder.HtmlAttributeEncode
  • Encoder.HtmlEncode
  • Sanitizer.GetSafeHtmlFragment

Jeśli wypisujesz dane przez Kontrolka.Text możesz wykorzystać Sanitizer.GetSafeHtmlFragment. Nawet jeśli znajdzie się tam kod użytkownika, to zostanie on oczyszczony z potencjalnie niebezpiecznych fragmentów. Lepszym rozwiązaniem jest użycie Encoder.HtmlEncode, ale wówczas należy pamiętać, by nie używać tej funkcji w przypadku, gdy Kontrolka automatycznie realizuje pożądany encoding.

Jeśli korzystasz z:

  • HyperLink.Target

Wykorzystaj Encoder.HtmlAttributeEncode.

Jeśli korzystasz z:

  • Control.ID
  • AttributeCollection.Add

Sprawdź czy:

  • wartość ID jest zgodna ze specyfikacją HTML (dopuszczalna wartość atrybutów id i name),
  • wartość key jest zgodna ze specyfikacją HTML (dopuszczalne nazwy atrybutów),

Jeśli korzystasz z:

  • HyperLink.NavigateUrl
  • Table.BackImageUrl

To potencjalnie masz problem. Zastanów się, czy można dokładnie walidować poprawność danych, które przekazuje użytkownik.

Czy to wszystko

Nie, to nie wszystko. Pokazałem tu tylko kilka specyficznych przypadków, w których w aplikacji ASP.NET może pojawić się XSS. Nie są to oczywiście wszystkie możliwe przypadki.

Dodatkowo chciałem po raz kolejny pokazać, że ASP.NET jest do pewnego stopnia niekonsekwentne, w części przypadków właściwy encoding jest realizowany automatycznie, w innych – musi o niego zadbać programista. Problem w tym, że nie zawsze wiadomo, z którym przypadkiem mamy do czynienia.

Lubię psuć

2012-05-04 Posted by Paweł Goleń under Polskie blogi IT

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.

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ł?

Nie wszystko złoto

2012-04-25 Posted by Paweł Goleń under Polskie blogi IT

Dziś będzie krótko. Na początek odsyłam do tekstu, do którego chcę się odnieść: A czy Twój bank jest odporny na ataki UI Redressing – Clickjacking? A teraz konkretniej – ja z UI Redressing mam pewien problem. To jest doskonała podatność do pokazywania na konferencjach, szkoleniach, prezentacjach. Jest bardzo efektowna, ale już z jej efektywnością bywa różnie. Tekst, do którego odsyłam, dotyczy bankowości internetowych. Obawiam się, że w tym konkretnym przypadku z efektywnością (czyli skutecznością) tego typu ataku byłoby bardzo słabo.

Istotą UI Redressing jest przekonanie użytkownika, że robi coś innego, niż w rzeczywistości robi. Możliwość nałożenia jednej strony na drugą pozwala w rezultacie „wykorzystać” użytkownika do tego, by wykonał on oczekiwane przez atakującego operacje. Kilka ciekawych przykładów można znaleźć choćby u Krzyśka, na przykład Exploiting the unexploitable XSS with clickjacking albo Cross domain content extraction with fake captcha. Warunkiem powodzenia takiego ataku jest użytkownik, który robi to, czego oczekuje od niego atakujący. I tu zaczynają się schody.

W przypadku bankowości internetowych każda istotna operacja powinna wymagać autoryzacji. Wynika to choćby z zasady defence-in-depth. Zakładamy, że atakujący może uzyskać dostęp do konta użytkownika (zdobycie danych uwierzytelniających, przejęcie sesji) i staramy się zastosować takie zabezpieczenia, by skutki takiego zdarzenia były ograniczone do ujawnienia informacji. Do tego należy dodać również pewne rozwiązania wprowadzane dla wygody użytkownika, jak choćby przelewy/szablony zaufane, albo brak wymagania autoryzacji operacji wewnętrznych (przelew między rachunkami, założenie lokaty). Każda inna operacja powinna wymagać autoryzacji, a autoryzacja nie powinna być możliwa „przypadkiem”. Użytkownik powinien wiedzieć, że autoryzuje operację oraz powinien wiedzieć dokładnie jaką operację autoryzuje. Mechanizmy techniczne muszą być uzupełnione edukacją użytkowników, a użytkownik nie powinien być zaskakiwany działaniem mechanizmu bezpieczeństwa (tak: Principle of least astonishment).

Ile operacji trzeba wykonać, by dokonać przelewu? Dla uproszczenia niech będzie to przelew krajowy. W większości banków, które widziałem, trzeba:

  • określić rachunek źródłowy,
  • wpisać rachunek docelowy,
  • określić kwotę przelewu,
  • podać dane odbiorcy przelewu,
  • podać tytuł przelewu,
  • potwierdzić przelew,

Przypominam, że jesteśmy jeszcze przed autoryzacją, a aby znaleźć się w tym miejscu, musieliśmy trafić na wyjątkowo skłonnego do współpracy użytkownika. Pozostaje go tylko przekonać do tego, by przepisał kod otrzymany w SMS, albo na przykład podał wskazanie tokenu. Nie jest to całkowicie niemożliwe, ale zbliżamy się niebezpiecznie do poziomu albańskiego wirusa. Jeśli trafiliśmy na użytkownika tak skłonnego do współpracy, to najprawdopodobniej Clickjacking nie jest wcale potrzebny.

Można rozważać kolejne funkcje w różnych bankowościach i zastanawiać się, którą z nich być może użytkownik wykonałby nieświadomie. Będę się jednak upierał, że by atak tego typu zadziałał musi być jednocześnie spełnionych bardzo wiele warunków. Prawdopodobieństwo zaistnienia ich wszystkich jednocześnie jest niewielkie. Jeśli natomiast użytkownik jest wyjątkowo podatny na social engineering, to może po prostu trzeba go wystarczająco ładnie poprosić, by zrobił to, czego od niego oczekujemy? Na przykład tak: Police Themed Ransomware Continues. Niezłe skutki odnoszą również ogłoszenia o „aktualizacji” numerów kont bankowych do opłat za czynsz, wodę, (…). Słyszałem nawet o fraudach, w których przesyłane były aktualizacje numeru kont komornika, na które miały być kierowane zajęte środki. Gra warta świeczki, jeśli uwzględnić, że zdarzają się zajęcia opiewające na wiele milionów złotych…

Podatności należy postrzegać w odpowiednim kontekście, choćby po to, by określić z jakim ryzykiem się wiążą. Jeśli podatność istnieje (i o niej wiemy) to powinniśmy zwracać uwagę na dwie rzeczy:

  • potencjalny skutek wykorzystania podatności,
  • prawdopodobieństwo wykorzystania podatności,

Prawdopodobieństwo wykorzystania podatności zależy między innymi od tego, jak łatwo ją wykorzystać. Jeśli do skutecznego wykorzystania podatności wymagane jest spełnienie wielu warunków, to prawdopodobieństwo jej wykorzystania jest niewielkie. W rezultacie niewielkie jest również ryzyko z nią związane, nawet jeśli potencjalnie skutki wykorzystania podatności mogą być duże.

Warto zauważyć, że określony typ podatności w różnych systemach/aplikacjach może charakteryzować się różnym stopniem trudności wykorzystania. Kradnięcie lajków to jedno, wykorzystanie UI Redressing do wyprowadzenia pieniędzy z konta w bankowości internetowej, to drugie.

P.S. Oczywiście uważam, że w ramach wspominanej już zasady defence-in-depth nagłówek X-FRAME-OPTIONS powinien być wykorzystany. Chcę natomiast pokazać, że nie zawsze brak tego zabezpieczenia to od razu tragedia i koniec świata.

Co zrobić z wyjątkiem?

2012-04-18 Posted by Paweł Goleń under Polskie blogi IT

Wdałem się ostatnio w nieco dziwną dyskusję odnośnie tego, czy każdy wyjątek występujący w aplikacji powinien być obsłużony. Dla uściślenia chodzi mi tutaj o aplikacje webowe i wyjątki występujące w wyniku manipulacji przez użytkownika przekazywanymi danymi.

Moim zdaniem obsługiwanie każdego wyjątku na siłę po to, by było „ładnie” nie jest dobrym pomysłem. Na przykłada dlatego, że do obsługi takiego wyjątku potrzeba jakiegoś kodu, a im więcej linii kodu, tym większe prawdopodobieństwo wystąpienia błędu. Jeszcze gorsze rzeczy mogą się dziać, jeśli kod obsługi wyjątku będzie usiłował „naprawić” sytuację wyjątkową i kontynuować wykonanie kodu (i to nawet wówczas, gdy nie ma to większego sensu). W takich przypadkach często jest lepiej po prostu przerwać wykonanie kodu, a nieobsłużony wyjątek robi to zwykle dość skutecznie.

Oczywiście można też patrzeć na sprawę w inny sposób. Co się stanie, jeśli wyjątek wystąpi w trakcie jakiejś operacji? Być może jakiś obiekt/proces/cokolwiek zostanie wówczas w osieroconym stanie i być może będzie można to w jakiś sposób wykorzystać. Pomijam tu również sytuację, gdy nieobsłużony wyjątek skutkuje wyświetleniem jakże przydatnych dla atakującego informacji (stacktrace).

Z ciekawości przejrzałem listę CWE na okoliczność wystąpienia słowa exception. Znalazłem na przykład takie elementy:

  • CWE-248: Uncaught Exception
  • CWE-396: Declaration of Catch for Generic Exception
  • CWE-397: Declaration of Throws for Generic Exception
  • CWE-460: Improper Cleanup on Thrown Exception
  • CWE-600: Uncaught Exception in Servlet

Pierwszy przypadek, CWE-248: Uncaught Exception, zdaje się kończyć dowód. Faktycznie, nieobsłużony wyjątek może doprowadzić do Denial of Service. Jeśli jednak ten nieobsłużony wyjątek znajduje się w kodzie uruchamianym dla konkretnego użytkownika i skutek tego wyjątku dotyczy tylko tego użytkownika, który go wywołał, nie jest to jeszcze tragedia. Zwłaszcza jeśli sam jest sobie winny.

Tu, by jeszcze bardziej zagmatwać sprawę, dość typowa sytuacja. Często w przypadku środowisk zwracających uwagę na typy (ASP.NET, J2EE) próba manipulacji parametrami numerycznymi kończy się wyjątkiem wynikającym z braku możliwości konwersji przekazanej wartości na liczbę. Czy jest to walidacja danych wejściowych? Czy błąd tego typu powinien być obsłużony? Czy powinien być obsłużony, jeśli po stronie przeglądarki są stosowne ograniczenia dla wprowadzanych danych? Albo jeśli określony parametr nie jest przewidziany do modyfikacji dla użytkownika? Pozostawiam do przemyślenia…

Jeśli chodzi o CWE-600: Uncaught Exception in Servlet, to jest to dokładnie to, o czym pisałem:

When a Servlet throws an exception, the default error response the Servlet container sends back to the user typically includes debugging information. This information is of great value to an attacker. For example, a stack trace might show the attacker a malformed SQL query string, the type of database being used, and the version of the application container. This information enables the attacker to target known vulnerabilities in these components.

A to przy okazji się łapie na CWE-7: J2EE Misconfiguration: Missing Custom Error
Page
(lub analogiczne dla innych środowisk).

CWE-460: Improper Cleanup on Thrown Exception mówi natomiast dokładnie o tym, o co mi chodzi:

The product does not clean up its state or incorrectly cleans up its state when an exception is thrown, leading to unexpected state or control flow.

oraz:

Often, when functions or loops become complicated, some level of cleanup in the beginning to the end is needed. Often, since exceptions can disturb the flow of the code, one can leave a code block in a bad state.

Doprowadzenie do takiego „nieokreślonego” stanu jest łatwiejsze, jeśli wyjątki są podnoszone i obsługiwane w bardzo „szeroki” sposób, czyli w sytuacjach, o których mówią CWE-396: Declaration of Catch for Generic Exception oraz CWE-396: Declaration of Throws for Generic Exception.

Jaki z tego morał? Moim zdaniem taki, że nie należy automatycznie uznawać, że każdy nieobsłużony wyjątek to błąd, który powinien być poprawiony. A jakie jest Wasze zdanie?

Za wrażenia artystyczne punktów brak

2012-04-13 Posted by Paweł Goleń under Polskie blogi IT

Zacznę od stwierdzenia, które powtarzam tak często, jak tylko mam okazję. Bezpieczeństwo nie jest celem samym w sobie, bezpieczeństwo musi czemuś służyć. To „coś” to niekoniecznie jest dobre samopoczucie bezpieczników. Celem aplikacji jest realizowanie jej celów biznesowych z zachowaniem akceptowalnego poziomu ryzyka. W wielu miejscach pewne rzeczy można zrobić lepiej. Widziałem ważne mechanizmy bezpieczeństwa, które są zaimplementowane źle. Widziałem pozorne mechanizmy bezpieczeństwa (ach ten security theater) zaimplementowane źle, widziałem też kompletnie nieprzydatne mechanizmy bezpieczeństwa (rozwiązujące nie ten problem, co trzeba), które akurat zostały zaimplementowane dobrze…

Najpiękniejsze rozwiązania techniczne (jeśli ktoś jest na tyle dziwny, by dostrzegać piękno w takich rzeczach) nie wpływa znacząco na ryzyko, jeśli rozwiązuje nie ten problem, co trzeba. Fort Eben-Emael może i był „piękny”, podobnie jak linia Linia Maginota, ale oba zabezpieczenia się nie sprawdziły. Może dlatego, że generałowie przygotowują swoje armie do wojny, która już była. Zmienił się sposób prowadzenia wojny, a wróg zachował się nie tak, jak powinien. Jak mógł…

Niestety, podobne tendencje można zauważyć również w przypadku bezpieczeństwa IT. Wciąż pewne „uświęcone tradycją” rozwiązania są uporczywie stosowane, choć świat się zmienił. Mógłbym tu po raz kolejny wspomnieć o klawiaturach wirtualnych lub hasłach maskowanych. Innym przykładem mogą być kanapkowe firewalle (najlepiej trzy, koniecznie różnych firm) na wejściu do sieci i jedna wielka płaska sieć wewnątrz. Podział „dobrzy my” i „źli oni” już od dawna jest niewystarczający.

Obrońcy mają być skuteczni. Mogą być toporni, ale mają być skuteczni. To samo tyczy się atakujących. Im zależy na osiągnięciu określonych celów, a nie na łamaniu każdego napotkanego zabezpieczenia, zwłaszcza, jeśli jego pokonanie nie jest konieczne do osiągnięcia ich celów.

Podobna sytuacja jest w przypadku testów bezpieczeństwa. Testy bezpieczeństwa to nie jest security research. Jeśli największe ryzyko jest związane z topornymi podatnościami (SQLi, błędy kontroli dostępu, zarządzanie sesją, CSRF), to trzeba zacisnąć zęby i skupić się w pierwszej kolejności na tym. To nie znaczy, że innych rzeczy nie należy zauważać/odnotować, ale trzeba umieć wybrać co w danym przypadku może być istotniejsze. Tak, jak czasem trzeba zastosować Triage i, niestety, oznaczyć kogoś jako Lost.

Opowiem pewną historię. Testowałem kiedyś aplikację i tak jak jedni widzą białe myszki czy różowe słonie, ja widziałem wszędzie padding oracle (polecam: CBC padding attacks). Miejsca, w których szyfrowanie było wykorzystane sugerowały, że być może w ten sposób chronione jest coś, czym można manipulować. Mniej dla zabawy, bardziej dla zysku. Oczywiście jak zwykle w takich sytuacjach okazało się, że standardowe narzędzia (np. PadBuster) niespecjalnie się w tym przypadku sprawdzają. Nie pozostało więc innego, niż zakasać rękawy, chwycić węża w łapy i napisać własne narzędzie, prawda?

Niezupełnie. Rozwiązanie problemu z padding oracle jest trywialne – trzeba dodać MAC. Temat zaparkowałem, choć kolejne przypadki skrzętnie odnotowywałem. Z czasem dowiedziałem się, że klucze i IV są statyczne i wspólne dla wszystkich użytkowników. Na podstawie tej wiedzy mogłem testować też kontrolę dostępu – po prostu wykorzystywałem zaszyfrowaną postać istotnych parametrów. Odroczona gratyfikacja i tym razem okazała się przynosić zyski – znalazłem miejsce, w którym nie musiałem się bawić w padding oracle, zamiast tego aplikacja grzecznie szyfrowała i odszyfrowywała to, o co ją poprosiłem.

Zupełnie inaczej przedstawia się sytuacja, w której potencjalnie słabe szyfrowanie jest istotnym mechanizmem bezpieczeństwa i jego pokonanie pozwala atakującemu osiągnąć właściwie wszystkie jego cele. Wówczas tematu nie można tak łatwo parkować, chyba, że jest jeszcze kilka innych sposobów osiągnięcia tych samych celów, potencjalnie w łatwiejszy sposób.

Skoro już jestem przy takich tematach, warto wspomnieć o prezentacji Building a cost-benefit model for application security testing Pawła Krawczyka. Kravietz przedstawił tu podejście (pewien model), z którym w znacznym stopniu się zgadzam. Tak jak nie każdą aplikację warto testować z taką samą uwagą, tak i nie każdy scenariusz ataku jest jednakowo ważny.

Tu miałem napisać o pewnym modelu/eksperymencie, nad którym jakiś czas temu spędziłem kilka chwil, ale po namyśle odłożyłem sobie to na później.

Drugi tekst, o którym chcę wspomnieć to Whats the point of application pen testing? Ten akurat pozostawię bez komentarza. Ale za to z cytatem:

Just as you can’t „test quality in” to a system, you can’t „test security in” either. It’s not possible to exhaustively pen test a large system — it would take too long and it wouldn’t be cost effective to even try.

I tym optymistycznym akcentem kończę.

Jak solić hasła

2012-04-03 Posted by Paweł Goleń under Polskie blogi IT

Dziś po TKonferencji jedna osoba pytała się mnie, jak bezpiecznie przechowywać salt użyty przy hashowaniu hasła w bazie. Odpowiedź brzmi – salta nie trzeba przechowywać bezpiecznie, ponieważ jest jawny. Albo inaczej – dostęp do bazy danych trzeba ograniczać (co jest oczywiste), jednak sam salt nie wymaga specjalnej ochrony.

Dlaczego hasła nie są przechowywane w formie jawnej? Przecież zgodnie z założeniami nikt do nich się nie dostanie. Chodzi o zasadę defence in depth. Jeśli jednak ktoś w jakiś sposób (a sposobów może być wiele, od sql injection aż po dostęp do „zużytego” dysku, z którego dane nie zostały do końca dobrze usunięte) uzyska dostęp do tej bazy, będzie miał dostęp do haseł. Pierwsza warstwa zabezpieczeń (fosa?) padła, pora na kolejne zabezpieczenie.

W tym miejscu załóżmy, że atakujący ma już dostęp do bazy haseł użytkowników (w tym ich haseł), ale chcemy, by nie mógł jej bezpośrednio wykorzystać. By to osiągnąć stosujemy jakąś funkcję jednokierunkową, która przekształca hasło użytkownika na hash. Często jest to po prostu funkcja skrótu (cryptographic hash function). Funkcja ta jest jednokierunkowa, więc na podstawie hasha nie można odtworzyć hasło hasła, można natomiast próbować wykorzystać różne hasła, może hash któregoś z nich odpowiada temu, który pozyskaliśmy.

Mamy kolejną warstwę zabezpieczeń, ale jest również sposób, by ją pokonać. Potrzeba tylko „trochę” czasu i mocy obliczeniowej. Trzeba przy okazji pamiętać, że funkcje skrótu projektowane są po to, by były szybkie co działa na korzyść atakującego. Atakujący może jednak działać jeszcze skuteczniej. Ponieważ funkcja skrótu jest deterministyczna (dla tego samego wejścia daje takie samo wyjście), atakujący może obliczenia wykonać raz, a z ich wyników korzystać wielokrotnie (patrz: rainbow table).

Dodajmy do tego trochę soli. Jej celem jest zmuszenie atakującego, by obliczenia musiał powtórzyć dla każdego użytkownika z osobna. Jeśli soli nie ma, atakujący liczy hash ze słowa P@$$w0rd, a następnie sprawdza, czy którykolwiek z hashy haseł w bazie ma wartość 6b283bb060c269432d08ac33b47a337c0a40035d. Jeśli natomiast wykorzystana jest sól, dla każdego rekordu musi wyliczyć hash dla testowanego hasła i właściwej (widocznej w bazie) wartości soli. Nie może skorzystać z tablic, bo musiałby przygotować oddzielną wersję tablic dla każdej możliwej wartości soli, co zajęłoby zarówno „trochę” czasu, jak i miejsca. Nadal mamy unikalny salt dla każdego użytkownika, ale by go otrzymać, trzeba uzyskać dostęp lub zgadnąć wartość sekretu.

Sól nie broni nas przed użytkownikiem, który wybiera proste, słownikowe hasła. Jeśli baza haseł wycieknie, istnieje szansa, że jego hasło zostanie złamane. Atakujący może mieć wystarczająco czasu i chęci, by sprawdzić typowe wartości haseł dla każdego użytkownika (a więc i soli) z osobna. By atakującemu utrudnić życie jeszcze bardziej, możemy wybrać taki sposób hashowania haseł, który jest obliczeniowo ciężki (wspominałem już kilkukrotnie o funkcjach typu bcrypt czy scrypt).

Czy można jakoś poradzić sobie z użytkownikami, którzy używają prostych haseł? W tym kontekście o spowodowanie, że w przypadku wycieku bazy ich hasła pozostaną bezpieczne, mimo tego, że są słabe. Do głowy przychodzi mi pewna konstrukcja polegająca na dodaniu do systemu jeszcze jednego sekretu (nazwijmy go mastersalt). Przed hashowaniem hasła na podstawie tego dodatkowego sekretu oraz jawnej i unikalnej dla użytkownika soli, generowany jest nowy salt, który dopiero jest wykorzystywany przy hashowaniu hasła. Atakujący musiałby odgadnąć prawidłowo dwa sekrety, czyli hasło użytkownika i mastersalt, a jeden sekret łatwiej jest chronić. Mimo wszystko zastanawiałbym się, czy gra jest warta świeczki…

Hash Length Extension Attacks

2012-03-31 Posted by Paweł Goleń under Polskie blogi IT

No cóż za zbieg okoliczności. Pojawił się dobry artykuł mówiący o tym, dlaczego należy używać HMAC. Ostatnio o tym wspominałem. Przy okazji akurat dzisiaj na WhiteHat Security Blog wygasł certyfikat.

I z tego wszystkiego aż dodałem kolejne zadanie do mojego wyzwania, trochę zawiązane z tematem.

Trochę o kryptografii

2012-03-29 Posted by Paweł Goleń under Polskie blogi IT

Kryptografia jest ważna, ale to nie jest magic dust, którym wystarczy posypać system, by stał się on automatycznie bezpieczny. Z kryptografii należy korzystać, ale trzeba znać jej ograniczenia. Niestety, w trakcie testów często spotykam się z sytuacją, w której kryptografia jest użyta w sposób nieprawidłowy. Dziś trochę na ten temat.

Szyfrowanie nie gwarantuje integralności i autentyczności danych

Wspominałem o tym problemie kilkukrotnie. Wykorzystałem go między innymi w ramach tego przykładu w bootcamp (patrz też: Bootcamp IIa: rozwiązanie (prawie)), ale błąd ten jest na tyle powszechny, że napiszę to jeszcze raz: szyfrowanie nie chroni integralności danych, a sam fakt poprawnego zaszyfrowania danych nie jest potwierdzeniem źródła ich pochodzenia.

W wielu aplikacjach spotkałem się z próbą wykorzystania kryptografii do uniemożliwienia atakującemu manipulacji pewnymi parametrami, które do tej aplikacji są przekazywane. Ewentualnie jeśli aplikacja składa się z różnych rozłącznych modułów, a dane między nimi przekazywane są za pośrednictwem klienta (np. przez przekierowanie z modułu A do modułu B), to fakt przekazania prawidłowych, zaszyfrowanych danych na wejściu modułu B jest uznawany za potwierdzenie tego, że pochodzą one z modułu A. Problem w tym, że takie założenie jest kompletnie nieuzasadnione.

Pierwszą możliwą ścieżką ataku jest sprawdzenie przez atakującego wszystkich możliwych wartości zaszyfrowanego parametru. Atakujący może liczyć na to, że uda mu się przekazać takie losowe dane, które po rozszyfrowaniu będą sensowne. Umówmy się jednak, że prawdopodobieństwo takiego zdarzenia jest znikome (zakładam, że użyty został właściwy algorytm szyfrowania i został on użyty w sposób prawidłowy).

Ciekawą ścieżką ataku na takie „rozwiązanie” jest również atak padding oracle. Jeśli moduł B będzie informował atakującego (w jakikolwiek sposób), czy dane rozszyfrowały się prawidłowo, czy też przy rozszyfrowaniu napotkano nieprawidłowy padding, atakujący może być w stanie nie tylko rozszyfrować dane przekazywane między modułami, ale również zaszyfrować dowolne, wybrane przez siebie dane i przekazać je do modułu B, który w końcu prawidłowo je rozszyfruje i potraktuje z pełnym zaufaniem, bo naiwnie wierzy, że pochodzą z modułu A.

Można oczywiście mieć nadzieję, że takich błędów nie będzie. Lepiej jednak oprócz szyfrowania zastosować mechanizmy, które integralność i autentyczność danych potwierdzą.

Hash nie jest potwierdzeniem integralności danych

Skoro hash nie jest potwierdzeniem integralności danych, to dlaczego jest do tego wykorzystywany? Dlatego, że hash może służyć do potwierdzenia integralności danych, ale tylko w pewnych specyficznych scenariuszach. Nadaje się on do sprawdzenia, czy plik pobrał się prawidłowo, lub czy nie został on zmodyfikowany od czasu wyliczenia sumy kontrolnej (hasha), w omawianym przypadku jest on jednak niewystarczający.

Wyobraźmy sobie, że mamy do czynienia z ową hipotetyczną aplikacją składającą się z modułów A i B. Moduł A w tej chwili przekazuje do modułu B dane postaci:

sha1(enc(msg))||enc(msg)

Czyli:

  • hash zaszyfrowanej wiadomości (w sensie hash z ciphertext tej wiadomości),
  • zaszyfrowaną wiadomość,

Problem w tym, że to nic nie daje. Moduł B może co prawda sprawdzić, czy hash zaszyfrowana wiadomość odpowiada przesłanemu z nią hashowi, ale atakujący nie ma żadnego problemu w wyliczeniu prawidłowej wartości sha1(enc(msg)). Hash w tym scenariuszu użycia w żaden sposób nie gwarantuje tego, że przesłane między modułem A i B dane nie zostały zmodyfikowane przez atakującego. Ups…

Kiedy hashe mogą być potwierdzeniem integralności? Wtedy, gdy atakujący nie ma możliwości modyfikacji wyliczonych hashy, jeśli na przykład istnieje jakieś publiczne repozytorium informacji o hashach. Wówczas każdy może weryfikować integralność (na przykład integralność plików, czy pakietów oprogramowania), ale jedynie uprawnione osoby mają prawo dodawać/zmieniać elementy w tym repozytorium. To jest jednak inna sytuacja, niż przesłanie danych i hasha mającego potwierdzać ich integralność przez niezaufane środowisko (co pokazywał inny przykład w moim bootcamp).

Jeśli potrzebujesz integralności i autentyczności, użyj MAC

Dobrym rozwiązaniem wspominanego tutaj problemu jest użycie MAC (patrz: Message Authentication Code). W tym przypadku moduł A do modułu B może przesłać komunikat następującej postaci:

MAC(enc(msg))||enc(msg)

Do wyliczenia wartości MAC potrzebny jest oczywiście współdzielony klucz, który musi znać zarówno moduł A, jak i moduł B. Nie zna go natomiast atakujący i o ile nie ma dodatkowych błędów w implementacji (Ataki czasowe na OpenID i OAuth (Twitter, Digg i Wikipedia podatne na atak)), jedyne co może zrobić, to próbować odgadnąć właściwą wartość MAC.

Spotykam się również czasem z następującą konstrukcją:

sha1(enc(msg)||klucz)||enc(msg)

W tym przypadku teoretycznie atakujący nie jest w stanie uzyskać prawidłowej wartości pierwszej części komunikatu, bo nie zna właściwej wartości klucza. Ta konstrukcja nie jest jednak prawidłowa, zamiast niej należy stosować na przykład HMAC (Hash-based Message Authentication Code). HMAC oczywiście nie jest jedynym dostępnym algorytmem (tu więcej informacji), jego niewątpliwą zaletą jest natomiast to, że zwykle jego implementacja jest dostępna w ramach frameworku, z którego użyciem aplikacja jest tworzona.

I w tym miejscu, skoro znamy mechanizm, który potwierdza integralność i autentyczność danych, warto się zastanowić, czy aby na pewno potrzebujemy je jeszcze szyfrować.

Oczywiście do potwierdzenia autentyczności tych danych można wykorzystać również podpisy cyfrowe, nie zawsze jest to jednak niezbędne. A jeśli już używasz podpisów cyfrowych, upewnij się, czy prawidłowo je weryfikujesz i czy przypadkiem nie posyłasz wyników tej weryfikacji do /dev/null…

Zamykam jedno oko i…

2012-03-27 Posted by Paweł Goleń under Polskie blogi IT

Czasami dostaję pytanie, co robię, gdy znajdę jakąś podatność. Oczywiście mowa o sytuacji, kiedy zdarzy mi się coś znaleźć poza działaniami związanymi z realizacją zleceń. W moim przypadku odpowiedź jest prosta – nic. Po prostu „po pracy” nie szukam podatności, a jak coś mi się rzuca w oczy, to po prostu zamykam jedno oko i udaję, że drugim tego nie widzę. Dlaczego?

Odpowiedź na to pytanie również jest dość prosta i można ją przedstawić prostym akronimem CY(O)A. Jeśli organizacja nie ma jasno określonego programu bug bounty, wolę nie sprawdzać organoleptycznie w jaki sposób potraktuje informację o podatności. Wolę nie być później w sytuacji, w której będę musiał udowodnić, że nie jestem wielbłądem.

Można dyskutować nad różnymi aspektami tego podejścia, w szczególności można zarzucać mi „obojętność” oraz narażanie innych na ryzyko przez to, że podatność nie zostanie usunięta. Cóż, powtarzam – jeśli będzie jasna informacja w jaki sposób zgłaszać takie informacje i jak one będą potraktowane, zmienię swoje podejście. Zwracam uwagę, że nie poruszam tu kwestii ewentualnego wynagrodzenia za taką informację, akurat w przypadku zgłaszania rzeczy „zauważonych przy okazji” ma ona drugorzędne znaczenie. Nie mam natomiast ochoty być traktowany jako potencjalny szantażysta, złodziej, mason i cyklista, a z takim podejściem wciąż można spotkać się zdecydowanie zbyt często.

Tu warto zauważyć, że winne są obie strony, często osoby zgłaszające informacje o błędzie robią to w sposób co najmniej dziwny, niejednokrotnie uzależniając przekazanie informacji o znalezisku od otrzymania „nagrody” (bo media na pewno zapłacą za taką informację). Oczekiwania co do wartości tej nagrody bywają wygórowane i nie mają uzasadnienia w rzeczywistej „wadze” znaleziska. Takie podejście naprawdę trudno traktować w sposób inny, niż szantaż. Można też usłyszeć historie o przypadkach, gdy szczęśliwy „odkrywca” przekazuje informacje na temat swojego znaleziska w postaci filmiku nagranego na płytce, którą przekazuje osobiście w nocy na środku mostu. Folklor.

Pomijam tu również kwestię legalności „niezamówionego testu bezpieczeństwa” oraz tego, czy właściciel testowanej w taki sposób aplikacji powinien być wdzięczny, ewentualnie czy jego „pretensje” są uzasadnione. Moje podejście jest takie, że jeśli nie mam zgody, to nie testuję. Kropka.

gxUDhPqqX0QUzzbRQIdIZEHQPT87GYsK

2012-03-21 Posted by Paweł Goleń under Polskie blogi IT

Jeśli zastanawiasz się o co chodzi w tytule tego wpisu, to spieszę wyjaśnić, że o nic. Po prostu nie miałem pomysłu jak go zatytułować, więc wygenerowałem sobie coś takiego :) A to dlatego, że wpis ten nie ma żadnego konkretnego tematu. No dobrze, może i ten tytuł ma pewien ukryty sens, ale to mocno poboczna sprawa.

Dziś chciałem poruszyć krótko kilka tematów. Pierwsza sprawa to mój challenge. Wiem o jednej osobie, która doszła do końca (czyli na dzień dzisiejszy rozwiązała ósme zadanie). Widzę też, że sporo osób utknęło na pierwszym zadaniu. Podpowiem (ponownie), że w odpowiedzi serwera (powtarzam, odpowiedzi serwera, a nie samej treści strony) są dwie podpowiedzi, które według mnie dość dobrze pokazują co i jak należy zrobić. Nie ma potrzeby generować kolejnych wartości ciągu Fibonacciego, raczej trzeba zobaczyć czego brakuje. A jeśli czegoś nie widać, to wcale nie znaczy, że tego czegoś nie ma.

Druga sprawa – na początku kwietnia występuję na TKonferencji. Właśnie jestem po pierwszych przymiarkach polegających na sprawdzeniu ile z tego, co chciałem pierwotnie powiedzieć, dam radę powiedzieć w założonym czasie. Mam trochę więcej przygotowanych materiałów niż będę w stanie wykorzystać, więc prawdopodobnie wcześniej lub później trafią one na bloga.

Pracuję też nad pewnym szkoleniem dotyczącym bezpieczeństwa aplikacji internetowych. W ramach przygotowań popełniłem kilka dziurawych przykładów (wcale nie jest tak prosto napisać dobrze działający dziurawy przykład) i miałem trochę radości w trakcie ich eksploitowania. Wrzucanie meterpretera na serwer linijka po linijce jest zabawne :) Trochę przypomniały mi się bardzo stare czasy. Kiedyś się człowiek namęczył, teraz wystarczy msfvenom.

W temacie tego szkolenia zacząłem szukać jakiegoś rozwiązania, które pozwoliłoby mi (ręcznie) dodawać adnotacje na ekranie. W najbardziej podstawowym wariancie wystarczyłby mi ZoomIt ze swoją funkcją rysowania oraz (przykładowo) GreenShot z automatycznym zapisywaniem screena do pliku w celu zapisania mojej radosnej twórczości dla potomnych, względnie w celu przygotowania materiałów (po)szkoleniowych. Aktualnie szukam jakiegoś wygodnego „urządzenia wskazującego”, którym takie adnotacje mógłbym w miarę wygodnie nanosić (coś w stylu „pióra”), oraz ewentualnie oprogramowania, które tworzenie i zapisywanie takich rysunków pozwoli realizować w sposób wygodniejszy, niż wspomniany duet ZoomIt oraz GreenShot. Ma ktoś może jakieś sugestie? Zarówno w odniesieniu do sprzętu, jak i oprogramowania?

Jakiś czas temu skończyłem czytać Poszukiwany żywy lub martwy. W zasadzie przeczytałem (prawie) wszystkie książki z tej serii (poza Locked On), były lepsze i gorsze momenty. Już Red Rabbit mnie rozczarował, a The Teeth of the Tiger było, moim zdaniem, porażką. Teraz jest trochę lepiej, ale nadal coś jest nie tak… Jakby nie ten klimat. Zmienił się albo Clancy, albo ja.

O moim wyzwaniu

2012-03-13 Posted by Paweł Goleń under Polskie blogi IT

Przynajmniej dwie osoby rozwiązały moje wyzwanie, to znaczy rozwiązały wszystkie pierwotnie opublikowane zadania, nie mam na razie żadnych informacji odnośnie zadania siódmego. Oczywiście zadania rozwiązać mogło więcej osób, mogły się po prostu nie pochwalić :) Feedback jest znikomy, choć raczej pozytywny:

Thanks for the nice challenges, really enjoyed it, especially number 5
was really good. Just solved the last one (…)

Czekam na więcej :)

A teraz zagadka, co przedstawia poniższy obrazek:

Odpowiadam – są to logi z dzisiejszych prób rozwiązania zadania. Dwa różne adresy IP (żółty i zielony) rozwiązują dwa różne zadania. Robią to przy pomocy bruteforce, co niekoniecznie jest najlepszym na to sposobem. Przykładowo zadanie, które próbuje rozwiązać IP żółty można pokonać przy pomocy jednego żądania składającego się z trzech znaków, a wszystkie informacje potrzebne do skonstruowania tego payloadu są zawarte w odpowiedzi serwera.

Zachęcam do dalszego próbowania. Jak na razie obie osoby, które rozwiązały wyzwanie nie pochodzą z terenu naszego pięknego kraju. Do dzieła :)

No i wyknułem

2012-03-10 Posted by Paweł Goleń under Polskie blogi IT

Challenge without an interesting name.

Knuję

2012-03-08 Posted by Paweł Goleń under Polskie blogi IT

Tak, knuję. Mianowicie nad tym, by przygotować pewnego rodzaju challenge. Koncepcja mam taką, by kolejne etapy były udostępniane w ramach zaszyfrowanego kontenera TrueCrypt. By dostać hasło do poziomu n+1 trzeba będzie rozwiązać poziom n. Zadania będą różne, nie tylko aplikacje webowe. Myślę, że zabawa ruszy, jak będę miał gotowych co najmniej 5 zadań.

Uczenie się na pamięć nie jest złe

2012-03-01 Posted by Paweł Goleń under Polskie blogi IT

Rzucił mi się dziś w oczy dość stary felieton, w którym autor „rozprawia się” z narzekaniem nauczycieli/wykładowców na młode pokolenia, że jego przedstawiciele „nic się nie uczą na pamięć, wszystkiego szukają”. Autor wykazywał różnice w dostępności wiedzy kiedyś i teraz, powoływał się również na książkę The Wisdom of Crowds. A ja się z nim nie do końca zgadzam.

Moim zdaniem nie można całkowicie „delegować” wiedzy na zewnątrz. Nie jestem zwolennikiem uczenia się na pamięć w sensie wykucia czegoś bez jednoczesnego zrozumienia tematu. Uważam jednak, że do korzystania z możliwości, które daje nam np. Internet potrzebna jest pewna wiedza bazowa. Wyszukiwanie informacji jest zdecydowanie bardziej efektywne wtedy, gdy wiemy co szukać. A taką „wiedzę bazową” trzeba zapamiętać oraz być w stanie ją przypomnieć, gdy zajdzie taka potrzeba.

No właśnie, uczenie się „na pamięć” miało taką dobrą cechę, że ćwiczyło naszą pamięć właśnie. W efekcie uczenie się, zapamiętywanie i przypominanie sobie informacji, przychodziło nam łatwiej. Bez tych ćwiczeń w pewnym stopniu głupiejemy. Ja na przykład od pewnego czasu korzystam z Anki, bo nie byłem do końca zadowolony ze swojej pamięci. Najbardziej irytowały mnie te sytuacje, kiedy wiedziałem, że coś wiem, ale nie potrafiłem sobie tego przypomnieć. Oczywiście moja podświadomość pracowała i po pewnym czasie tą zagubioną informację odnajdywałem, ale co z tego, jeśli tym, co mi uleciało było jakieś słówko, które było mi potrzebne w aktualnie mówionym zdaniu? Zauważyłem też, że miewam problem z translacją PL -> EN w sytuacjach, w których w drugą stronę (EN -> PL) żadnego problemu nie mam.

Uważam, że ewolucyjnie nie jesteśmy gotowi do funkcjonowania w warunkach takiego zalewu informacji, z jakim mamy obecnie do czynienia. Trzeba pracować nad sobą, nad swoją pamięcią, nad myśleniem (różnymi rodzajami myślenia). Oczywiście, można też pójść na łatwiznę i zawsze pytać wujka Google albo „sieci społecznościowej”, ale dla mnie to nie jest akceptowana opcja.

I tak mi się jeszcze skojarzyło:

A może jednak szyfrować?

2012-02-27 Posted by Paweł Goleń under Polskie blogi IT

Czasami zastanawiam się intensywnie nad sensownością szyfrowania danych, nawet jeśli klucz użyty do szyfrowania tych danych jest potencjalnie łatwy do ustalenia (czyli właściwie bardziej obfuskacja niż szyfrowanie). Już wyjaśniam o co mi chodzi.

Załóżmy, że tworzę serwis podobny w koncepcji do Pastebin. Nie ma on służyć do przekazywania szczególnie istotnych danych, ale chciałbym ograniczyć prawdopodobieństwo masowego ich ujawnienia. Załóżmy, że:

  • w bazie danych przechowywana jest tylko „metryczka” wklejki,
  • same wklejki przechowywane są jako pliki na dysku,

Nagle okazuje się, że z jakiegoś powodu mamy do czynienia z klasycznym głębokim ukryciem i wszystkie pliki zawierające wklejki stają się publicznie dostępne…

Załóżmy teraz, że każdy z tych plików jest szyfrowany, a klucz szyfrowania jest generowany np. w oparciu o:

  • identyfikator pliku,
  • losowy salt przechowywany w metryczce pliku,
  • sekret zawarty w (konfiguracji) aplikacji,

Jeśli ktoś przejmie pełną kontrolę nad serwerem, będzie on oczywiście w stanie odszyfrować każdy plik, jeśli jednak mamy do czynienia wyłącznie z „głębokim ukryciem”, to jego szczęśliwi odkrywcy nie bardzo będą mieli się czym chwalić.

Zastanawiam się też nad podobnym podejściem w przypadku części danych przechowywanych w bazach SQL. Wówczas klucz mógłby być unikalny dla użytkownika, ładowany przy jego uwierzytelnieniu i wykorzystywany przy odczycie/zapisie pewnych danych. Mogłoby to chronić do pewnego stopnia przed skutkami SQLi czy błędami kontroli dostępu – atakujący nie otrzymałby danych innego użytkownika, ponieważ nie dysponowałby odpowiednim kluczem. Co więcej, jeśli byłby wykorzystany tryb szyfrowania z kontrolą integralności (authenticated encryption), w bonusie dostalibyśmy wykrywanie próby dostępu do cudzych danych.

Tu warto też wspomnieć o szyfrowaniu homomorficznym, ale to już trochę wyższa szkoła jazdy.

Asymetria

2012-02-21 Posted by Paweł Goleń under Polskie blogi IT

Jeśli szukacie jakiegoś podcastu do posłuchania, możecie rzucić uchem na ISC Monthly Threat Update – February 2012. Może nie jest on specjalnie porywający, ale warto zwrócić uwagę na temat, który pojawia się (na krótko) mniej więcej między 7:45 i 9:50. Johannes mówi tam o asymetrii między obrońcami i atakującymi.

Jeśli atakujący ma skuteczność na poziomie 1% (na 100 parametrów podatnych na SQLi przegapi 99 z nich), to i tak odniesie sukces. Jeśli natomiast obrońca ma skuteczność na poziomie 99.9% (na 1000 parametrów podatnych na SQLi przegapi 1), to i tak ktoś może znaleźć ten jeden parametr i skutecznie wyprowadzić dane.

Między innymi na temat tej asymetrii (dysproporcji) pisałem już dawno temu: Pentester: doomed to fail?. Ciekawym pomysłem na zmniejszenie tej dysproporcji są programy typu bug bounty. Ludzie i tak szukają błędów, lepiej więc, jeśli znalezione błędy raportują bezpośrednio do dostawcy, a nie „puszczają w obieg”. Dzięki temu firma, która taki program ogłasza, zyskuje za niewielkie pieniądze rzeszę testerów, a w bonusie – pozytywny PR.

Bootcamp II(c|d): hinty

2012-02-18 Posted by Paweł Goleń under Polskie blogi IT

Pora na kilka wskazówek do przykładu z bootcamp. A więc po kolei:

  • nie, nie chodzi o przeszukanie przestrzeni identyfikatorów (jest znany: 2161),
  • jak aplikacja zachowuje się przy próbach SQLi (patrz: Bootcamp #5: Jak szukać SQLi #1, Bootcamp #6: Jak szukać SQLi #2),
  • czy działają operacje arytmetyczne,
  • co jest przekazywane w parametrze p w drugim kroku (przekierowanie), czy można go zmodyfikować,

Dodatkowo:

  • znaku + nie można używać bezpośrednio w wartości parametru (jest traktowany jako spacja),
  • 1=1 nie jest jedyną możliwością (można znaleźć inne wyrażenia, które są zawsze prawdziwe),

Bootcamp II(c|d)

2012-02-14 Posted by Paweł Goleń under Polskie blogi IT

Po dłuższej przerwie kolejne przykłady:

  • http://bootcamp.threats.pl/lesson02c/
  • http://bootcamp.threats.pl/lesson02d/

Zadanie jest proste – odczytać wiadomość o identyfikatorze 2161. W obu przypadkach ta wiadomość jest taka sama. Same przykłady są bardzo podobne do siebie, a cel można osiągnąć na kilka sposobów.

Powodzenia!

P.S. Inspiracją tego przykładu (jego części) jest jedno zdanie wypowiadane w tym filmiku. Które?

Myślenie przeszkadza w myśleniu

2012-02-07 Posted by Paweł Goleń under Polskie blogi IT

Pamiętacie rozważania odnośnie tego, czy pierwsza odpowiedź jest najlepsza? Miałem wówczas poważne wątpliwości, czy twierdzenie to zostało słusznie uznane za mit. Wątpliwości te bynajmniej się nie zmniejszyły, co więcej słuchałem sobie ostatnio interesującego podcastu (wiekowego dość), w którym pada zdanie będące tytułem tego wpisu.

Podcast jest do odsłuchania tutaj: Potęga podświadomości, czyli uwierz, że wiesz. Całość dość długa, ale moim zdaniem warta posłuchania. Dodatkowo można też posłuchać tych podcastów: Intuicja w pracy i w życiu osobistym oraz Czy intuicja jest sprzeczna z racjonalnością.

I może ta pierwsza odpowiedź jest właśnie intuicyjna?

Niekonsekwencje w ASP.NET

2012-01-30 Posted by Paweł Goleń under Polskie blogi IT

Na ostatnim spotkaniu OWASP w Krakowie jedna z prezentacji dotyczyła zapobiegania XSS w aplikacjach tworzonych w ASP.NET. Z prezentacją można zapoznać się tutaj: Defending ASP.Net apps against XSS. Ja chciałem z kolei zwrócić uwagę na pewną niekonsekwencję w zachowaniu platformy, która może doprowadzić do niepożądanych skutków, czyli do XSS.

W ramach przykładu popatrzmy na trzy kontrolki:

  • TextBox,
  • Label,
  • HyperLink,

Kontrolka TextBox

Zacznijmy od kontrolki TextBox, czyli normalnego pola tekstowego, w które użytkownik może wpisać dane. Jest ona również wykorzystywana do prezentacji danych, zarówno w celu ich edycji, jak i w trybie tylko do odczytu. Spróbujmy w tym przypadku:

  • przypisać jej wartość,
  • przypisać jej atrybut,

Przykładowy fragment kodu:

TextBox2.Text = TextBox1.Text;
TextBox2.Attributes.Add("attr1", TextBox1.Text);
TextBox2.Attributes.Add(TextBox1.Text, "val1");

W ramach payloadu testowego wykorzystam tym razem coś takiego:

pmq'"<>

Jak wygląda kod HTML zwrócony do klienta? Tak:

<input name="ctl00$MainContent$TextBox2" type="text" value="pmq&#39;&quot;&lt;>" id="MainContent_TextBox2" attr1="pmq&#39;&quot;&lt;>" pmq'"<>="val1" />

Rozbijmy go na poszczególne fragmenty.

Po pierwsze wartość przypisana do Text jest prawidłowo kodowana na wyjściu, fragment kodu HTML wygląda następująco:

value="pmq&#39;&quot;&lt;>"

W tym przypadku programista nie musi troszczyć się o właściwy encoding danych na wyjściu. Analogicznie sytuacja wygląda w przypadku, gdy dodaje on atrybut. Wartość atrybutu jest kodowana, co pokazuje ten fragment kodu:

attr1="pmq&#39;&quot;&lt;>"

W tym samym miejscu jest jednak pewna niespodzianka, o oczyszczenie/zakodowanie nazwy atrybutu programista musi zadbać sam, bo inaczej może spotkać go coś takiego:

pmq'"<>="val1"

Można argumentować, że sytuacja, w której atakujący kontroluje nazwę atrybutu nie jest powszechna. Zgoda, ale tym większe prawdopodobieństwo, że jeśli już taka konstrukcja zostanie zastosowana, to programista przyzwyczajony do tego, że o encoding troszczyć się nie musi, nie zastosuje go również w tym przypadku. A kontekst jest o tyle przyjemny (dla atakującego), że mechanizm ValidateRequest na niewiele się tutaj przyda (przy okazji polecam: Bypassing ValidateRequest in ASP.NET przy czym w tym kontekście bypass nie jest potrzebny).

Kontrolka Label

Co jest nie tak z kontrolką Label? Służy często do wypisania tekstu na stronę, w szczególności jest ona używana jako zamiennik kontrolki TextBox w sytuacjach, gdy dane mają być wyświetlone w trybie tylko do odczytu i gdy zastosowanie TextBox z wyłączoną edycją nie jest najlepszym pomysłem (na przykład stronę do wydruku).

W przypadku tej kontrolki zróbmy tylko jeden test, spróbujmy wypisać dane przy pomocy takiego kodu:

Label1.Text = TextBox1.Text;

Kod jest identyczny, jak w przypadku kontrolki TextBox. A rezultat? Trochę inny:

<span id="MainContent_Label1">pmq'"<></span>

Jak widać w tym przypadku to programista musi zadbać o odpowiednie zakodowanie znaków na wyjściu. Pojawia się pewna niekonsekwencja między różnymi kontrolkami. Ten sam sposób wypisania danych może być zarówno bezpieczny, jak i niebezpieczny, w zależności od wykorzystanej kontrolki.

W tym przypadku warto ponownie zwrócić uwagę na kontekst. Akurat tutaj mechanizm ValidateRequest może okazać się wystarczającym zabezpieczeniem, ale oczywiście nie musi. Ponownie nie chodzi mi tutaj o możliwość jego obejścia (np. w sposób opisywany we wcześniej wspominanym artykule), ale o to, że nie zawsze dane wchodzą do aplikacji tylko jedną ścieżką i nie na każdej ścieżce działa ValidateRequest. Przykładowo może istnieć XSS, jeśli:

  • dane są wprowadzane przez WebService,
  • dane są pobierane z importowanego pliku (np. import z XML),
  • wypisywane dane są „składane” z kilku pól,

Jeśli zajrzeć w dokumentację, znajdzie się tam następujący fragment:

This control can be used to display user input, which is a potential security threat. By default, ASP.NET Web pages validate that user input does not include script or HTML elements.

Nie lubię tego typu zagmatwanych komunikatów, bo na dobrą sprawę po jego przeczytaniu można mieć poważne wątpliwości, czy i w jakich przypadkach problem istnieje. Z moich doświadczeń wynika, że w 100% przypadków, w których byłem w stanie w pełni kontrolować dane przekazywane do kontrolki Label, miałem tam XSS.

Kontrolka HyperLink

Na koniec kontrolka HyperLink. W tym przypadku kod wygląda następująco:

HyperLink1.Text = TextBox1.Text;
HyperLink1.Target = TextBox1.Text;
HyperLink1.NavigateUrl = TextBox1.Text;
HyperLink1.Attributes.Add("attr1", TextBox1.Text);

Wykonuje on kolejno:

  • ustawienie tekstu odnośnika,
  • ustawienie atrybutu target (okna, w którym ma otworzyć się odnośnik),
  • ustawienie adresu (atrybutu href),
  • dodanie wartości atrybutu o nazwie attr1

Wygenerowany kod wygląda następująco:

<a id="MainContent_HyperLink1" attr1="pmq&#39;&quot;&lt;>" href="pmq&#39;&quot;&lt;>" target="pmq'"<>">pmq'"<></a>

Przypisanie wartości (tekstu) zachowuje się dokładnie tak samo, jak w przypadku kontrolki Label, czyli programista sam musi zadbać o właściwy encoding. Ciekawsze rzeczy dzieją się w przypadku atrybutów. Ustawiane były trzy atrybuty:

  • href,
  • target,
  • attr1,

Oczekiwać można, że we wszystkich trzech przypadkach zachowanie kontrolki będzie takie samo. Jest nieco inaczej:

attr1="pmq&#39;&quot;&lt;>"
href="pmq&#39;&quot;&lt;>"
target="pmq'"<>"

Jak widać wartość atrybutu jest prawidłowo kodowana w przypadku atrybutu attr1 oraz href, w przypadku atrybutu target natomiast żaden encoding nie ma miejsca. WTF? Co więcej w dokumentacji dla HyperLink.Target nie widzę nawet jednego zdania, które mogłoby sugerować takie zachowanie.

Przy okazji trafiłem na ciekawy wątek związany ogólnie z atrybutami: .Net 4.0 Attributes.Add encoding bug. Wynika z niego, że encoding atrybutów dodawanych przez Attributes.Add pojawił się w ASP.NET 4.0 i, niespodzianka, spowodowało to trochę problemów.

Swoją drogą programiści wypowiadający się w tym wątku to jakieś mięczaki. Przecież problem można rozwiązać w trywialny sposób, wystarczy dać następującą nazwę atrybutu oraz jego wartość:

String name = "onclick=\\\"alert('Works!')\\\" foo";
String value = "";
(...).Attributes.Add(name, value);

Co się ślicznie przetłumaczy do postaci (przykład z testowego kodu, istotny pogrubiony fragment, wyciąłem trochę nieistotnej zawartości):

<input value="onclick=&quot;alert(&#39;Works!&#39;)&quot; foo" onclick="alert('Works!')" foo="val1" />

Twardym trzeba być :)

Podsumowanie

To, co pokazałem, to oczywiście tylko mały wycinek tematu. Chodziło mi o pokazanie pewnego braku konsekwencji w ASP.NET, który może okazać się bolesny w skutkach. Gdyby przyjrzeć się dokładniej poszczególnym kontrolkom, może okazać się, że występują bardziej realistyczne przypadki. Rozumiem przez to takie sytuacje, w których istnieje większe prawdopodobieństwo, że wypisane zostaną dane pochodzące od klienta, bo przyznaję, że zarówno w przypadku nazwy atrybutu czy atrybutu target dla kontrolki HyperLink taka sytuacja jest umiarkowanie prawdopodobna.

Istniejący w ASP.NET mechanizm ValidateRequest czasami jest zabezpieczeniem wystarczająco skutecznym. Powtarzam: czasami, ponieważ:

  • payload może nie wymagać wychwytywanych przez ValidateRequest ciągów znaków,
  • może istnieć bypass tego mechanizmu,
  • dane wchodzą do systemu różnymi ścieżkami, w szczególności niechronionymi przez ValidateRequest,
  • dane wchodzą do systemu w postaci zakodowanej,

Porównując ilość podatności typu XSS w aplikacjach tworzonych w oparciu o ASP.NET i w tych tworzonych z wykorzystaniem innych technologii, ASP.NET prezentuje się całkiem nieźle. To nie znaczy jednak, że istniejące w platformie mechanizmy ochrony zwalniają od myślenia o ochronie przed XSS.

Jak szybko testować? Po raz kolejny z pomocą przychodzi mój „ulubiony” payload (tym razem właściwie dwa):

pmq'"<>
pmq'"<A>

Jeśli na wyjściu pojawi się pierwszy payload, to mamy problem, ale prawdopodobnie ValidateRequest nas ocali. Jeśli na wyjściu pojawi się drugi payload, mamy dziurę. Różnica jest taka, że drugi payload nie ma prawa wejść do aplikacji z aktywnym ValidateRequest. Jeśli wejdzie, mechanizm ten dla aplikacji, lub tej strony, został wyłączony. Oczywiście pomijam tutaj inne możliwe ścieżki wprowadzenia tego typu payloadu do aplikacji (patrz wyżej).

Swoją drogą myślę, że ciekawym eksperymentem mogłoby być zastosowanie podejścia secure by default. W tym kontekście mogłoby to oznaczać domyślne stosowanie encodingu w każdym przypadku, oraz dostarczenie programistom wygodnej metody wypisania danych „surowych”, jeśli rzeczywiście jest taka potrzeba. Tu aż prosi się by wspomnieć o ctemplate i Auto Escape (tak, mało związane z ASP.NET).

(D)DoS jest trendy

2012-01-24 Posted by Paweł Goleń under Polskie blogi IT

No bo jak inaczej skomentować poniższy obrazek?

Nowe videocasty: jak szukać SQLi

2012-01-21 Posted by Paweł Goleń under Polskie blogi IT

Przygotowałem dwa nowe videocasty na temat szukania błędów typu sql injection. Nie są one specjalnie oryginalne, dokładnie na ten temat pisałem tutaj: Jak szukać SQLi – przykład, temat poruszałem także tutaj: Lekcja 7: (blind) SQL injection.

W pierwszym wideo, Bootcamp #5: Jak szukać SQLi #1, omawiam przykładowe payloady, które pozwalają w miarę prosty sposób identyfikować „podejrzane” parametry, których zachowanie sugeruje, że może istnieć podatność. Drugi odcinek, Bootcamp #6: Jak szukać SQLi #2 , to już praktyczne wykorzystanie tych payloadów na przykładowej aplikacji. Cała playlista dostępna jest tutaj.

Zgadywanka: ciąg dalszy

2012-01-20 Posted by Paweł Goleń under Polskie blogi IT

Ciąg dalszy poprzednich dwóch wpisów. Pomysł tej „zgadywanki” wpadł mi do głowy przy okazji zupełnie innego tematu. Wszystkie obliczenia robiłem dopiero po opublikowaniu przykładu i okazuje się, że zupełnie przypadkiem udało mi się znaleźć ciekawy przykład, który jeszcze na kilka sposobów wykorzystam.

Na poniższym wykresie zaznaczone są oczekiwane wartości wygranych w dwóch grach. Pierwsza z gier (kolor niebieski) to nasz oryginalny przykład. Druga gra (kolor czerwony) to gra o prawie identycznych zasadach, różnica jest tylko taka, że losowanie jest ze zwracaniem.

W pierwszym wariancie oczekiwana wartość wygranej jest prawie zawsze dodatnia. Pierwszym wyjątkiem jest przypadek, gdy gracz rezygnuje z losowania, wówczas ma takie same szanse wygranej i przegranej, również pieniądze do wygrania/przegrania są dokładnie takie same. Drugi wyjątek to sytuacja, w której gracz losuje 10 kul. Ma co prawda pewność wygranej, ale nic na tym nie zyskuje, otrzymana wypłata jest równa kwocie, którą zapłacił za przystąpienie do gry. Ten wariant gry zdecydowanie nie ma sensu dla organizatora. Na dłuższą metę będzie musiał dopłacać do tego interesu. Gracz ma natomiast pewność, że (w dłuższej perspektywie czasu) na grze zarobi.

W drugim wariancie to, czy gracz w dłuższej perspektywie czasu osiągnie sukces (zarobi), czy raczej straci, zależy od przyjętej przez niego strategii – ile kul będzie losował. Ta gra również nie ma sensu (dla kasyna), w tym wypadku również organizator będzie musiał dokładać do interesu, przynajmniej teoretycznie. Teoretycznie, bo nie wiem jaką strategię gry przyjmowałby „statystyczny gracz”. Być może większość graczy wybierałaby taką strategię gry, która daje większe szanse na zarobek dla kasyna (patrz wspominany już paradoks Monty Halla).

Teraz na chwilę odłóżmy liczydła. Mając do wyboru dwie takie gry (gra A – losowanie bez zwracania, gra B – losowanie ze zwracaniem) oraz opcję rezygnacji z gry, co wybrać? Jak dużą rolę w tym wyborze odgrywa awersja do ryzyka?

Załóżmy, że wybrany został pierwszy wariant gry. Jaką strategię gry (ilość losowanych kul) wybrać? Czy jest jakaś różnica w obrębie wariantów 1 i 9 kul, 2 i 8 kul, 3 i 7 kul oraz 4 i 6 kul? Oczekiwana wygrana w przypadku wyboru 1 kuli jest taka sama, jak w przypadku wyboru 9 (i dalej parami analogicznie), ale czy rzeczywiście nie ma różnicy między tymi wariantami?

Zgadywanka: czy ta gra ma sens II

2012-01-19 Posted by Paweł Goleń under Polskie blogi IT

Nie spodziewałem się, że mój poprzedni wpis zaowocuje tak dużą ilością komentarzy. Tak, pytanie o to, czy gra ma sens, jest nieco przewrotne. Odpowiedź na to pytanie zależy od tego, co uznamy za sensowność. Z grubsza (na dłuższą metę) mogą zajść trzy sytuacje:

  • gracze i organizator gry wychodzą na zero,
  • gracze są stratni,
  • organizator gry jest stratny,

W komentarzach utworzyły się dwie partie. Według części komentujących w grze wychodzi się „na zero”, według innych – organizator gry musi dopłacać do interesu (pojawia się też trzecia partia obstająca za trzecią możliwością – zarobkiem dla kasyna). I tutaj jest kolejna przewrotność: zgadywanka. Tu nie chodzi o zgadywanie, tylko o fakty.

Załóżmy taką strategię gry:

  • gracz zawsze losuje pewną liczbę kul,
  • jeśli wśród wylosowanych kul znajduje się czarna, odpowiada: jest czarna,
  • jeśli wśród wylosowanych kul nie ma kuli czarnej, odpowiada: nie ma czarnej,

Jaką ma szansę na wygraną przy takim postępowaniu?

Dla ułatwienia posłużmy się schematem:

W grze są możliwe trzy sytuacje, od lewej:

  • w skrzyni nie ma czarnej kuli, gracz wyciąga same białe,
  • w skrzyni jest czarna kula, gracz wyciąga same białe,
  • w skrzyni jest czarna kula, grach wyciąga (również) czarną kulę,

Przy przyjętej strategii gry, gracz przegrywa wyłącznie w jednym przypadku, który na schemacie został oznaczony kolorem czerwonym. Jakie jest prawdopodobieństwo tego zdarzenia? Czy gracz ma większą szansę na wygraną, czy porażkę? Jak zmienia się oczekiwana wartość (zysk/strata) w zależności od ilości losowanych kul?

Zgadywanka: czy to ma sens?

2012-01-18 Posted by Paweł Goleń under Polskie blogi IT

Wyobraźmy sobie następującą grę. W skrzyni znajduje się 10 kul, z czego 1 może być czarna. Prawdopodobieństwo, że czarna kula znajduje się w skrzyni wynosi 1/2. Zadaniem gracza jest właściwe odgadnięcie, czy w skrzyni znajduje się czarna kula. Gracz może:

  • udzielić odpowiedzi natychmiast,
  • raz wylosować dowolną, z góry zadeklarowaną, liczbę kul,

Przystąpienie do gry kosztuje 50 PLN. Za prawidłową odpowiedź gracz otrzymuje 100 PLN, przy czym kwota jest pomniejszana o 5 PLN za każdą wylosowaną kulę.

Przykładowo: gracz przystępuje do gry, deklaruje wylosowanie 3 kul. Jedna z kul jest czarna, więc oczywiście odpowiada (prawidłowo), że w skrzyni znajduje się czarna kula. Otrzymuje 85 PLN (100 – 3 * 5 = 100 – 15 = 85 PLN).

Pytanie: czy ta gra ma sens?

Całość z kawałków II

2012-01-17 Posted by Paweł Goleń under Polskie blogi IT

Kontynuuje ten interesujący chyba tylko dla mnie temat odzyskiwania sekretu z jego kawałków. Korzystając z poprawionej wersji skryptu, postanowiłem przeprowadzić prosty eksperyment, który pozwoli z grubsza oszacować ilość próbek, które należy przechwycić, by ustalenie sekretu było możliwe. To tak z ciekawości jak będzie się to miało do liczby prób uwierzytelnienia, które malware musi podpatrzeć (wie, o które znaki system pyta!), by odgadnąć całe hasło. Pisałem na ten temat wiele razy, ostatnio (chyba) tutaj: Hasła maskowane. Znowu.

Na wejściu dysponuję autentyczną listą masek wygenerowanych w pewnym systemie. Na podstawie tej listy uzyskuję zbiór możliwych do uzyskania z pewnego sekretu haseł jednorazowych. Z tego zbioru wybieram losowe próbki składające się z określonej liczby haseł jednorazowych i próbuję odzyskać sekret. Całość powtarzam określoną ilość razy (w tym przypadku – 10), oraz dla różnych sekretów (trzy różne).

Na początek rezultat skryptu dla próbek składających się z 5 elementów. W kolejnych kolumnach podana jest ilość kroków (wykorzystanych haseł jednorazowych), ilość możliwych sekretów, czas wykonania oraz dane wejściowe.

Secret: JavSyffiapDybji

   5   1433    1.97 ['yiapb', 'avyaj', 'Sfpy', 'JaSfiaDbji', 'avfiybi']
   5  18825    4.86 ['vSaj', 'afipyji', 'Sfpy', 'aSyfiapbji', 'vSffibj']
   5      3    1.31 ['vSffibj', 'Javyfiapji', 'JvfpDyji', 'avSfiaD', 'Sfiybj']
   5   3593   55.57 ['JaSfi', 'fiaDy', 'SffaDyb', 'yiybi', 'SyfapDj']
   5   2022    2.14 ['aSaji', 'aSfi', 'JaSb', 'JffiDi', 'aSyfiapybj']
   5   6177   34.16 ['yiapyb', 'aSyfipyb', 'vSyfiybi', 'avfipDyj', 'avfpb']
   5   5717    3.76 ['JffiDy', 'ayDb', 'vSfiapybi', 'JSyfapDi', 'vSfy']
   5 246043  123.31 ['aSfipyb', 'Jvipb', 'vSfy', 'aSyfayj', 'Jyfii']
   5    550    2.33 ['JffaDji', 'JffiDi', 'JvSyfiaDbj', 'afapDbj', 'vSfy']
   5 254020   48.19 ['vSaj', 'SypD', 'JffiDi', 'aapDji', 'aSyfipDj']

Secret: 6OgAdyeOkamousk

   5    298    7.94 ['gAeo', 'OgeOouk', 'Adykams', 'dOkau', '6gOau']
   5     16    1.63 ['6OAdeOkmok', '6OAdyOkouk', 'OAdyOams', 'gkous', '6gAdeOkmus']
   5    834   31.88 ['Okamuk', 'OgyeOos', 'OAeOaou', 'OAyOkaous', 'gdykaou']
   5   3131    3.92 ['gAeOao', 'Oyauk', 'Aams', 'gAdeOkmouk', 'gAyeOus']
   5     40    1.02 ['6yekmsk', 'dykamk', 'OAdeOaok', 'gdyOkmusk', 'gAkok']
   5 1312100  538.78 ['Odmu', 'OOaos', '6dyOk', 'eOkmo', 'yeamu']
   5   1034   13.34 ['6yeOmk', 'Oekamus', 'OAyOamok', 'OAdyOamosk', 'OAdekos']
   5     24    0.33 ['6gdyekamuk', 'demk', '6OAeO', '6OAdeOkmok', 'Odeask']
   5   6209   13.23 ['yekamu', '6OyeOmo', 'gAeo', '6gAdekou', 'OgAyOkm']
   5    704   38.63 ['6geamosk', '6OAok', 'gekmuk', 'Odeams', 'gyeOau']

Secret: 1234567890abcde

   5    384    8.53 ['356abce', '15670bc', '245790bc', '235670bcd', '58bce']
   5     15    0.26 ['2389acde', '246890bcd', '2680bde', '135670abde', '245680ad']
   5    977    6.16 ['2346780abd', '245680ad', '670ac', '389ce', '2358bce']
   5   4904   24.39 ['2390bd', '124c', '24680abe', '379ace', '2478']
   5   2635    1.17 ['249de', '290ade', '25ac', '1356790ace', '46780de']
   5 155075  187.19 ['3560b', '378ab', '5679', '124c', '245780bc']
   5     90    0.53 ['246abd', '1356790ace', '2570ad', '235690a', '245680ad']
   5     10    4.18 ['145690ae', '245780be', '4679abc', '578d', '235690cde']
   5   1624    4.31 ['235690ac', '2450ad', '46780abe', '290ace', '245780bc']
   5  13803    3.75 ['24680abe', '470b', '235670bcd', '356abce', '2680ade']

Jak widać w żadnym przypadku nie udało się ustalić sekretu. Zwykle ilość możliwych sekretów jest na tyle duża, że wyniki są praktycznie nieprzydatne. Można zaryzykować więc stwierdzenie, że próbka 5 haseł jest zbyt mała by na ich podstawie z powodzeniem odtworzyć wykorzystany sekret.

Sytuacja zmienia się nieco w przypadku próbki złożonej z 10 haseł jednorazowych. W tym wypadku większość prób zakończyła się znalezieniem ilości możliwych sekretów nieprzekraczających 50. Jeśli jednak uwzględnić fakt, że w przypadku hasła zwykle użytkownik (a więc i atakujący) dysponuje kilkoma próbami zanim konto zostanie zablokowane, taka ilość możliwych sekretów może już umożliwić uwierzytelnienie się w systemie.

Secret: JavSyffiapDybji

  10     18    0.37 ['yiapyb', 'avSfay', 'vSyfiaDybi', 'vyfapyb', 'aSfDyj', 'vfaDbi', 'ayfpDj', 'JSyfapDi', 'vyfipDyj', 'avSffipDyj']
  10     38    2.32 ['yfij', 'JvyfiDbji', 'JaSb', 'aSfipDyi', 'ypDi', 'viabi', 'SffaDyb', 'vyfDybi', 'vyfipDyj', 'afipDji']
  10     18    2.03 ['vaybj', 'vSaj', 'JSffiDyb', 'vSyfiDyi', 'yiapb', 'viabi', 'vypy', 'JSfipj', 'avyffpybj', 'vffipb']
  10      4    0.28 ['JvSffaDyj', 'avyiybi', 'aSyfiapbji', 'yiybi', 'afipDji', 'yiapb', 'SffaDyb', 'vfiDy', 'ffpDb', 'avffiyj']
  10     18    2.82 ['vfaDbi', 'avSfapDji', 'JaSfiaDbji', 'yiybi', 'vSaj', 'avSfpDbi', 'yiapyb', 'JSibi', 'vffipb', 'yffa']
  10      2    3.91 ['aviaDbji', 'avyfiDyb', 'vffipb', 'ffpDb', 'avyfapD', 'yfij', 'avyffpybj', 'vSyfipDj', 'vyfpy', 'JvfpDyji']
  10      2    2.70 ['avfiaDyji', 'JaSfi', 'afpyb', 'vSffibj', 'JffiDi', 'vSfiapybi', 'aSypDj', 'avSfpDbi', 'Sfiapj', 'vyfiaDbji']
  10     20    1.92 ['aSfi', 'JaSyfiaDyi', 'viabi', 'yiapyb', 'JffiDi', 'avyfapD', 'afapDbj', 'ayfpDj', 'JaSb', 'vyfipy']
  10     14   13.67 ['afipDji', 'Sfiybj', 'avSfiaD', 'JfpDbj', 'JvfpDyji', 'aSaji', 'fpDb', 'ayDb', 'yiapb', 'JvyffpDyji']
  10     12    4.86 ['aSypDj', 'avSffipybi', 'JSffiDyb', 'aSfipDyi', 'vSffibj', 'aSyfapyb', 'ayDb', 'yiapb', 'avyiaD', 'ypDy']

Secret: 6OgAdyeOkamousk

  10    722   28.68 ['geOmo', 'gAdeOouk', 'yeamu', 'damk', 'OAdeOaou', 'OAyOamok', 'dyek', '6yeOmk', '6Aks', 'gaous']
  10    181    0.56 ['6dyOk', 'Okamuk', 'OyOamsk', 'OgdOouk', '6AOuk', 'OyOaosk', 'OAdya', '6OgdyOkask', 'OAdyOamosk', 'OAdyOkausk']
  10     42   22.35 ['OAksk', 'gAdekamu', 'damk', 'OAdekos', '6dyOk', 'geOmo', '6OAeO', 'dykamk', 'gOkuk', 'gAdeOmok']
  10   2884   96.03 ['yekamu', 'Odmu', 'gOkuk', 'Oyaou', 'Aams', 'gkous', 'Adam', 'dykamk', 'OgAekamu', 'gdykaou']
  10      2    1.35 ['gAdeOams', 'OgeOkmosk', 'Ogyau', 'OgyOamos', 'AyOous', 'OyOaosk', 'Odyms', 'OgAekamu', 'yamu', '6gAyekmos']
  10      6   24.26 ['6eamus', 'OgyOamos', 'OAksk', 'gAeOou', 'OgAyamuk', 'gOkuk', 'OgyeOos', 'OgOkmusk', 'gdymouk', 'gAdekamu']
  10      4    0.52 ['gdykaou', 'OOaos', 'OgeOkmosk', 'OgAOkk', 'OAeOaou', '6OAok', 'Odeask', 'AyeO', '6OAeO', '6OAdyOkouk']
  10      5    0.88 ['Aeao', 'OAyOkaous', 'OgAyeOaouk', 'OgAaus', 'OAdyOamosk', 'Okamsk', '6yeOmo', 'OdOmo', 'OAdyOams', 'dOouk']
  10      1    3.98 ['OAdeOaok', 'Ogdyeaous', '6OAeOks', 'yekamu', 'gAyeOamos', 'OAeOaou', 'OgAyeOamos', '6yekmsk', 'gAyeOus', 'OgAyeOaouk']
  10      4    0.63 ['demk', 'OAyOkaous', 'gAks', 'OdOmo', 'OyOaosk', '6gdyOmusk', 'OgyeOos', 'Okamuk', 'eOkmo', 'Odyeamok']

Secret: 1234567890abcde

  10      8    0.75 ['290ade', '30bcd', '149d', '345789abce', '450a', '3468e', '256ad', '350b', '345790ac', '135780bcd']
  10      2    0.38 ['1245789abe', '23589a', '1678ae', '2359d', '1567ac', '356abce', '24560', '1370abde', '346780abd', '2390bd']
  10     16    1.33 ['234790ac', '58bce', '135780bcd', '347b', '2680ade', '46890d', '578d', '5890c', '2570ad', '1568e']
  10      4    1.45 ['25ac', '23589a', '234690b', '347b', '1246780bcd', '2478', '58bce', '2570de', '1568e', '50ae']
  10      2    0.44 ['245790bc', '50ab', '2790acd', '2340cd', '14780d', '124c', '13568acde', '234690b', '1246780bcd', '2358bce']
  10      4   19.47 ['24579bd', '25670abe', '789cd', '1348', '149d', '24680abe', '234689a', '15670bc', '137cde', '1370abde']
  10     48    4.91 ['170acd', '378ab', '290ace', '389ce', '356abce', '346780abd', '260ce', '46780de', '670ac', '346890bce']
   6      1    0.37 ['1245789abe', '148ce', '135670abde', '24780bc', '24579bd', '245680abde', '35689acde', '1348', '50ae', '234790ac']
  10      4    0.44 ['135780bcd', '789cd', '234790ac', '24560', '2456890bcd', '23789abde', '349be', '137cde', '148ce', '34780b']
  10    173    0.41 ['356abce', '290ace', '5890bc', '3478bc', '246890bcd', '346780abd', '23680abd', '35680abd', '369abce', '345789abce']

Ostatni test na próbkach o rozmiarze 15 haseł jednorazowych daje już zwykle ilość możliwych sekretów poniżej 5. W części przypadków udaje się nawet ustalić konkretny sekret (we wcześniejszej próbie również się to udało, dwukrotnie). Warto przy tym zauważyć, że tam, gdzie udało się ustalić konkretne hasło, wymaganych było mniej prób, niż rozmiar próbki. Czasami wystarczyło 5 lub 6 haseł jednorazowych, tylko musiały to być właściwe hasła (próbka w pierwszym kroku była sortowana).

Secret: JavSyffiapDybji

  15      3    0.48 ['Javyfiapji', 'avyiybi', 'avSffipDyj', 'Jafapybj', 'JvyfiDbji', 'avSfiaD', 'JSffiDyb', 'JSaj', 'JSfp', 'JaSffipybj', 'avSfpDbi', 'aapDbi', 'ayfDj', 'aSfipDyi', 'JvfpDyji']
  15      4    5.87 ['vSyfiaDybi', 'vSfy', 'SypD', 'aSyfapyb', 'SffaDyb', 'aSyfipyb', 'Sffipji', 'ayiDy', 'aSfipDyi', 'vfiDy', 'vpybj', 'JvfpDyji', 'JaSfy', 'JaSfiaj', 'yiybi']
  15      2    1.58 ['Jafapybj', 'JSfp', 'afipDji', 'SpDj', 'JyffDb', 'vSyfiDyi', 'avSfapy', 'JvyffapDbi', 'fiaDy', 'afpyb', 'aSyfayj', 'aSaji', 'aviaDbji', 'vaybj', 'SypD']
  15      3    0.70 ['vSfipy', 'vSfiapybi', 'aSfipDyi', 'JvfpDyji', 'aSyfiapbji', 'afapDbj', 'aSyfapyb', 'JaSfiaDbji', 'Jyffpyb', 'JvSyfayb', 'aSfi', 'SpDj', 'avDyj', 'aSfipyb', 'JaSyfaDbi']
  15      2    0.35 ['avSffipybi', 'SyfapDj', 'avSfpDy', 'yfij', 'JvSyfiaDbj', 'afpyb', 'aSypDj', 'ayfpDj', 'yfDi', 'JSaj', 'avyiybi', 'avfipDyj', 'Jayfiapbj', 'JvfpDyji', 'JSibi']
  15      4    0.48 ['Jyffpyb', 'aSyfp', 'ffpDb', 'vpybj', 'vyfiaDbji', 'yiapb', 'aSyfiapbji', 'avSfapDb', 'avSffipDyj', 'SypD', 'ypDi', 'avSffipybi', 'aSfipDyi', 'aSaji', 'yiapyb']
   8      1    0.29 ['yfij', 'avSffipybi', 'vyfiaDbji', 'JSaj', 'avSiai', 'JvSyfayb', 'avyiybi', 'aSyfipyb', 'JaSyfaDbi', 'aSfiapybj', 'ayffpDyi', 'JaSyfiaDyi', 'vyfapyb', 'aviaDbji', 'JSyfapDi']
  15      8    4.44 ['JaSb', 'ffpDb', 'vSyfiybi', 'JfpDbj', 'vSfii', 'SypD', 'avyfapDb', 'yiapyb', 'JaSyfapyb', 'aapDbi', 'JSfipj', 'avSfay', 'JSyfapDi', 'JvfpDyji', 'avfiybi']
  15      2    0.38 ['yiapyb', 'avyaj', 'avSfapy', 'avyiaD', 'aSfDyj', 'SffipDyi', 'aSaji', 'JffiDy', 'JvSffaDyj', 'JaSffipybj', 'vSffibj', 'JvfpDyji', 'vffipb', 'JvyffapDbi', 'avfiybi']
  15      4    0.69 ['JvSi', 'avfpb', 'JvyfiDbji', 'ayiDy', 'SffipDyi', 'Jvyfipybj', 'vSffipDyj', 'JffaDji', 'vfaDbi', 'vSffibj', 'aSypDj', 'yfapDi', 'afipDji', 'aSfi', 'aSyfiapbji']

Secret: 6OgAdyeOkamousk

  11      1    0.37 ['yeamu', '6OdeOamo', 'eOkus', 'dykamk', 'OgyOamos', 'Okamsk', 'gAdeOkmouk', 'Oekamus', '6OAdyOkouk', 'OAeO', 'OgAyamo', 'Odmu', '6OAok', 'OgAyOkm', 'gykmouk']
  15      8    1.36 ['Okamsk', '6geusk', 'OAdyOams', 'dykamk', 'Ogdykausk', 'AyeOamok', 'OAdeOaok', 'gAdekamu', 'OdOmo', 'OAyOamok', 'OAdyOamosk', 'damo', 'gAeOao', 'yamu', 'AyeOask']
  15      2    5.76 ['dOkaou', 'Okamsk', '6OAeO', 'Oekamus', 'geOmo', 'Odeams', '6OdeOamo', '6gAdekou', 'yamu', '6AeOas', 'yekamu', 'OAdeOaou', 'OAdeOaok', 'OAymos', '6OdyOkaus']
  15      4    0.66 ['yekamu', 'gAyeOamos', 'OAyOamok', '6yekmsk', '6geamosk', 'gAeo', 'gyeOau', 'gdao', '6OAdykmuk', 'OgdOouk', 'yamu', '6OAeo', 'deOs', 'OgyeOos', 'gAdekamu']
  15      2    2.08 ['OAeOaou', 'geOmo', 'OAdeOaou', '6gAO', 'Odyeamok', 'gAdekamu', '6OAok', 'AyOous', '6yekmsk', 'Oyauk', '6OAdyOkouk', 'eOkmo', 'gAeOou', 'OAdekaou', 'gdyao']
   6      1    0.63 ['dOkaou', 'Aams', '6OAdeOkmok', 'OAdyOkaous', 'damo', 'dOouk', 'gAks', '6gdyOmusk', 'gyeOau', '6geusk', 'eOkmo', 'gdao', '6gOau', 'OgAyamuk', 'Oyaou']
   6      1    0.39 ['gAeOou', 'Ogdykausk', 'gAeOao', 'OgdyOmou', 'OyOaosk', '6gAyekmos', 'Odyms', 'Odyeamok', 'yekamu', 'OAyOkaous', 'Oyaou', '6OAdeOkmok', 'Oyauk', '6dyeaou', '6Oekaous']
  15      2    0.78 ['OAdekaou', 'OAdekos', 'gykmouk', '6Adykamk', 'Odeams', 'Okamuk', 'Odmu', 'gAyOkaouk', 'Oekamus', '6eamus', 'Odeask', '6gdyekamuk', '6gAyekmos', 'OgeOkmosk', 'gAyOk']
  11      1    1.37 ['OdOmo', 'gAeOao', 'dyek', '6OdyOkaus', '6OAeo', 'gdykaou', 'Oyaou', 'Okamsk', '6Aks', '6gdeOaous', 'OgOkmusk', '6OAyeOaous', 'Adykams', 'OAdyOams', 'OyOamsk']
  15      4    0.22 ['yamu', 'AyOous', '6AOuk', 'Oekamus', 'Ogdykam', 'dOouk', 'dykamk', 'Ogmos', 'demk', 'OAdyOkausk', '6gdyeamosk', 'damo', 'Odyms', '6yeOmk', '6gdyekamuk']

Secret: 1234567890abcde

  15      2    0.40 ['246abd', '235690ac', '170acd', '1245790bc', '1356790ace', '260bc', '5890bc', '235690a', '36780c', '3468e', '5890c', '470b', '15670bc', '23568abc', '1245689bce']
  15      2    0.39 ['137cde', '2456890cde', '60ac', '2346780abd', '1345789acd', '235670bcd', '290ade', '350b', '134679abd', '23789abde', '30bcd', '2790acd', '1348', '15670bc', '470b']
   9      1    0.30 ['450a', '23680abd', '135670abde', '124789d', '235690a', '35690bc', '23460ace', '2450ad', '2680ade', '280bd', '124569ace', '148ce', '124789acde', '124c', '25670abe']
   9      1    0.21 ['235690cde', '2360c', '235670bcd', '23789abde', '23469b', '135670abde', '135780bcd', '350b', '280bd', '1380c', '170acd', '2456890cde', '246abd', '2680bde', '12356890de']
  15      2    0.37 ['235670bcd', '1345789acd', '35680abd', '46890d', '23678bd', '170acd', '12478', '345790ac', '245680ad', '134579bc', '148ce', '260bc', '346890bce', '12678ab', '57ae']
  15      2    1.43 ['14780d', '1380c', '2790acd', '148ce', '350b', '789ab', '134679abd', '235670bcd', '2346780abd', '34578abe', '245680ad', '35680abd', '5679', '1370abde', '245780bc']
  15      4    0.83 ['2456890cde', '1567ac', '50ae', '2360c', '30bcd', '137cde', '5690ae', '245680ad', '235670bcd', '1470', '246abd', '2390bd', '260bc', '1380c', '46780de']
   5      1    0.32 ['35689acde', '1245789abe', '12478', '2346780abd', '345789abce', '5690ae', '46780abe', '349be', '1678ae', '137cde', '245680ad', '2378bce', '1245790bc', '4678', '134579bc']
  15      2    0.40 ['2570de', '1246780bcd', '1345789acd', '23489e', '35689acde', '234690b', '2456890cde', '2478', '3468e', '23678bd', '134679abd', '124789d', '34780b', '135780bcd', '2456890bcd']
  15      3    0.23 ['137cde', '2456890cde', '1356790ace', '5890c', '45690ad', '134679abd', '46780de', '369abce', '149d', '249de', '60ac', '46780abe', '1380c', '1567ac', '345780ad']

Prawdopodobnie wyniki te różniłyby się w zależności od zbioru masek i początkowego sekretu. Na razie chyba jednak zakończę ten temat, bo:

  • haseł maskowanych i tak nie lubię,
  • w innych schematach hasło jednorazowe powinno być tworzone w oparciu o losowe znaki z sekretu,

Jeśli ktoś korzysta z usługi, w której jest uwierzytelniany przy pomocy określonych cyfr/znaków z jakiegoś współdzielonego sekretu, może zwrócić uwagę, czy uwierzytelnienie odbywa się w oparciu o „uporządkowane” znaki, czy pytania odnoszą się do losowych znaków w sekrecie. Jeśli zachodzi ten pierwszy przypadek, w ramach eksperymentu może sobie policzyć ile razy ktoś musi podsłuchać (dosłownie, jeśli jest to są to usługi jakiegoś call-center) taką operację (uwierzytelnienia), by móc się pod niego podszyć.

Do tematu jeszcze może kiedyś wrócę, w nieco innym kontekście. W przypadku pewnej aplikacji mobilnej spotkałem się z wykorzystaniem „hasła maskowanego” do… autoryzacji transakcji. Jeśli wykreślimy z obrazka malware, może to mieć sens. A i w przypadku ponownego wprowadzenia malware, wcale nie jest wykluczone, że koszt zrobienia tego lepiej (szeroko rozumiany, z akcentem na koszt w postaci wygody użytkownika), może być dość wysoki.

Kolejność ma znaczenie II

2012-01-13 Posted by Paweł Goleń under Polskie blogi IT

Jeszcze raz powrót do poprzedniego tematu. Przerobiłem swój skrypt na nieco mniej naiwną formę. Z ciekawości uruchomiłem go na jednym zestawie danych wejściowych, przy czym kolejność haseł była losowa. Wszystkich, poza pierwszym przypadkiem, który był brany bezpośrednio z poprzedniego wpisu. Nie można porównywać wprost czasów działania obu wersji skryptu (różne maszyny). Różnica pomiędzy poszczególnymi przypadkami w obrębie jednego zestawu haseł jest jednak bardzo widoczna:

['avSffipDyj', 'JaSyfapyb', 'aSyfipyi', 'avSfapDb', 'avSfpDy', 'ayfpji', 'JyffDb', 'vSfiyb', 'vaybj', 'ffpDb'] 0.411000013351
['ffpDb', 'avSfapDb', 'vaybj', 'avSfpDy', 'JaSyfapyb', 'avSffipDyj', 'vSfiyb', 'aSyfipyi', 'JyffDb', 'ayfpji'] 1056.74699998
['aSyfipyi', 'avSffipDyj', 'ffpDb', 'avSfpDy', 'vSfiyb', 'JaSyfapyb', 'avSfapDb', 'vaybj', 'JyffDb', 'ayfpji'] 38.4509999752
['vSfiyb', 'avSfapDb', 'aSyfipyi', 'ffpDb', 'avSffipDyj', 'vaybj', 'avSfpDy', 'ayfpji', 'JyffDb', 'JaSyfapyb'] 174.740999937
['avSffipDyj', 'JyffDb', 'ffpDb', 'JaSyfapyb', 'aSyfipyi', 'vSfiyb', 'ayfpji', 'vaybj', 'avSfapDb', 'avSfpDy'] 3.45799994469
['avSfpDy', 'aSyfipyi', 'JaSyfapyb', 'ayfpji', 'avSfapDb', 'vaybj', 'JyffDb', 'avSffipDyj', 'ffpDb', 'vSfiyb'] 25.5379998684
['avSfpDy', 'aSyfipyi', 'avSffipDyj', 'vaybj', 'JaSyfapyb', 'vSfiyb', 'ayfpji', 'ffpDb', 'avSfapDb', 'JyffDb'] 87.6420001984
['JyffDb', 'avSfapDb', 'avSfpDy', 'avSffipDyj', 'vaybj', 'aSyfipyi', 'vSfiyb', 'ffpDb', 'JaSyfapyb', 'ayfpji'] 290.555999994
['ayfpji', 'ffpDb', 'vaybj', 'avSfapDb', 'vSfiyb', 'aSyfipyi', 'avSffipDyj', 'avSfpDy', 'JaSyfapyb', 'JyffDb'] 734.882999897
['aSyfipyi', 'vSfiyb', 'avSffipDyj', 'avSfpDy', 'JaSyfapyb', 'ffpDb', 'vaybj', 'avSfapDb', 'ayfpji', 'JyffDb'] 84.8340001106
['JyffDb', 'vSfiyb', 'avSfapDb', 'avSffipDyj', 'ayfpji', 'aSyfipyi', 'avSfpDy', 'vaybj', 'ffpDb', 'JaSyfapyb'] 180.236000061
['ayfpji', 'vaybj', 'aSyfipyi', 'avSfapDb', 'vSfiyb', 'avSffipDyj', 'ffpDb', 'avSfpDy', 'JyffDb', 'JaSyfapyb'] 478.786000013
['aSyfipyi', 'avSfapDb', 'ayfpji', 'avSfpDy', 'vSfiyb', 'vaybj', 'ffpDb', 'JyffDb', 'JaSyfapyb', 'avSffipDyj'] 37.1860001087
['JyffDb', 'vaybj', 'ayfpji', 'avSfpDy', 'ffpDb', 'aSyfipyi', 'avSffipDyj', 'vSfiyb', 'JaSyfapyb', 'avSfapDb'] 530.560000181
['ayfpji', 'ffpDb', 'vaybj', 'vSfiyb', 'avSffipDyj', 'JaSyfapyb', 'avSfpDy', 'aSyfipyi', 'avSfapDb', 'JyffDb'] 879.066999912
['avSfapDb', 'avSfpDy', 'vSfiyb', 'ffpDb', 'avSffipDyj', 'JyffDb', 'vaybj', 'ayfpji', 'JaSyfapyb', 'aSyfipyi']

Ostatni przypadek zakończył się niepowodzeniem, wyleciał wiele mówiący wyjątek MemoryError. Akurat ten wyjątek był skutkiem mojego błędu (nie usuwałem między krokami identycznych masek).

Po drobnych poprawkach i wprowadzeniu funkcji sortującej, przypadki testowe z poprzedniego przykładu teraz wykonują się znacznie szybciej (posortowane dane wejściowe, czas wykonania).

['gAdeOkmouk', 'gdyOkmusk', 'OgdyOmou', 'OAdeOaok', 'OyOamsk', '6OAeOks', 'yekamu', '6yeOmo', 'Oyauk', 'Aeao'] 1.2990000248
['aSyfipDyji', 'vyfiaDbji', 'Jvyfipybj', 'avfiybi', 'JffaDji', 'avyiybi', 'vSfipy', 'avyaj', 'yiybi', 'ypDy'] 0.599999904633
['OgeOkmosk', 'Ogdykausk', 'Odyeamok', 'OgOkmusk', 'gdykaou', '6OAeOks', 'OAdekos', 'gdyOao', 'gAyOk', 'OAeO'] 2.882999897
['avSffipDyj', 'JaSyfapyb', 'aSyfipyi', 'avSfapDb', 'avSfpDy', 'ayfpji', 'JyffDb', 'vSfiyb', 'vaybj', 'ffpDb'] 0.365000009537
['vSyfiybi', 'JSffiDyb', 'avSfpDbi', 'JffiDy', 'Jvfbji', 'vSayi', 'aSaji', 'JaSfy', 'JSaj', 'Sfpy'] 9.55900001526

To teraz można się zabrać za wymyślanie haseł wyjątkowo trudnych do złamania w ten sposób ;)

Hasła maskowane inaczej

2012-01-07 Posted by Paweł Goleń under Polskie blogi IT

Podczas ostatniej dyskusji na temat haseł maskowanych pojawiła się sugestia, że jeśli atakujący „nie widzi”, które znaki są wpisywane, wówczas odgadnięcie pełnego hasła jedynie na podstawie przechwyconych fragmentów haseł, jest trudne. No, w każdym razie trudniejsze. Temat już wówczas mnie zaintrygował, ale Q4 zbliżał się nieubłaganie i temat zarzuciłem. Pora wrócić do tego tematu.

O co chodzi

Przede wszystkim oderwijmy się od haseł maskowanych. Zamiast tego pomyślmy o pewnej metodzie generowania hasła jednorazowego na podstawie współdzielonego sekretu. Metoda ta polega na podaniu wybranych znaków występujących w sekrecie. Co ważne, znaki są wybierane w kolejności wystąpienia, czyli np. jeśli sekret wygląda tak:

1234567890

to dopuszczalnymi wartościami hasła jednorazowego są:

1570
1239
3468

Przykładowe nieprawidłowe wartości to, przykładowo:

2185
1191

Wartości te są nieprawidłowe, bo po 2 nie występuje 1, a po 8 nie występuje 5 (pierwszy przykład), oraz 1 występuje tylko raz (drugi przykład). Czy na początku lepiej jest wybrać dłuższe, czy krótsze hasło (jeśli generowane hasła nie mają równej długości, tak jest np. dla części przypadków haseł maskowanych)? Czy kolejne hasło powinno być „odległe”, czy raczej bliskie tym wcześniej rozważanym?

Hasła maskowane są jednym z przykładów wykorzystania tej metody generowania hasła jednorazowego, ale nie jedyną. Zdarzają się na przykład metody uwierzytelnienia polegające na podaniu wskazanych cyfr z numeru PESEL, czy innego tajnego kodu. Spotkałem się nawet z metodą autoryzacji transakcji, która działała właśnie w ten sposób. Na początku klient uzgadniał z bankiem pewien dzielony sekret, a następnie przy wysyłaniu operacji musiał podać znaki występujące w nim na określonych pozycjach.

Zwracam jeszcze raz uwagę na założenie odnośnie tego, że podawane znaki muszą występować w kolejności. Nie w każdym schemacie tak jest.

Co ma atakujący

Jaki scenariusz rozważamy? Co dla atakującego jest znane? Załóżmy, że atakujący ma możliwość podsłuchiwania wielu haseł generowanych przez użytkownika. Nie wie natomiast, o które znaki z całego hasła użytkownik jest proszony. Taki przypadek może zaistnieć na przykład wówczas, gdy „ofiara” w obecności atakującego uwierzytelnia się w call center.

Atakujący może robić pewne założenia co do długości sekretu. Ta długość może być albo znana (bo wiadomo, że w określonym przypadku należy wybrać sekret o długości np. 8 znaków), albo jego długość mieści się w określonym zakresie (minimalna i maksymalna długość hasła). Dodatkowe wnioski odnośnie długości sekretu atakujący może wyciągnąć na podstawie zebranych haseł jednorazowych. Jeśli występuje w nich 10 unikalnych znaków, to długość sekretu musi wynosić co najmniej 10, nawet jeśli system pozwala na użycie sekretu długości 8 znaków.

Co robi atakujący

Dla uproszczenia przyjmijmy, że atakujący zna długość sekretu, którego szuka. Jeśli w rzeczywistości ta wartość pozostaje nieznana, atakujący może przyjąć pewne założenia i trzymać się ich tak długo, jak długo nie znajdzie dowodu na to, że założenie jest błędne.

W ramach przykładu załóżmy, że szukana przez nas wartość to 2435 i dysponujemy następującymi fragmentami:

25
35
43

W pierwszym kroku atakujący wybiera jedno z haseł jednorazowych i generuje wszystkie sekrety, które mogły posłużyć do wygenerowania takiego hasła. Wygląda to mniej więcej tak:

25xx
2x5x
2xx5
x25x
x2x5
xx25

Ten zestaw „możliwych sekretów” jest punktem wyjścia. W drugim kroku operację powtarza dla kolejnego wybranego hasła i otrzymuje następujący zbiór wartości:

35xx
3x5x
3xx5
x35x
x3x5
xx35

W tej chwili pozostaje sprawdzenie „części wspólnej”, czyli znalezienie takich wartości, które mogły posłużyć do wygenerowania zarówno hasła jednorazowego o wartości 25 jak i 35. Dla każdej z wartości ze zbioru wyjściowego porównywane są wszystkie wartości z bieżącego kroku. Czyli:

25xx 35xx (sprzeczne)
25xx 3x5x (sprzeczne)
25xx 3xx5 (sprzeczne)
25xx x35x (sprzeczne)
25xx x3x5 (sprzeczne)
25xx xx35 2535

W tej chwili mamy pierwszego kandydata na sekret. Trzeba go jednak odrzucić, bo nie ma tu już żadnego „wolnego” miejsca, a z zebranych danych wiemy, że są co najmniej 4 unikalne znaki.

Druga próba:

2x5x 35xx (sprzeczne)
2x5x 3x5x (sprzeczne)
2x5x 3xx5 (sprzeczne)
2x5x x35x 235x
2x5x x3x5 2355
2x5x xx35 (sprzeczne)

W tym przypadku znajdujemy dwie wartości, z czego jedna spełnia warunki potrzebne do przejścia do następnego kroku rozważań, 235_. Idźmy dalej:

2xx5 35xx (sprzeczne)
2xx5 3x5x (sprzeczne)
2xx5 3xx5 (sprzeczne)
2xx5 x35x 2355
2xx5 x3x5 23x5
2xx5 xx35 2x35

x25x 35xx (sprzeczne)
x25x 3x5x 325x
x25x 3xx5 3255
x25x x35x (sprzeczne)
x25x x3x5 (sprzeczne)
x25x xx35 (sprzeczne)

xx25 35xx 3525
xx25 3x5x (sprzeczne)
xx25 3xx5 3x25
xx25 x35x (sprzeczne)
xx25 x3x5 x325
xx25 xx35 (sprzeczne)

Nowy „punkt wyjścia” wygląda następująco:

235x
23x5
2x35
325x
3x25
x325

Nie pozostaje nic innego, jak powtórzyć zabawę dla kolejnej przechwyconej wartości, czyli 43:

43xx
4x3x
4xx3
x43x
x4x3
xx43

Na wyjściu w tej chwili otrzymamy tylko jeden możliwy ciąg znaków, w oparciu o który można uzyskać podane hasła jednorazowe. Jest to 2435, czyli oryginalny sekret (jak zauważył Koziołek, nie jest to jedyne rozwiązanie, może też być 4325).

Czy to działa

Jak pokazałem na bardzo prostym przykładzie – tak, działa. W praktyce jest jednak trochę gorzej, ten algorytm jest dość naiwny. Ilość przypadków „pośrednich”, które są generowane i które trzeba sprawdzić, jest olbrzymia. Nieoptymalne jest również porównanie każdego z elementów „punktu wyjścia” z każdym elementem dla aktualnie rozważanego hasła jednorazowego.

Trzeba pamiętać, że na wyjściu nie zawsze osiągnie się dokładnie jedno rozwiązanie. Może okazać się, że na podstawie zebranych haseł jednorazowych można stworzyć więcej niż jeden sekret, który pozwoli na wygenerowanie wszystkich zebranych przypadków. W takiej sytuacji nie pozostaje nic więcej, jak próbować przechwycić kolejne hasło jednorazowe i eliminować ze zbioru te sekrety, w oparciu o które tego nowego hasła jednorazowego nie można wygenerować.

Jednym z założeń w tym przykładzie było posiadanie na początku „ataku” kilku(nastu) zebranych haseł jednorazowych. Ciekawym zagadnieniem jest to, jaki wpływ na efektywność odzyskiwania sekretu, ma wybór (kolejność) haseł jednorazowych, które wykorzystywane są w obliczeniach.

A wniosek płynie z tego taki, że…

Jeśli w jakimś zastosowaniu wykorzystane ma być hasło jednorazowe generowane w oparciu o pewien współdzielony sekret, ilość informacji, które atakujący może uzyskać o sekrecie wyłącznie na podstawie przechwyconych haseł jednorazowych, powinna być jak najmniejsza. Na przykład kolejność znaków występujących w haśle jednorazowym nie powinna sugerować kolejności znaków w sekrecie, a to oznacza, że lepsze są te schematy, w których mamy podać losowe znaki z sekretu, a nie kolejne (kolejne, czyli pozycja znaku n+1 musi być większa, niż znaku n, choć nie koniecznie o 1).

I tak na koniec, nieco w temacie: Improving the Security of Four-Digit PINs on Cell Phones. Temat do przemyśleń – jak zmienia się złożoność zadania odgadnięcia sekretu w opisywanym tutaj przykładzie, jeśli:

  • na różnych pozycjach powtarzają się te same znaki (np. 1424),
  • znak występuje co najwyżej raz (np. 1234),

The Great KeePass Debate

2012-01-03 Posted by Paweł Goleń under Polskie blogi IT

Tytuł tego wpisu jest celowo mylący, wcale nie mam zamiaru debatować. Chcę natomiast odnieść się do tego komentarza. Adam napisał:

Nie rozumiem ludzi którzy korzystają z takich bajerów jak zapamiętywanie haseł za pośrednictwem programów czy serwerów, kiedy wystarczy sobie napisać własną funkcję i miksować 1 hasło przez url + md5, base64 itd, a na koniec np ucinać do 10 znaków i w 2ga stronę nie jest to możliwe do odtworzenia w praktyce.

Różnica między KeePass a opisanym przez Adama podejście jest taka, że hasło generowane przez KeePass jest losowe (właściwie: może być losowe, jeśli użytkownik sobie takie wygeneruje), natomiast hasło uzyskiwane w wyniku opisanej metody jest „wyprowadzane” na podstawie kilku danych wejściowych. Jeśli ktoś będzie w stanie odgadnąć „hasło główne” oraz ustalić sposób „wyliczania” hasła „docelowego”, będzie w stanie uzyskać hasło dla dowolnej strony. Mamy tutaj security through obscurity (tajny sposób „wyliczania” hasła) oraz swoisty class break. Co z tego wynika? Nic.

„Wynikowe” bezpieczeństwo jest zawsze kompromisem między wygodą, a „poziomem bezpieczeństwa”. Każdy z nas ma różne oczekiwania co do poziomu bezpieczeństwa, albo inaczej – jest w stanie zaakceptować inne ryzyko. Każdy jest również w stanie zaakceptować inny poziom upierdliwości przyjętego rozwiązania. Ja nie jestem w stanie zaakceptować uciążliwości związanych z hasłem maskowanym, dla innych brak hasła maskowanego sprawa krytyczna, przez którą nie mogą spać po nocach…

Warto zastanowić się nad tym tekstem Schneiera: How to Think About Security. Ja tutaj posłużę się nieco innymi pytaniami pochodzącymi z Beyond Fear:

  1. What assets are you trying to protect?
  2. What are the risks to these assets?
  3. How well does the security solution mitigate those risks?
  4. What other risks does the security solution cause?
  5. What costs and trade-offs does the security solution impose?

Jakie mogę udzielić odpowiedzi na te pytania w kontekście używania KeePass czy Password Safe (ale już nie LastPass)?

Pierwsze pytanie: jakie zasoby staram się chronić? Odpowiedź wydaje się prosta – hasła. W rzeczywistości wartością nie są hasła same w sobie, ale to, do czego te hasła dają dostęp. Druga część odpowiedzi na to pytanie jest nieco przewrotna – kolejnym zasobem, który staram się chronić, jest moja pamięć :)

Drugie pytanie: jakie są ryzyka? Jeśli chodzi o pamięć, to głównym ryzykiem jest to, że hasła zapomnę i utracę dostęp do zasobu, który chroni to hasło, albo co najmniej narażę się na konieczność przejścia procedury odzyskiwania hasła. Jeśli chodzi o hasła (i pośrednio chronione przez nie zasoby), to ryzyk jest wiele, w szczególności:

  • odgadnięcie hasła (bo jest za łatwe),
  • podglądnięcie hasła (bo ktoś stoi za plecami i podgląda jak je wpisuje),
  • podsłuchanie hasła (keylogger na komputerze, z którego korzystam lub podsłuchanie hasła w trakcie transmisji),
  • „wyciek” hasła z serwisu „docelowego”,

Część z tych ryzyk pozostaje poza moją kontrolą. Mogę korzystać z trudnych haseł, sprawdzać czy ktoś za mną nie stoi oraz korzystać wyłącznie z komputerów, do których mam zaufanie, ale to niewiele pomoże, jeśli komunikacja z usługą, z której korzystam, odbywa się bez szyfrowania lub jeśli hasła wyciekną z serwisu (w postaci jawnej lub w postaci łatwych do złamania hashy). Mogę co najwyżej minimalizować skutki takiego zdarzenia. Robię to używając różnych haseł dla różnych stron/usług/serwisów. Dodatkowo jeśli moje hasła są długie i złożone, to nawet jeśli wyciekną w postaci łatwych do złamania hashy, to i tak atakującemu nie będzie się opłacało inwestować czasu w ich łamanie.

Biorąc pod uwagę to, co napisałem powyżej, głównym ryzykiem, przed którym chcę się chronić jest to, że udane włamanie do serwisu Foo, w którym mam konto spowoduje, że atakujący uzyskają moje hasło dostępu do serwisu Bar, w którym zasoby są być może bardziej wartościowe, niż te w Foo.

Kolejne pytanie: czy to działa? Ponieważ korzystam z KeePass, dla każdego serwisu tworzę losowe, długie i unikalne hasło. Nawet jeśli to hasło zostanie przechwycone lub wycieknie z serwisu, atakujący nie uzyska automatycznie dostępu do pozostałych kont, które posiadam. Odpowiedź na to pytanie brzmi: tak, to działa i to działa całkiem dobrze. Dobrze działa to również w zakresie oszczędzania mojej pamięci. Muszę pamiętać tylko jedno hasło, dzięki któremu uzyskuję dostęp do bazy haseł (na podstawie którego generowany jest klucz wykorzystywany przy jej szyfrowaniu).

Czwarte pytanie jest ważne: jakie inne ryzyka powoduje rozwiązanie, z którego korzystam? Czy korzystanie z KeePass (or compatible) powoduje jakieś nowe ryzyka? W skrócie: tak.

A teraz trochę mniej skrótowo. Przede wszystkim zapisując swoje hasła tworzę nowy zasób, który jest bardzo wartościowy, i który muszę chronić. Muszę go chronić zarówno przed dostępem osób trzecich i ujawnieniem zapisanych haseł (warto się zapoznać: Detailed information about the security of KeePass), jak i przed jego zniszczeniem (utratą).

Ostatnie, piąte pytanie: jakie są „koszty” tego rozwiązania? Koszty związane są głównie z moją wygodą. Opisywałem w jaki sposób korzystam z KeePass. Koszt związany wybraniem odpowiedniego hasła z bazy i użyciem go na stronie, nie jest duży. Czasami poprzez korzystanie z funkcji autotype mogę nawet oszczędzać nieco czasu. Bardziej istotnym kompromisem jest to, że bez dostępu do mojego KeePass praktycznie nie mam dostępu do żadnego z moich kont. Może się to wydawać poświęceniem, które jest kompletnie nie do zaakceptowania, ale… nie dla mnie. I tak z założenia nie korzystam z nieswoich komputerów. Innymi słowy – jeśli korzystam z komputera, to jest to mój komputer. Jeśli to jest mój komputer, mam na nim KeePass i bazę do niego.

Podsumowując: używanie KeePass dobrze rozwiązuje problem(y), które chcę rozwiązać (oszczędność mojej pamięci, ograniczenie ryzyka związane z wyciekiem haseł z jednego z serwisów, w którym mam konto), powoduje nowe ryzyka (których jestem świadom i staram się je minimalizować w inny sposób), a koszty związane z korzystaniem z tego rozwiązania, są dla mnie akceptowalne.

Podejrzewam, że Adam w analogiczny sposób mógłby uzasadnić korzystanie z opisanego przez siebie rozwiązania. Ktoś inny mógłby z kolei twierdzić, że korzystanie z trzech różnych haseł (różne hasła dla różnych poziomów „ważności” serwisu) jest dla niego absolutnie wystarczające. Jak dla mnie OK, pod warunkiem, że każdy zdaje sobie sprawę z ryzyka, jakie podejmuje i świadomie je akceptuje.

I na koniec: Loss aversion, Risk aversion oraz: Loss aversion w S24.

O głębokim ukryciu nieco inaczej

2011-12-28 Posted by Paweł Goleń under Polskie blogi IT

Głębokie ukrycie doczekało się wpisu na Wikipedii. I wszystko byłoby w porządku, gdyby nie ten przykład:


http://example.com/29d9283aba927109a289b03812738d89201/2873944786672/10284.pdf

Wydaje mi się, że na temat trzeba spojrzeć nieco z szerszej perspektywy. A przy okazji – moim zdaniem głębokie ukrycie nie do końca zasługuje na oddzielny wpis w Wikipedii. Na dobrą sprawę jest to pewna forma security through obscurity, która w dodatku, w pewnych przypadkach i do pewnego czasu, działa.

Załóżmy, że mamy do czynienia z aplikacją, za której pośrednictwem każdy może sprawdzić swoje faktury za telefon. W pierwszym kroku należy uwierzytelnić się do aplikacji, a następnie pobrać plik PDF z interesującą nas fakturą.

Na początku mamy identyfikację (kim waść jesteś) i uwierzytelnienie (udowodnij waść, że jesteś tym, za kogo się podajesz) użytkownika (jeśli ktoś w mojej obecności użyje potworków językowych autentykacja lub autentyfikacja – pożałuje). Naturalnym celem atakującego będzie pozyskanie/odgadnięcie prawidłowej kombinacji nazwy użytkownika i hasła.

Załóżmy na chwilę, że nazwa użytkownika jest prosta do ustalenia (niech będzie to numer telefonu), do odgadnięcia pozostanie więc hasło. A hasłem tym niech będzie (tak jak w przypadku jednej z sieci) zestaw 4 + 8 cyfr (forma uwierzytelnienia dwuskładnikowego, gdzie pierwsze 4 cyfry są statyczne, pozostałe 8 cyfr to hasło dynamiczne), przy czym w dodatku obie części można zgadywać oddzielnie. W pierwszym etapie można ustalić statyczną część hasła, następnie należy próbować odgadnąć właściwe w danej chwili hasło dynamiczne. Hasło statyczne ma 10 000 możliwych wartości, hasło dynamiczne 100 000 000. Jeśli nie ma żadnych mechanizmów mających na celu ograniczenie tempa łamania hasła (czas trwania pojedynczej próby uwierzytelnienia, blokowanie konta, …), atakujący wcześniej lub później uzyska dostęp do konta swojej ofiary.

Ile czasu mu to zajmie? Można próbować to oszacować. Istotną pomocą jest tu fakt, że można oddzielnie ustalić statyczną część hasła i później szukać tylko części dynamicznej. Daje to 10**4 + 10**8 prób. I tu warto się chwilę zatrzymać, zwłaszcza przy tej części 10**8. Zakładam, że dla każdej próby generowane jest nowe hasło jednorazowe, więc nie jest gwarantowane, że właściwe hasło zostanie znalezione w 10**8 prób. Wiemy tylko, że w każdej próbie jest szansa 1/10**8 na trafienie właściwego hasła. Dla porównania prawdopodobieństwo trafienia 6 w lotto to trochę lepiej niż 1/14 000 000 (patrz: kombinacja bez powtórzeń). Chwilowo przyjmijmy jednak optymistyczny sposób postrzegania świata i załóżmy, że mamy gwarancję, że maksymalnie w 10**8 (trochę mniej niż 2**27) poznamy hasło właściwe hasło dynamiczne i uzyskamy w ten sposób dostęp do konta ofiary. Wystarczy przemnożyć czas potrzebny na jedną próbę uwierzytelnienia i (…)

Po uwierzytelnieniu pojawia się identyfikator sesji, który zastępuje dane uwierzytelniające (nazwa użytkownika + hasło statyczne + hasło dynamiczne). Obecnie identyfikator sesji ma zwykle długość 128 bitów przy entropii ponad 100 bitów. Szansa na odgadnięcie identyfikatora sesji jest więc (w przybliżeniu) jak 1 do 2**100. Jest to więc dużo trudniejsze, niż zgadnięcie właściwych danych uwierzytelniających. Dodatkowo trzeba pamiętać, że:

  • w danej chwili aktywna jest zwykle więcej niż jedna sesja,
  • identyfikator sesji jest ważny jedynie przez pewien czas (od uwierzytelnienia do zamknięcia lub wygaśnięcia sesji),

To, że aktywna jest więcej niż jedna sesja powoduje, że trafienie prawidłowego identyfikatora może być nieco łatwiejsze, niż to 2**100. Oczywiście mowa o jakiejś prawidłowej sesji, a nie prawidłowej sesji należącej konkretnie do użytkownika, którego dane chcemy poznać. Nawet jeśli identyfikator sesji jest trywialny do odgadnięcia nie można przejąć czegoś, czego nie ma. W tym wypadku nie można przejąć sesji użytkownika, jeśli użytkownik nie jest zalogowany.

Idźmy dalej. Jesteśmy uwierzytelnieni w aplikacji i pobieramy wybrany rachunek. Załóżmy, że nie ma kontroli dostępu. Niech będzie jeszcze gorzej, załóżmy, że nie tylko użytkownik A może pobierać rachunki użytkownika B. Niech rachunki można pobrać anonimowo, bez żadnego uwierzytelnienia. Jedynym „zabezpieczeniem” przed pobraniem cudzego rachunku jest w takim przypadku właściwa „ścieżka”, pod którą ten rachunek jest dostępny.

W tej chwili należy dobrze przyjrzeć się temu jak taki „identyfikator” wygląda, jak jest konstruowany. Jeśli będzie miał postać:

<numer_telefonu>_<rok>_<miesiąc>.pdf

oczywiste jest, że pobranie dowolnego rachunku dla dowolnego numeru za dowolny okres rozliczeniowy, jest trywialne. Jedynym „wyzwaniem” jest ustalenie numeru telefonu.

Inaczej sytuacja przedstawia się, jeśli ścieżka wygląda tak, jak na początku wpisu. Tutaj „strzelanie w ciemno” może spowodować więcej problemów. Pierwsza część ścieżki zawiera 35 znaków z zakresu (prawdopodobnie) od 0 do F. Daje to 16**35 możliwości (2**140). Druga część to 13 znaków z zakresu 0-9, co z kolei daje 10**13 możliwości (mniej więcej 2**43), ostatnia część to 10**5 możliwości (mniej niż 2**17). Jeśli porównać te wartości (wystarczy pierwsza część), to szansa na trafienie odpowiedniego identyfikatora zasobu jest zdecydowanie mniejsza, niż na odgadnięcie hasła lub identyfikatora sesji. Pokazaniu tego faktu służyło określanie ilości możliwych wartości jakie przyjąć mogą dane uwierzytelniające, identyfikator sesji oraz identyfikator zasobu.

Z faktu, że długość identyfikatora (lub jego części) oraz wykorzystanej przestrzeni znaków wynika tyle możliwości, wcale nie znaczy, że rzeczywiście tyle tych możliwości zostanie wykorzystanych. Dlatego w przypadku tak konstruowanych „identyfikatorów” trzeba zbadać jak one się zmieniają, czym mogą być poszczególne części. Może się okazać, że mimo teoretycznie ogromnej ilości możliwych wartości, w praktyce ilość tych możliwości jest zdecydowanie mniejsza. Może się również okazać, że identyfikatorów nie trzeba będzie zgadywać, bo dostępny będzie listing katalogów. Może się okazać jeszcze wiele innych rzeczy, w wyniku których okaże się, że efektywne zgadywanie identyfikatorów zasobów jest możliwe.

Może się również okazać, że zabezpieczenie oparte o „głębokie ukrycie” jest jednak wystarczające. Sam kilka razy spotkałem się z aplikacjami, w których kontrola dostępu pozostawiała wiele do życzenia, w zasadzie była realizowana wyłącznie w GUI (użytkownik widział tylko swoje obiekty, stary przykład: Lekcja 2: Nieprawidłowa kontrola dostępu do danych). Po analizie okazywało się jednak, że szansa na trafienie odpowiedniego identyfikatora obiektu, tak, by dostać się do cudzych danych, była wielokrotnie mniejsza niż szansa na to, że odgadnę identyfikator sesji ofiary, lub jego hasło. Nie jest to może rozwiązanie poprawne, ale w pewnych przypadkach – skuteczne.

Jako „głębokie ukrycie” można potraktować też mechanizm udostępniania zdjęć przez „linka”, co wygląda tak:


https://plus.google.com/photos/10xxxxxxxxxxxxxxxxx48/albums/514yyyyyyyyyyyyy889?authkey=CNKujbzQ-JuzWQ

W tym wypadku, by uzyskać dostęp do udostępnionego albumu trzeba podać w adresie:

  • prawidłowy identyfikator użytkownika,
  • prawidłowy identyfikator albumu,
  • prawidłową wartość authkey,

Jak w każdym przypadku systemu, którego działanie opiera się na tajności, całość działa do czasu, gdy sekrety nie zostaną celowo lub przypadkiem ujawnione. Lub do czasu, gdy ktoś ich nie zgadnie.

Dla mnie pojęcie „głębokie ukrycie” jest określeniem pejoratywnym. Rozumiem pod nim przede wszystkim sytuację, w której z powodu różnych (najczęściej głupich) błędów pewne dane/informacje zostały upublicznione. Może to wyglądać tak, lub tak, lub tak, jak zrobiłem w tym wpisie :) Powodzenia!

Encoding: ESAPI

2011-12-18 Posted by Paweł Goleń under Polskie blogi IT

Pokazanie przykładów niewłaściwego encodingu mamy za sobą (patrz: #1, #2, #3, #4 i #5). Na koniec przykład: http://bootcamp.threats.pl/lesson09b/, w którym encoding jest realizowany za pośrednictwem ESAPI (konkretnie owasp-esapi-php, jest to jeszcze wersja nieprodukcyjna). Dane wpisane przez użytkownika wypisywane są w trzech miejscach, w których encodowane są przy pomocy funkcji:

  • encodeForJavaScript,
  • encodeForJavaScript i encodeForHTMLAttribute
  • encodeForJavaScript,

Funkcja doStuff obecnie coś robi, konkretnie wypisuje wartość otrzymanego parametru w drugim textarea. Zrobiłem to po to, by każdy mógł się przekonać, że te dziwne znaczki, które generuje ESAPI to rzeczywiście to, co zostało oryginalnie przekazane. A te dziwne znaczki wyglądają tak:

Dla encodeForJavaScript w kontekście atrybutu HTML:

<a href="#" onclick="javascript:doStuff('\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x2B,\x2D.\x2F\x3A\x3B\x3C\x3D\x3E\x3F\x40\x5B\x5C\x5D\x5E_\x60\x7B\x7C\x7D\x7E')">demo 1</a><br />

Dla encodeForJavaScript i encodeForHTMLAttribute w kontekście atrybutu HTML:

<a href="#" onclick="javascript:doStuff('&#x5c;x21&#x5c;x22&#x5c;x23&#x5c;x24&#x5c;x25&#x5c;x26&#x5c;x27&#x5c;x28&#x5c;x29&#x5c;x2A&#x5c;x2B,&#x5c;x2D.&#x5c;x2F&#x5c;x3A&#x5c;x3B&#x5c;x3C&#x5c;x3D&#x5c;x3E&#x5c;x3F&#x5c;x40&#x5c;x5B&#x5c;x5C&#x5c;x5D&#x5c;x5E_&#x5c;x60&#x5c;x7B&#x5c;x7C&#x5c;x7D&#x5c;x7E')">demo 2</a><br />

Jeszcze raz dla encodeForJavaScript, tym razem w kontekście skryptu:

<script type="text/javascript">
doStuff('\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x2B,\x2D.\x2F\x3A\x3B\x3C\x3D\x3E\x3F\x40\x5B\x5C\x5D\x5E_\x60\x7B\x7C\x7D\x7E');
</script>

Mam nadzieję, że ten przykład wystarczająco dobrze pokazuje, że:

  • encoding może być prosty, wystarczy wywołać odpowiednią dla kontekstu funkcję,
  • encoding może być skuteczny (ktoś potrafi obejść encoding implementowany przez ESAPI i wykorzystany w tym przykładzie?),
  • mimo encodingu wszystko może nadal działać,

I tym optymistycznym akcentem kończę ten temat. Przynajmniej na jakiś czas.

Niewłaściwy encoding #5

2011-12-17 Posted by Paweł Goleń under Polskie blogi IT

Pora na zakończenie tematu przykładu z niewłaściwym encodingiem (patrz: #1, #2, #3 i #4). Ponownie, by nie przeciągać, dla tradycyjnego zestawu znaków testowych otrzymujemy:

<script type="text/javascript">
doStuff('!\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~');
</script>
<script type="text/javascript">
doStuff('!\"#$%&\'()*+,-.\/:;<=>?@[\\]^_`{|}~');
</script>

Czy można tu coś zepsuć?

Na pierwszy rzut oka, wydaje się, że wszystko jest w porządku. Dane przekazane przez użytkownika są wstawiane w kontekście tagu <script> jako część łańcucha znaków. Łańcuch ten ujęty jest w znak ‘, który to znak, jeśli wystąpi w danych przekazanych przez użytkownika, przekształcany jest do postaci \’. Czyli wszystko w porządku?

Nie, nie wszystko w porządku. W tym wypadku problem polega na tym, że parser HTML jest ważniejszy od parsera JavaScript. Najłatwiej pokazać to na przykładzie następującego payloadu:

</script><script>alert(/xss/)</script>

W kodzie strony wygląda to w sposób następujący (dla pierwszego przypadku):

<script type="text/javascript">
doStuff('</script><script>alert(/xss/)</script>');
</script>

Pozornie wszystko jest w porządku, dane przekazane przez użytkownika znajdują się wewnątrz łańcucha znaków w kontekście JavaScript. Tylko przeglądarka ma na ten temat nieco inne zdanie. Ona ten fragment kodu widzi w sposób następujący:

Parser HTML natrafiający na zamknięcie tagu script nie interesuje się specjalnie w jakim kontekście on wystąpił. Po prostu uznaje, że jest to koniec tagu. W oparciu o tę prostą sztuczkę można „uciec” z łańcucha znaków, w którym jesteśmy zamknięci przez zamianę znaku ‘ na \’ i po prostu otworzyć nowy tag script, w którym można umieścić własny kod.

Tu warto zwrócić uwagę na drugi przypadek, który przeglądarka zrozumie następująco:

W tym wypadku przeglądarka nie natrafia na kończący tag script, więc opisany wyżej przypadek nie działa. No chyba, że ktoś znajdzie sposób na to, by przeglądarkę jednak oszukać. Jakieś pomysły?

Niewłaściwy encoding #4

2011-12-16 Posted by Paweł Goleń under Polskie blogi IT

Tego odcinka mogłoby właściwie nie być. W tym przypadku sytuacja jest praktycznie taka sama, jak w poprzednim przykładzie. Dobrze to widać poniżej:

<a href="#" onclick="javascript:doStuff('!\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~')">demo 3</a><br />
<a href="#" onclick="javascript:doStuff('!\"#$%&\'()*+,-.\/:;<=>?@[\\]^_`{|}~')">demo 4</a><br />

Jedyna różnica pojawia się przy znaku /, który w drugim przypadku zyskuje postać \/. Czy ma to jakieś znaczenie w tym konkretnym kontekście? Może w pewnym, niewielkim stopniu ma, ale przed XSS nie chroni, przykład:

"><img src=x onerror=eval(String.fromCharCode(97,108,101,114,116,40,39,120,115,115,39,41))><foo bar="

Różnica w stosunku do poprzedniego przykładu sprowadza się do tego, że w tym przypadku nie jestem w stanie uzyskać tagu:

</script>

ponieważ funkcja escapowania znaków spowoduje jego przekształcenie do postaci:

<\/script>

Użycie String.fromCharCode natomiast obchodzi drobną niedogodność związaną z brakiem znaków ‘, ” oraz / dostępnych w „czystej” postaci. Jak widać całkiem dobrze można sobie poradzić bez nich.

Archiwa
  • Maj 2012 (68)
  • Kwiecień 2012 (159)
  • Marzec 2012 (196)
  • Luty 2012 (153)
  • Styczeń 2012 (128)
  • Grudzień 2011 (101)
  • Listopad 2011 (80)
  • Październik 2011 (94)
  • Wrzesień 2011 (49)
  • Sierpień 2011 (30)
  • Lipiec 2011 (21)
  • Czerwiec 2011 (14)
  • Maj 2011 (21)
  • Kwiecień 2011 (32)
  • Marzec 2011 (14)
  • Luty 2011 (13)
  • Styczeń 2011 (29)
  • Grudzień 2010 (11)
  • Listopad 2010 (22)
  • Październik 2010 (19)
  • Wrzesień 2010 (19)
  • Sierpień 2010 (15)
  • Lipiec 2010 (9)
  • Czerwiec 2010 (5)
  • Maj 2010 (5)
  • Kwiecień 2010 (13)
  • Marzec 2010 (13)
  • Luty 2010 (20)
  • Styczeń 2010 (13)
  • Grudzień 2009 (16)
  • Listopad 2009 (19)
  • Październik 2009 (30)
  • Wrzesień 2009 (14)
  • Sierpień 2009 (11)
  • Lipiec 2009 (25)
  • Czerwiec 2009 (2)
  • Maj 2009 (12)
  • Kwiecień 2009 (9)
  • Marzec 2009 (5)
  • Luty 2009 (5)
  • Styczeń 2009 (6)
  • Grudzień 2008 (6)
  • Listopad 2008 (4)
  • Październik 2008 (6)
  • Wrzesień 2008 (3)
  • Kwiecień 2008 (1)
  • Grudzień 2007 (1)
Kategorie
2003 2010 access Access 2003 Access 2010 Aktualności Bez kategorii BI CTP exchange online Exchange Server Exchange Server 2010 featured funkcje Grzegorz Tworek How To Hyper-V 3 Hyper-V Server 8 interoperacyjność IT Pro blogerzy Jak to zrobić Komputery i Internet Microsoft Outlook najlepsze praktyki Narzędzia open source Oprogramowanie PLSSUG Polskie blogi IT Porady PowerPivot Relacje Reporting Services SharePoint Foundation 2010 SharePoint Server 2010 Skrypty System Center 2012 Techniczne Tips and tricks video Virtual Machine Manager wersje beta WGUiSW Windows 8 Beta Windows 8 Customer Preview
Tagi
.net Active Directory Artykuły Blog blogosfera Cloud Computers and Internet CRM 2011 Excel Exchange Exchange 2010 Hyper-V Inne IT konferencja Konferencje Linux Lync Microsoft Microsoft Dynamics CRM News office 365 Ogólne PowerShell Private Cloud programowanie Publikacje Security SharePoint Społeczności IT SQL SQL Server SQL Server 2012 System Center Uncategorized Windows Windows 7 Windows 8 Windows Phone 7 Windows Server Windows Server 8 Windows Server 2008 Wirtualizacja Wydarzenia [EN]
Autorzy
Kamil Skalski, Konrad Sagala, Szymon Bochniak, Tadeusz , Tomasz Filipowicz, RSS , Łukasz Kałużny, kgorczewski , Łukasz , Wojciech Gardziński, Paweł Goleń, Dariusz Porowski, Piotrek Gardy, koprowskit , nExoR , Joanna Subik, Mateusz Świetlicki, Marcin , piotrpawlik , TechNet Polska, gsgalezowski , T4ngram , Metorio , Maciek Blog, Bloggers Underground, blog Michała Cywińskiego..., Me & Technology – Paula’s Security Blog, swilczew , pawp81 , programistaaccess , Bartek Bielawski, soisk , Zygmunt B., MS Dynamics Blog, Jarek Szybiński, rtynski , Filip , Świat Office, voytas , jaroslawsokolnicki , rem8 , Łukasz Matuszewski, Seb , kaarol , Peter , Kamil Karczmarczyk, Dariusz Brejnak, JeZZoo , bns , Pawel Potasinski, Kuba Skałbania, t.onyszko , robertmandziarz , Krzysiek , MKr , szulcu , Robert Stuczynski - Noise, kicekpicek , Dobert , Łukasz Zięba, drixter , Maciej Krasuski, Tomasz_Sochacki , Przemek Kuczyński, losiak , paramo , OSKAr , SzymonN , Marcin Milewski, marcinbojko , l10n , Łukasz Z., Grzesiek Bartosik, jnx
Polskie blogi specjalistów IT / Microsoft powered by WordPress and The Clear Line Theme