Dzisiaj pierwsza część rozwinięcia rozwiązania zadania XXIV z bootcamp.
Bootamp XXIV: co było nie tak w przykładzie
Przykłady były ostatecznie dwa, dostępne są one pod adresami:
Przykłady te różnią się w bardzo niewielkim stopniu, ale o tym później.
Pierwsza, podstawowa sprawa - skąd pochodzą dane pobierane przez skrypt i wstawiane na stronę? W tym przypadku dane pochodzą z blipa, skrypt pobiera statusy oznaczone tagiem #thrts. Ze specyfiki serwisu wynika, że autorem statusu może być każdy jego użytkownik. Jest to istotna różnica w stosunku, do sytuacji z wyświetlaniem mojego własnego statusu, którego autorem mogę być tylko ja, przynajmniej teoretycznie (przypomnę: Spoofing SMS, czyli fałszowanie nadawcy SMS-a).
Skoro dane na pewno będą pochodziły od niezaufanego odbiorcy, należy je traktować jako niezaufane. W zasadzie nawet jeśli nadawca jest zaufany, same dane i tak powinny być traktowane jako niezaufane, choćby dlatego, że:
- serwis jest pod kontrolą strony trzeciej, nie koniecznie zaufanej,
- dane między serwerem (serwisem) a aplikacją są przesyłane w sposób, który nie wyklucza ewentualnego tamperingu,
Czy przykłady zawierały jakiś mechanizm oczyszczania lub encodingu danych? Tak, przy czym ten mechanizm był (specjalnie) nieskuteczny. W pierwszym przypadku wyglądał on mniej więcej tak:
body = body.replace(/<([^<>]*?)>/g, '<$1>');
W założeniu ten regexp miał dokonywać następującej zamiany:
<script>alert(/XSS/);</script> <script>alert(/XSS/);</script>
Problem w tym, że choć zamiana była globalna (flaga g, zamieniane były wszystkie dopasowania), to nie była ona rekursywna. W rezultacie kod postaci:
<img onerror="javascript:alert(/Wanna play?/);" src="./notfound" id="<>" />
był przekształcany na postać:
<img onerror="javascript:alert(/Wanna play?/);" src="./notfound" id="<>" />
Dlaczego właśnie tak? Wynika to z konstrukcji regexpa. Znajduje on fragmenty, które zaczynają się od znaku < i kończą znakiem > przy czym w środku zawierają zero lub więcej znaków, które nie są < oraz >. Dopasowanie to robione jest w sposób niezachłanny (dlatego jest fragment *? w regexp).
Tak skonstruowany regexp w powyższym przykładzie operację zamiany wykonywał wyłącznie na wartości id, czyli na fragmencie <>. Całość tagu <img (...) > pozostaje natomiast bez zmian. Cóż, regexpy są fajne, ale trzeba umieć z nich korzystać... Poza tym z tego, że regexpy są fajne, wcale nie wynika, że nadają się do wszystkiego. Przykład? Proszę bardzo, choćby to: BBCode won't protect you from XSS.
Druga wersja przykładu "naprawia" błąd z wersji pierwszej. Funkcja replace wołana jest w pętli tak długo, jak długo jej wywołanie ma jakikolwiek efekt (następuje zamiana fragmentu stringu). W tym drugim wariancie sytuacja wygląda w sposób następujący (kolejne iteracje):
<img onerror="javascript:alert(/Wanna play?/);" src="./notfound" id="<>" /> <img onerror="javascript:alert(/Wanna play?/);" src="./notfound" id="<>" /> <img onerror="javascript:alert(/Wanna play?/);" src="./notfound" id="<>" />
W pierwszej linii znajdują się dane oryginalne. W drugiej - po pierwszej iteracji. Łatwo zauważyć, gdzie nastąpiła zmiana. W kolejnej iteracji następuje kolejna (i ostatnia przy okazji) zmiana danych. W rezultacie dane są "bezpieczne". Ale czy na pewno?
Oczywiście, że nie. W przykładowym rozwiązaniu payload został podzielony na dwa statusy:
<img src="./notfound" onerror="javascript:alert(/Have fun/);" alt=" " />
W takim przypadku regexp nie wykona żadnego podstawienia. W efekcie po połączeniu tych dwóch fragmentów na przykładowej stronie zostanie wstawiony następujący kod:
#thrts <img src="./notfound" onerror="javascript:alert(/Have fun/);" alt="</p><p>"> #thrts
Efekt zgodny z oczekiwaniami: XSS.
Oczywiście temu przykładowi krzywdę można zrobić na więcej niż jeden sposób. O innym ciekawym sposobie napiszę w kolejnym odcinku.
Tak, zamiast budować dziwne regexpy, równie dobrze można było zamienić po prostu wystąpienia znaków < oraz > na stosowne encje, ale nie o to chodziło w tym przykładzie.
Dobrze ujął to Douglas Crockford - "A mashup is a self inflicted cross-site scripting attack", a dziś pojawił się bardzo ciekawy artykuł w sprawie: http://jeremiahgrossman.blogspot.com/2010/07/third-party-web-widget-security-faq.html