Zastanawiałem się właśnie nad pewnym scenariuszem ataku, do którego przydatny byłby XMLHttpRequest. Scenariusz był raczej mało prawdopodobny z uwagi na mechanizm same-origin policy, ale spróbować można. W trakcie tej próby trafiłem na kolejny przykład moich "ulubionych" różnic między przeglądarkami. Chodzi mianowicie o same-origin policy dla XMLHttpRequest, a w zasadzie o różne postępowanie przeglądarek w przypadku, gdy same-origin policy jest naruszone. Okazuje się, że Interrnet Explorer w przypadku naruszenia zasad same-origin policy nie wyśle żądania, Firefox i Chrome (a może i inne przeglądarki) żądanie wyślą. Zresztą mają ku temu pewne uzasadnienie, ale o tym później.
Różnice między przeglądarkami: XMLHttpRequest
Szybka demonstracja
Do demonstracji tego przypadku wykorzystam http://bootcamp.threats.pl/lesson09/ i następujący payload XSS:
<script> if (!x) { var x = new XMLHttpRequest(); x.open("GET","http://bootcamp.threats.pl/lesson11/"); x.send(); setTimeout("alert(x.getAllResponseHeaders());",5000); } </script>
W tym przypadku wszystkie trzy rozważane przeglądarki, a więc Internet Explorer, Firefox oraz Chrome zadziałają zgodnie z oczekiwaniami. Żądanie za pośrednictwem XMLHttpRequest zostanie wysłane, a po 5 sekundach pojawi się komunikat zawierający nagłówki odpowiedzi, w tym nagłówek Set-Cookie.
Drugi przypadek będzie zawierał odwołanie do adresu http://threats.pl, czyli innego niż ten, na którym osadzony jest skrypt. Tu sytuacja wygląda nieco inaczej. W przypadku Internet Explorer żądanie nie zostanie wysłane, pojawi się natomiast błąd:
Message: Access is denied. Line: 54 Char: 1 Code: 0 URI: http://bootcamp.threats.pl/lesson09/index.php
W debuggerze wygląda to tak (nie należy sugerować się numerami linii, numer linii podawany w komunikacie błędu jest dla mnie zagadką):
Nieco inaczej przedstawia się sytuacja w przypadku Chromie i Firefox. Ten sam przykład w tych przeglądarkach wygląda tak w Chrome:
A tak w Firefox:
Skrypt wykonał się, żądanie zostało wysłane:
Warto jednak zwrócić uwagę na to, że żądanie wysłane przez przeglądarkę jest w pewnym stopniu "okrojone". Przykładowo "normalne" żądanie do http://threats.pl wysyłane przez Chrome wygląda tak:
GET http://threats.pl/ HTTP/1.1 Host: threats.pl Connection: keep-alive User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.1.249.1045 Safari/532.5 Referer: http://threats.pl/bezpieczenstwo-aplikacji-internetowych Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Encoding: gzip,deflate,sdch Accept-Language: pl-PL,pl;q=0.8,en-US;q=0.6,en;q=0.4 Accept-Charset: ISO-8859-2,utf-8;q=0.7,*;q=0.3 Cookie: PHPSESSID=c7febcc866ebab4c64cb664a615b7de6;
Zwracam uwagę na istnienie w tym żądaniu cookie PHPSESSID. W przypadku żądania wygenerowanego przez XMLHttpRequest nagłówki wyglądają nieco inaczej:
GET http://threats.pl/ HTTP/1.1 Host: threats.pl Connection: keep-alive User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.1.249.1045 Safari/532.5 Referer: http://bootcamp.threats.pl/lesson09/index.php Origin: http://bootcamp.threats.pl Accept: */* Accept-Encoding: gzip,deflate,sdch Accept-Language: pl-PL,pl;q=0.8,en-US;q=0.6,en;q=0.4 Accept-Charset: ISO-8859-2,utf-8;q=0.7,*;q=0.3
Chodzi mi tu przede wszystkim o brak nagłówka Cookie, choć przeglądarka stosowne cookie posiada i teoretycznie mogłaby je wysłać.
O co chodzi?
Szczerze mówiąc w pierwszej chwili nieco się zdziwiłem tym zachowaniem przeglądarki, ale później przypomniałem sobie, że jakiś czas temu "na topie" był temat Cross-site HTTP requests (patrz: HTTP access control) oraz Cross-Origin Resource Sharing i rzeczywiście, prawdopodobnie właśnie dlatego Firefox i Chrome zamiast tchórzliwie odmówić wysłania żądania, wysyłają je, bo być może w odpowiedzi będzie informacja, że takie żądanie jest dozwolone.
Odnośnie cookies: można je wysłać ustawiając atrybut withCookies na true, w związku z czym mogę wykorzystać XMLHttpRequest do cross-site request forgery, choć ten sam efekt mogę osiągnąć w inny sposób (tag img, oskryptowany formularz), choć może i użycie XMLHttpRequest będzie w pewnych scenariuszach łatwiejsze.
Jeśli chodzi o oryginalny scenariusz ataku, nad którym się zastanawiałem, to zgodnie z oczekiwaniami - nie powiódł się. Co prawda mogłem wysłać żądanie, ale nie byłem w stanie odczytać odpowiedzi serwera. Dobry przykład zastosowania zasady defence-in-depth, w którym mniej zaufana treść (w której byłem stanie umieścić XSS) jest serwowana z innej domeny, niż "treść główna".