Jak uniknąć Cross-Site Scripting w aplikacji?

Pomimo, że ataki Cross-Site Scripting (XSS) są znane od lat, nadal najpopularniejszą metody ochrony przed nimi pozostają proste techniki w stylu htmlencode. Dlaczego to nie działa i jak skutecznie chronić się przed XSS?

Ataki Cross-Site Scripting (XSS) są jedną z najbardziej rozpowszechnionych podatności w aplikacjach webowych. Ich skutki nie dotykają bezpośrednio właściciela systemu, mogą być jednak bardzo nieprzyjemne dla jego użytkowników - począwszy od kradzieży danych, a skończywszy na instalacji złośliwego oprogramowania. W każdym wypadku ich ujawnienie podważa kompetencje zaufanie do twórcy aplikacji jako kompetentnego dostawcy usług.

Najbardziej rozpowszechnioną metodą walki z XSS pozostają nadal proste metody "wycinania" z danych wejściowych znaków uznawanych za groźne, czyli przede wszystkim nawiasów trójkątnych (<,>) lub zamiana ich na odpowiednie encje HTML (&gt;, &lt;) - na przykład htmlencode (ASP.NET), escapeHtml (Java) czy htmlspecialchars (PHP). Popularność tych nieskutecznych w praktyce technik można wytłumaczyć ich rozpowszechnieniem w różnego rodzaju poradnikach projektowania aplikacji webowych, tak dostępnych w sieci jak i drukowanych.

Zobacz również:

  • Wyjaśniamy czym jest SD-WAN i jakie są zalety tego rozwiązania

Tego typu zabezpieczenie istotnie jest w stanie ochronić użytkownika aplikacji przed trywialnym "wstrzyknięciem" złośliwego kodu JavaScript w postaci kompletnego bloku - różnego rodzaju poradniki chętnie posługują się tutaj przykładem np. komentarzy w blogu, gdzie atakujący ma do dyspozycji praktycznie nieograniczoną przestrzeń. Taki wymarzony dla demonstracji htmlencode przykład dziurawego kodu wyglądałby mniej więcej tak:

<tr><td>Value:<td><%= request.getParameter("hackme") %>

Wykorzystanie tego przykładu polegałoby na wysłaniu do aplikacji parametru hackme o wartości w rodzaju <script>alert(1);</script>. I rzeczywiście, przed czymś takim htmlencode zapewne nas uchroni. Niestety, w atakach Cross-Site Scripting chodzi nie tyle o "wstrzyknięcie" koniecznie kodu HTML z kodem JavaScript, co o wstrzyknięcie czegokolwiek co zostanie zinterpretowane jako kod JavaScript. To ostatnie jednak zależy przede wszystkim od kontekstu, w którym "wstrzyknięty" kod się pojawi, a współczesne przeglądarki są w tym względzie niebywale liberalne, a dane pochodzące od użytkownika lądują w najmniej spodziewanych miejscach. Weźmy na przykład taki fragment dziurawej aplikacji:

<script type="text/javascript" language="JavaScript">

function resetFormFields() {

document.loginForm.USER.value = "<%= request.getParameter("hackme") %>";

}

</script>

W tym przypadku aplikacja wyświetla kod pochodzący od użytkownika już wewnątrz bloku <script> i żadne trójkątne nawiasy nie są w tym przypadku potrzebne. Czy htmlencode cokolwiek w tym przypadku pomoże? Nic a nic, co najwyżej trochę utrudni zadanie włamywaczowi jeśli będą mu potrzebne cudzysłowy i apostrofy. Takiego problemu może on jednak w ogóle nie mieć, jeśli uda mu się "wstrzelić" w atrybuty tagów HTML, które są interpretowane jako JavaScript (OnMouseOver, OnLoad, OnClick), pozwalają na podanie adresu URL zewnętrznego pliku JavaScript (<script src=...>) lub interpretują pseudo-adres URL javascript:, który pozwala na podanie bloku JavaScript. We współczesnych aplikacjach, szczodrze czerpiących z szerokich możliwości dynamicznego HTML, tego typu punktów ataku może być bardzo wiele. Techniki ich wykorzystywania można już liczyć w dziesiątkach, a kreatywność autorów ataków XSS można podziwiać na stronie HTML5 Security Cheatsheet.

Innym popularnym mitem dotyczącym ataków XSS jest przekonanie, że chroni przed nimi stosowanie BBCode. Istotnie, zadanie autorów aplikacji, które muszą pozwalac użytkownikowi na publikację komentarzy z jakimś formatowaniem jest raczej niewdzięczne. BBCode pozornie rozwiązuje ten problem, dając użytkownikowi zamknięty i teoretycznie niekompatybilny z HTML zestaw tagów. Tymczasem BBCode nie powinno być w ogóle traktowane jako forma ochrony przed atakami XSS (przykład - PEAR-14579). Problem polega na tym, że większość implementacji BBCode używa do zamiany BBCode z powrotem na kod HTML wyrażeń regularnych, które są podatne na kilka sztuczek, umożliwiających przemycenie złośliwego kodu do wynikowego HTML. Jedną z nich jest użycie znaku NULL (%00), który kończy przetwarzanie wyrażenia regularnego - ale niekoniecznie kopiowanie "wstrzykniętego" kodu HTML. Inną jest użycie odpowiedniego wektora HTML, który co prawda pasuje do wymyślonego przez autora wyrażenia regularnego, ale pozwala na przemycenie nadmiarowej informacji (przykładem był niedawny atak na Twitter).

Zalecenia

Jak powininen wyglądać poprawnie zaimplementowany mechanizm publikacji informacji pochodzących od użytkownika? Zgodnie z obecnym stanem wiedzy powinien on być oparty na formalnym parserze kodu HTML na wejściu i na generatorze drzewa HTML na wyjściu. Generowanie wyjściowego drzewa należy rozpocząć od pustego korzenia (root), a następnie dodawać do niego wyłącznie dopuszczone przez autora tagi w miarę postępów w parsowaniu danych wejściowych (o szczegółach technicznych można przeczytać w artykule Krzysztofa Kotowicza)

W przypadku aplikacji budowanych na bazie popularnych środowisk programistycznych warto wykorzystać gotowe biblioteki do "czyszczenia" danych wejściowych (patrz poradnik Data Validation na stronie OWASP) - na przykład ESAPI (na razie głównie Java), filtry walidujące (PHP) czy Web Protection Library (ASP.NET).

W celu komercyjnej reprodukcji treści Computerworld należy zakupić licencję. Skontaktuj się z naszym partnerem, YGS Group, pod adresem [email protected]

TOP 200