Rozwiązanie konkursu unpickle

05 lipca 2014, 22:50 | Teksty | 0 komentarzy
: zin o bezpieczeństwie - pobierz w pdf/epub/mobi.

Kilka dni temu w artykule o unpickle ogłosiliśmy konkurs, polegający na wykorzystaniu omawianej podatności w celu uzyskania XSS-a. Jeszcze tego samego dnia przyszło do nas pięć rozwiązań (ale najszybszy był Adam Dobrawy). W rozwiązaniach zastosowano trzy różne podejścia do problemu; w tej notce przedstawię ja wraz z krótkim opisem.

Zanim jednak do tego przejdę, najpierw pokażę małą sztuczkę, przydatną do znalezienia odpowiedniej metody wykorzystania podatności. Jak wiadomo, Python dysponuje interaktywną konsolą, w której polecenia wpisywane przez użytkownika są natychmiast wykonywane. Konsola może być też wywołana z poziomu skryptu pythonowego – metodą code.interact(). Zatem ciało funkcji malicious() jest następujące:

Wówczas, gdy wkleimy kod wynikowy do aplikacji, uzyskamy na serwerze dostęp do pythonowego shella w kontekście naszej aplikacji.

Konsola Pythona na serwerze

Konsola Pythona na serwerze

Jeśli używacie IPythona, można przejść również do niego:

 

IPython na serwerze

IPython na serwerze

IPython to alternatywna konsola interaktywna do Pythona, wzbogacona m.in. o przystępniejsze formatowanie wyjścia, łatwiejszy dostęp do pomocy, uzupełnianie atrybutów TAB-em, makra i wiele innych. Strona projektu przedstawia kilka podstawowych funkcji.

1. Utworzenie własnego obiektu z atrybutami year, monthday

Trzy rozwiązania wykorzystywały lukę w 39. linii kodu przykładowej aplikacji:

Aplikacja wstawia elementy date.year, date.month oraz date.day do szablonu, nie chroniąc się w żaden sposób przed XSS-em (zakłada, że będą one liczbami). Można zatem utworzyć dowolny obiekt z tymi atrybutami i wykonamy XSS-a. W każdym z rozwiązań użyto innego sposobu, by to osiągnąć.

Jak widać, w takim rozwiązaniu panowała dość duża dowolność w osiągnięciu celu. Wynik wykonywania:

XSS - metoda 1

XSS – metoda 1

2. Przeładowanie cgi.escape

Rozwiązanie zwycięskie – całkiem ciekawe, bo trwale psujące serwer w taki sposób, że każdy, kto trafi na jakąkolwiek stronę błędu zostanie XSS-owany. Spójrzmy na funkcję wyświetlającą błędy na serwerze:

Funkcja cgi.escape() służy do ochrony przed XSS-ami (odpowiednik htmlentities() z PHP). Widzimy, że funkcja jest wywoływana za każdym razem, gdy wyświetlany jest błąd – możemy więc funkcję przeładować i zmienić jej zachowanie.

Przez obiekt sys.modules możemy uzyskać dostęp do wszystkich zaimportowanych modułów w danej aplikacji. Bierzemy więc moduł cgi i zmieniamy działanie funkcji escape(). Zapis lambda x: "<h1>XSS here</h1>" jest równoważny:

Wynik wykonania:

XSS - metoda 2

XSS – metoda 2

Jako że działanie cgi.escape() zostaje trwale zmienione, od tej pory każde wyświetlenie komunikatu o błędzie powoduje wykonanie XSS-a. (dzieje się tak testowym serwerze; efekt wcale nie musiałby być trwały na prawdziwych serwerach rozrzuconych w chmurach/farmach korzystających z jakichś form wielowątkowości).

3. Uzyskanie dostępu do obiektu klasy MyHandler

Ten sposób został również użyty na moim zrzucie ekranowym w oryginalnym artykule. Jak było widać w kodzie aplikacji, wyświetlanie tekstu na stronie jest obsługiwane przez klasę MyHandler, poprzez odwołania do atrybutu wfile instancji tego obiektu, na którym z kolei wykonywano metodę write().

Zatem w chwili, gdy wykonujemy nasz kod poprzez unpickling, gdzieś w pamięci musi żyć obiekt tej klasy. Trzeba tylko znaleźć sposób na jego znalezienie.

Posłużymy się modułem inspect, który, jak podaje dokumentacja, służy do wyciągania informacji o żywych obiektach. W szczególności, metoda inspect.stack() zwraca pełny stos prowadzący do aktualnego miejsca w kodzie w postaci listy krotek, gdzie każda krotka składa się z sześciu elementów:

  1. Referencja do ramki stosu,
  2. Nazwa pliku,
  3. Numer linii kodu,
  4. Nazwa funkcji,
  5. Kontekst kodu,
  6. Indeks aktualnie wykonywanej linii w kontekście kodu.

Punkty 5. i 6. mogą brzmieć mało klarownie, ale i tak nie są nam potrzebne ;) Nas interesuje nazwa funkcji oraz referencja do ramki stosu. Jak pamiętamy, złośliwy kod w serwerze był wywoływany z funkcji do_GET() (tam były deserializowane dane). Poszukamy zatem w stosie wywołań funkcji „do_GET” i odwołamy się do jej ramki stosu. Z tej ramki stosu wyciągniemy z kolei zmienne lokalne (atrybut f_locals), co umożliwi uzyskanie referencji do obiektu self. Mając to wszystko za sobą, wystarczy już wywołać self.wfile.write() i możemy pisać na wyjściu cokolwiek chcemy ;)

I wynik wykonania:

XSS - metoda 3

XSS – metoda 3

Michał Bentkowski

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



Komentarze

Odpowiedz