Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book

XSS – jak wykonać Javascript bez małych liter?

07 kwietnia 2014, 11:10 | Teksty | komentarzy 8

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.

Przypominamy że publikowane tutaj informacje o lukach służą tylko celom edukacyjnym.Przed testami penetracyjnymi na realnych systemach, koniecznie uzyskaj zgodę właściciela!

Środowisko testowe

Pod adresem http://jsfiddle.net/k3AxS/3/show/ wystawiłem prostą stronę, na której można przetestować omawianą podatność.

xssm1

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>.

xssm2

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 &lt; &gt; &quot; 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 &#97; oraz &#x61; i zobaczmy co zostanie wyświetlone na wyjściu HTML.

xssm3

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=&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x31;&#x29;>

xssm4

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ę:

  1. Przeglądarka napotyka na tag <img src=123 onerror=&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x31;&#x29;>
  2. Przeglądarka próbuje pobrać obraz z zasobu 123, co się nie udaje.
  3. W tagu został zdefiniowany atrybut onerror, zostaje więc pobrana jego wartość.
  4. Wartość atrybutu zostaje zdekodowana do postaci alert(1).
  5. Kod alert(1) zostaje wykonany przez silnik JS.
  6. ???
  7. 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):

xssm5

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.

xssm6

Wyjaśnienie poszczególnych podpunktów:

  1. Odwołanie się do właściwości constructor, zwraca Function()
  2. Konstruujemy obiekt Function podając w argumencie alert(123).
  3. 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.

xssm7

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ść:

xssm8

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. „sortconstructoralertznajdują 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:

xssm9

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:

xssm10

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ń.

Michał Bentkowski

 

Spodobał Ci się wpis? Podziel się nim ze znajomymi:



Komentarze

  1. Amadeuszx

    Nie znam JS i sposób 2 zrobił na mnie powalające wrażenie.
    Bardzo fajny artykuł.

    Odpowiedz
  2. 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!

    Odpowiedz
  3. Piotr

    Weźmy jako przykład znak „A” – jego kod ASCII to 97, szesnastkowo 0×61.

    Wiem, czepiam sie, ale x61 to „a”

    Odpowiedz
  4. cvb

    „Stosujemy konstruktor Function() dlatego, że łatwo można go uzyskać dostać.” – dziwne zdanie

    Odpowiedz
  5. Kacper

    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 ;)

    Odpowiedz
    • Kacper

      Mialo byc „nie wszystkie litery beda dostepne bezposrednio”. Z tego powodu payloady sie robia troche dlugie…

      Odpowiedz

Odpowiedz