Bootamp XXIV: co było nie tak w przykładzie

Dzisiaj pierwsza część rozwinięcia rozwiązania zadania XXIV z bootcamp.

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:

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>

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:

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 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=”<>” />

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:

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 </p><p>”> <a href=#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.

Oryginał tego wpisu dostępny jest pod adresem Bootamp XXIV: co było nie tak w przykładzie

Autor: Paweł Goleń