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.
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.
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:
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:
demo 1
Dla encodeForJavaScript i encodeForHTMLAttribute w kontekście atrybutu HTML:
demo 2
Jeszcze raz dla encodeForJavaScript , tym razem w kontekście skryptu:
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.
Oryginał tego wpisu dostępny jest pod adresem Encoding: ESAPI
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:
“><foo bar=”
Różnica w stosunku do poprzedniego przykładu sprowadza się do tego, że w tym przypadku nie jestem w stanie uzyskać tagu:
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.
Raz, dwa, trzy (to teraz). To make long story short:
?@[\]^_`{|}~')“>demo 3
Co jest tu nie tak? Cóż, przykładowy payload, czyli:
'+alert(/xss/)+'
w tym przypadku nie zadziała, będzie rezultat w kodzie strony będzie wyglądał tak:
demo 3
Jak widać znak ' jest wypisywany jako \', co jest właściwym sposobem escapingu tego znaku. Podobnie zresztą jak \\\\” jest właściwym sposobem escapingu znaku “, uwaga, w JavaScript. Problem tylko w tym, że nie jesteśmy (wyłącznie) w kontekście JavaScript, ale również w kontekście atrybutu HTML. A skoro tak, to:
“><foo bar=”
da w rezultacie:
demo 3
Zagmatwane? Przeglądarka zobaczy to tak (IE i Firefox):
Poprzednio okazało się, że encoding HTML w kontekście atrybutu HTML, który zawiera JavaScript, nie jest zbyt skuteczny. Tak samo encoding właściwy dla JavaScript stosowany w przypadku JavaScript, który zawarty jest w atrybucie HTML, nie jest wystarczający. Zupełnie inaczej sytuacja wyglądałaby, gdyby złożyć ze sobą te dwa sposoby kodowania znaków. Najpierw dane wstawiane do kontekstu JavaScript zabezpieczyć w sposób właściwy dla JavaScript właśnie, a potem, skoro trafiają do atrybutu HTML, całość zakodować w sposób właściwy dla tego kontekstu. Wyglądałoby to mniej więcej tak:
demo 3
Jak zobaczy to przeglądarka – do sprawdzenia we własnym zakresie :)
Jak łatwo zauważyć, jedyną różnicą w stosunku do poprzedniego przypadku jest dodatkowe kodowanie znaku “. Dobrze to widać, jeśli oba przypadki zestawi się ze sobą:
demo 1
demo 2
Można się domyślić, że problemu z XSS w tym kontekście ta zmiana raczej nie rozwiązuje.
Pierwsze wyjaśnienie odnośnie ćwiczeń z nieprawidłowego encodingu. Na pierwszy ogień pójdzie pierwsze miejsce, w którym wprowadzone dane są wypisywane. Jako string testowy wykorzystam następujący zestaw znaków:
!“#$%25&'()*+,–./:;<=>?@[]^_`{|}~
Na wyjściu natomiast dostaje się coś takiego:
demo 1
Jak widać, specjalnie traktowane są tylko nieliczne znaki, które są przekształcane w następujący sposób:
& –> &
< –> <
> –> >
Byłoby to zabezpieczenie wystarczające, gdyby dane te były wypisywane w kontekście HTML. Problem w tym, że nie są. Są wypisywane w kontekście atrybutu HTML, który, tak się dodatkowo składa, zawiera kod JavaScript.
Pod adresem http://bootcamp.threats.pl/lesson09a/ znajduje się prosty przykład “aplikacji”, która nie do końca poprawnie stosuje encoding danych wyjściowych. Przekazane dane są wypisywane w kilku miejscach, w różnym kontekście z wykorzystaniem różnego encodingu. Każdy z zastosowanych sposobów encodingu danych pozwala na XSS (na różne sposoby), choć nie w każdym(?) kontekście.
To tak w nawiązaniu do tego, że właściwy encoding jest trudny. W przeciwieństwie do encodingu, ten przykład trudny specjalnie nie jest. Miłej zabawy!
P.S. W zasadzie można się zastanawiać, czy i w którym przypadku stosowany jest encoding, a w którym escaping.
Gmail zaczął witać swoich użytkowników w następujący sposób:
O tym, że warto mieć unikalne hasła w różnych serwisach mówi się zwykle wówczas, gdy pojawia się jakaś informacja o możliwym wycieku danych użytkowników (w szczególności haseł lub stosunkowo łatwych do złamania hashy) z jakiegoś bardziej lub mniej popularnego serwisu. Zastanawiam się jakie są efekty takich dyskusji i jaki będzie efekt informacji wyświetlanej w Gmailu.
Ja tylko po raz kolejny podpowiem, że do przechowywania haseł i do generowania unikalnych haseł do różnych serwisów można wykorzystać na przykład KeePass. O tym, jak z niego korzystam pisałem tutaj: Jak korzystam z KeePass.