Michał Zalewski w książce The Tangled Web zawarł następującą myśl:
The only reasonable approach to tag sanitization is to employ a realistic parser to translate the input document into a hierarchical in-memory document tree, and then scrub this representation for all unrecognized tags and parameters, as well as any undesirable tag/parameter/value configurations.
Niestety, często programiści myślą, że są w stanie oczyszczać HTML łatwiej. I wciąż pokutuje tutaj podejście oparte na blackliście niedopuszczalnych tagów/atrybutów/zdarzeń. Takie podejście to proszenie się o kłopoty.
Zacznijmy jednak trochę inaczej. Atakujący przez XSS chce doprowadzić do wykonania swojego kodu w przeglądarce ofiary. Najlepiej, jeśli ten kod wykona się automatycznie. Może być również tak, że aktywacja wstrzykniętego kodu odbędzie się w odpowiedzi na akcję użytkownika, przy czym ta akcja powinna być jak najbardziej prawdopodobna. XSS, który aktywuje się tylko wtedy, gdy użytkownik najedzie myszką nad jeden piksel po czym go kliknie trzy razy trzymając się jednocześnie za lewe ucho, jest umiarkowanie przydatny.
Jak atakujący może wstrzyknąć swój kod tak, by osiągnąć swój cel? Pierwsza metoda to oczywiście wstrzyknięcie tagu script, co w podstawowej formie przyjmuje postać:
<script>alert(0)</script>
Naiwny sposób "radzenia sobie" z tym tematem polega na przykład na usunięciu słowa script. Przy czym zagadka - co stanie się wówczas w przypadku takiego kodu (payloadu):
<sscriptcript>alert(0)</sscriptcript>
Zasłonę milczenia spuśćmy też na programistów blokujących wystąpienie script, ale jednocześnie przepuszczających Script czy sCript. Tak, to 2012 rok, a nadal takie pomysły pokutują...
Innym pomysłem na blokowanie wykonania kodu jest modyfikacja "niebezpiecznych" słów, na przykład w taki sposób:
script -> scXipt
script -> scrXipt
Takie podejście można nawet zaobserwować w Internet Explorer w działaniu filtru anti-XSS. Przykład (link do sprawdzenia):
<input type="submit" value="Zaloguj" /><input type="hidden" name="target" value=""><script>alert#/xss/#</script><foo bar="" />
W powyższym przykładzie kod JavaScript został "zepsuty" przez przeglądarkę, dzięki czemu reflected XSS nie mógł się aktywować.
Blokowanie tagu script oczywiście nie rozwiązuje problemu z XSS. Innym popularnym sposobem osadzania kodu jest na przykład następująca konstrukcja:
<img src=evil.png onerror=alert(/xss/) />
W tym przypadku przeglądarka próbuje pobrać obrazek evil.png, którego oczywiście nie ma, a więc wywołuje handler onerror. Gdyby obrazek był, zamiast onerror można byłoby użyć zdarzenia onload.
Z innych ciekawych zdarzeń można wymienić również:
- onmouseover,
- onclick,
- (...),
W miejsce (...) należy wstawić całą masę przeróżnych zdarzeń, które można wykorzystać do automatycznej aktywacji wstrzykniętego payloadu.
Jak wykorzystać zdarzenie onmouseover do automatycznego wykonania kodu? Przecież by to zdarzenie się aktywowało, użytkownik musi najechać myszką na ten konkretny element strony? Cóż, poza JavaScript jest jeszcze coś takiego jak CSS. Skoro atakujący jest w tagu HTML i jest w stanie dodawać swoje atrybuty, prawdopodobnie będzie w stanie dodać swój atrybut style i w nim zadbać o to, by myszka na pewno znalazła się nad nim (odpowiednie ustawienie wielkości i pozycji elementu).
Wróćmy teraz do tego naiwnego filtra, który ma przeciwdziałać XSS. Podejście polegające na modyfikacji "niebezpiecznych" słów kluczowych będzie działało tylko wtedy, jeśli ich lista będzie kompletna. I tu właśnie jest problem - listy najczęściej nie są kompletne, a jeśli takie się wydają, to rzeczywiście - wydają się.
Chyba nie ma nikogo, kto nie słyszał pojęcia HTML5, prawda? A "skutkiem ubocznym" pojawienia się HTML5 są zarówno nowe tagi, jak również nowe zdarzenia i atrybuty, które można wykorzystać do automatycznego uruchomienia kodu. Dość znany jest ten przykład:
<input type="text" onfocus="alert(0)" autofocus="autofocus" />
W zdarzeniu onfocus znajduje się kod, który atakujący chce uruchomić. Nie musi on jednak czekać na to, by ofiara łaskawie przeszła do pola, w którym payload został osadzony. Zamiast tego może skorzystać z HTML5 do automatycznego ustawienia focus w tym polu (tak, dokładnie - atrybut autofocus).
Jak atakujący może obejść filtry oparte na czarnej liście niedozwolonych słów? Bardzo prosto - wystarczy, że znajdzie taki tag/zdarzenie/atrybut, które na tej liście się nie znajduje. A jest z czego wybierać... I w tym miejscu odsyłam wszystkich do cytatu z początku tego wpisu.
P.S. W roli małpy z brzytwą wystąpił gościnnie HTML5 :)
P.S. I pozwolę sobie przypomnieć: Lekcja 23: Oczyszczanie HTML jest trudne