Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book
XSS – jak wykonać Javascript bez małych liter?
Wstęp
XSS jest jednym z najczęściej opisywanych błędów bezpieczeństwa aplikacji internetowych. Na Sekuraku podatność miała już swój artykuł, było też kilka tekstów o XSS-ach w Google’u, a nawet o wykorzystaniu ich do ataku DDoS.
W tym tekście skupię się na jednym przypadku, w którym samo droga od znalezienia XSS-a do jego potwierdzenia (wykonania Javascriptu) jest dłuższa niż zwykle – mianowicie, gdy aplikacja zamienia w tekście podanym przez użytkownika wszystkie małe litery na wielkie. Jako że w JavaScripcie nie istnieje metoda EVAL ani ALERT, trzeba się trochę nagimnastykować, aby wykonać poprawny kod.
Środowisko testowe
Pod adresem http://jsfiddle.net/k3AxS/3/show/ wystawiłem prostą stronę, na której można przetestować omawianą podatność.
W pierwszym polu można wpisać dowolny kod HTML, który jest wykonywany w elemencie iframe poniżej przycisku Go. Na samym dole natomiast wypisany jest kod źródłowy po przetworzeniu go przez aplikację (czyli po zamianie małych liter na wielkie). Zalecam włączyć w przeglądarce jakieś narzędzie do analizy kodu stron internetowych, aby widzieć ewentualne błędy wykonania JS.
Zacznijmy od najprostszego możliwego payloadu, jakim jest <script>alert(1)</script>.
Payload nie wykonał się – aplikacja zamieniła „alert” na „ALERT”, a funkcja o takiej nazwie nie istnieje, więc został wywołany wyjątek ReferenceError.
Zanim przejdziecie do dalszej części tekstu, zachęcam, by poświęcić kilka minut i spróbować samemu się zastanowić jak w tym przykładzie utworzyć działający payload.
Sposób 1.
JavaScript jest językiem wrażliwym na wielkość liter ale HTML – nie. Wykorzystajmy więc możliwości, jakie daje nam sam HTML, by wykonać dowolny JS. Standard HTML-a definiuje encje (HTML entities), które są używane do zastępowania znaków. Najczęściej widzi się encje takie jak < > " odpowiadających odpowiednio znakom: < > „, których powszechność wynika z ochrony przed XSS-ami. Mimo wszystko nie są to jedyne encje, jakich możemy używać. Właściwie dowolny znak może zostać zapisany za pomocą encji według jednego z dwóch formatów:
- &#nnn; – gdzie nnn to reprezentacja liczbowa danego znaku w postaci dziesiętnej,
- &#xnnn; – gdzie nnn to reprezentacja liczbowa danego znaku w postaci szesnastkowej.
Weźmy jako przykład znak „a” – jego kod ASCII to 97, szesnastkowo 0x61. Spróbujmy użyć encji a oraz a i zobaczmy co zostanie wyświetlone na wyjściu HTML.
Przeglądarka wyświetla znaki „aa” – encje zostały więc zamienione na znaki, które reprezentują.
Dobrze, ale jak encje wykorzystać do XSS-a? Skorzystamy z często używanego tagu <IMG>:
<img src=123 onerror={payload}>
Przeglądarka próbuje pobrać obrazek o nazwie 123, co nie powinno się udać, wówczas zostanie wywołany kod JS z atrybutu onerror. Jako że jesteśmy w zawartości atrybutu, zanim cokolwiek zostanie wykonane, przeglądarka najpierw zamieni encje na odpowiadające im znaki. Zatem zapiszmy po prostu wewnątrz atrybutu onerror wartość „alert(1)” zapisaną samymi encjami:
<img src=123 onerror=alert(1)>
Udało się; mimo zamiany wszystkich małych liter na wielkie, kod JS został wykonany. Krótkie podsumowanie, jak ten kod został zinterpretowany przez przeglądarkę:
- Przeglądarka napotyka na tag <img src=123 onerror=alert(1)>
- Przeglądarka próbuje pobrać obraz z zasobu 123, co się nie udaje.
- W tagu został zdefiniowany atrybut onerror, zostaje więc pobrana jego wartość.
- Wartość atrybutu zostaje zdekodowana do postaci alert(1).
- Kod alert(1) zostaje wykonany przez silnik JS.
- ???
- Profit!
Sposób jak widać działa i jest dosyć prosty do zastosowania. Może się jednak zdarzyć, że wstrzyknięcie XSS-owe będziemy mieli bezpośrednio w tagu <script> i nie będziemy mogli „uciec” z tego tagu. Potrzebna będzie metoda na wykonanie alert(1) w „czystym” JS.
Sposób 2.
W tym sposobie przyjmujemy założenie, że cały payload musi zmieścić się w tagu <script>. Jak już pokazywałem wcześniej, nie można użyć typowego „alert(1)” ze względu na zamianę na „ALERT(1)”, co powoduje błąd. Zanim przygotujemy odpowiedni payload, omówimy sobie kilka cech JavaScriptu, które będziemy musieli wykorzystać.
Po pierwsze, w JavaScripcie istnieją dwie metody odwoływania się do właściwości obiektu: notacja kropkowa (dot notation) i notacja z nawiasami kwadratowymi (square bracket notation). Załóżmy, że chcemy przeczytać wartość pola title obiektu document, spróbujmy użyć obu notacji (na przykładzie testuję kod w Firebugu):
Notacja z nawiasami kwadratowymi daje tę przewagę, że nazwa pola znajduje się w stringu – a na stringach można wykonywać pewne operacje (takie jak konkatenacja), których nie da się przeprowadzać na nazwach w notacji kropkowej.
Po drugie, warto wiedzieć, że obok często używanej funkcji eval() (która po prostu wykonuje kod javascriptowy podany w parametrze), istnieje jeszcze konstruktor Function(), który pozwoli nam osiągnąć ten sam efekt.
Zapis:
var x = Function(„alert(1)”);
x();
jest równoważny
var x = function(){ alert(1); };
x();
Stosujemy konstruktor Function() dlatego, że łatwo można uzyskać do niego dostęp. Każdy obiekt w JavaScripcie ma właściwość constructor, która, w przypadku funkcji, zwraca właśnie Function().
Na rysunku poniżej pokazałem to na przykładzie.
Wyjaśnienie poszczególnych podpunktów:
- Odwołanie się do właściwości constructor, zwraca Function()
- Konstruujemy obiekt Function podając w argumencie alert(123).
- Wywołujemy skonstruowany obiekt Function. Efekt – wyświetlenie alertu z tekstem 123.
W powyższych przykładach używaliśmy ciągle słowa alert na samym początku każdej z linii, więc wciąż było jedno słowo składające się z małych liter będące poza stringiem. Aby to obejść, używamy obiektu [] (tablica), który ma zdefiniowaną funkcję sort.
Udało się wywołać alert, mając kod javascriptowy, w którym wszystkie małe litery są wewnątrz stringów.
Pamiętamy, że na stringach zdefiniowana jest operacja konkatenacji, a więc 'sort’ === 's’+’o’+’r’+’t’. Musimy tylko wymyślić skąd wziąć te pojedyncze litery i będziemy w stanie taki string zbudować.
Skorzystamy z cechy Javascriptu, polegającej na tym, że jeżeli wykonujemy operacje dodawania, w których jeden z operandów jest stringiem, to drugi operand jest zamieniany na string i robiona jest konkatenacja. Dodatkowo, wykorzystamy następujące tożsamości:
- !0 === true
- !1 === false
- 1/0 === Infinity
- „”+{} === „[object Object]”
Połączmy wszystko w jedną całość:
Utworzyłem w zmiennej A stringa o wartości „truefalseInfinity[object Object]”. Pamiętacie ten wcześniejszy sposób na wywołanie alert(1) za pomocą [][’sort’][’constructor’](’alert(1)’)()? Po dokładnym przyjrzeniu się, możecie zauważyć, że wszystkie potrzebne litery, tj. „sortconstructoralert” znajdują się w stringu A.
Weźmy dla przykładu string „sort”, zauważmy, że litery s, o, r, t znajdują się w stringu A: „truefalseInfinity[object Object]”. A zatem możemy zapisać „sort” w postaci A[7]+A[18]+A[1]+A[0]. Analogicznie pozostałe słowa:
Pozostało już tylko połączyć wszystko w jedną całość, tj. wrzucić tak utworzone stringi we wcześniej przygotowany payload i dodać tag script. Powstanie wówczas:
<script>
A = „” + !0 + !1 + 1/0 + {};
[][A[7]+A[18]+A[1]+A[0]][A[22]+A[18]+A[10]+A[7]+A[0]+A[1]+A[2]+A[22]+A[0]+A[18]+A[1]](A[5]+A[6]+A[3]+A[1]+A[0]+'(1)’)()
</script>
Sprawdźmy czy payload zadziała:
Alert wyświetlony; cel osiągnięty – wykonaliśmy kod JS bez używania małych liter.
Podsumowanie
Przedstawiłem w tekście dwa sposoby na poradzenie sobie z sytuacją, w której nie możemy używać małych liter w payloadzie XSS. Okazuje się, że wykorzystując odpowiednio pewne cechy HTML-a lub JavaScriptu, możliwe jest wykonanie kodu pomimo ograniczeń.
Nie znam JS i sposób 2 zrobił na mnie powalające wrażenie.
Bardzo fajny artykuł.
Inny, dosc prosty patent, z ktorego korzystam osobiscie:
– zamieniony na uppercase da nam wiec jedyne co musimy zrobic to nazwac nasz plik „duzymi literami” ;-)
Polecam również http://jsfuck.com ;-)
Pzdr!
Bardzo ładnie filtrujecie przed XSS’ami ;-) przez co wycięło dość istotny fragment mojego komentarza – check this -> http://pastebin.com/58tMZP3q
Wygląda na to, że wordpress „połknął” fragmenty komentarza. Przypuszczam, że chodziło o zapis taki jak na obrazku: http://i.imgur.com/lunhteL.png
Weźmy jako przykład znak „A” – jego kod ASCII to 97, szesnastkowo 0×61.
Wiem, czepiam sie, ale x61 to „a”
„Stosujemy konstruktor Function() dlatego, że łatwo można go uzyskać dostać.” – dziwne zdanie
Bawilem sie wczesniej ta druga technika. Generalnie mozna doprowadzic do XSS bez stosowania jakichkolwiek znakow alfanumerycznych. Np:
[][(![]+[])[+!![]+!![]+!![]]+([]+{})[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][([]+{})[+!![]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]]+(![]+[])[+!![]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+[]]+([]+{})[+!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+([]+{})[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+(!![]+[])[+!![]+!![]+!![]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+!![]]+([]+{})[+!![]+!![]+!![]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(![]+[])[+!![]+!![]]+(!![]+[])[+!![]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]])()((+!![]+[]))
To nic innego jak „alert(1)”. Problem jest taki, ze niestety nie wszystkie litery nie beda dostepne i trzeba kombinowac ;)
Mialo byc „nie wszystkie litery beda dostepne bezposrednio”. Z tego powodu payloady sie robia troche dlugie…