PHP Object Injection i ZendFramework2

24 sierpnia 2015, 09:24 | Teksty | komentarzy 5
: zin o bezpieczeństwie - pobierz w pdf/epub/mobi.

Podatność PHP Object Injection jest już czytelnikom Sekuraka znana; opisywaliśmy ją rok temu, pokazując na przykładzie błędu tego typu w Joomli jak doprowadzić do usuwania dowolnego pliku na dysku. Tym razem przedstawimy nasz autorski research, w jaki sposób można wykorzystać tę podatność do wykonania dowolnego kodu, jeśli odnajdziemy ją w aplikacji korzystającej z bardzo popularnego frameworka dla PHP: Zend.

Krótkie przypomnienie

W telegraficznym skrócie, podatność PHP Object Injection wiązała się z wywołaniem funkcji unserialize() z danymi pochodzącymi od użytkownika. Dzięki temu atakujący mógł zainicjalizować dowolne klasy w podatnym kodzie, a także – przy odrobinie szczęścia – wpłynąć na przepływ działania programu. Było to możliwe dzięki magicznym metodom wywoływanym automatycznie przez PHP w pewnych sytuacjach. W kontekście tego artykułu będą nas interesować konkretnie następujące metody:

  • __destruct() – wywoływana w momencie niszczenia obiektu (destruktor),
  • __toString() – wywoływana, gdy dana klasa musi zostać zinterpretowana jako string (np. w przypadku porównania $obj == "abc"),
  • __call() – wywoływana, gdy próbowano odwołać się do metody nieistniejącej w danej klasie. Na przykład: załóżmy, że próbujemy wykonać $obj->jakasMetoda("abc"), jednak jakasMetoda() nie jest zdefiniowana dla klasy kryjącej się pod zmienną $obj. PHP spróbuje wówczas wywołać metodę __call("jakasMetoda", array("abc")).

Exploitujemy Zenda!

W przykładzie użyty jest Zend w wersji 2.3.3. Działanie poszczególnych klas może się minimalnie różnić w innych wersjach, ale ogólny przepływ działania powinien być taki sam dla dowolnej wersji Zenda w wersjach 2.x.

Spójrzmy zatem co ciekawego kryją w sobie klasy Zenda. Jak to zazwyczaj bywa, zaczynamy od destruktorów. Najpierw destruktor klasy \Zend\Http\Response\Stream

Wygląda nieźle :) Jeśli $this->cleanup będzie równe true, wówczas wykona się unlink() (czyli usuwanie pliku na dysku) dla ścieżki pobranej z $this->streamName. Już samo usuwanie plików jest niezłą podatnością samą w sobie, ale w tym przypadku zwracam uwagę na ten kod z innego powodu – mianowicie argumentem dla funkcji unlink() musi być zmienna znakowa, zatem jeśli podstawimy pod $this->streamName jakąś klasę, zostanie w niej wykonane  __toString().  Co z kolei prowadzi nas do klasy \Zend\Tag\Cloud:

Spójrzmy w takim razie na render():

W Zendzie stosowane są gettery i settery, toteż  getItemList() zwraca  $this->tags, zaś getTagDecorator() zwraca  $this->tagDecorator. Powyższy kod pozwala w takim razie wykonać metodę render() dla klasy kryjącej się pod  $this->tagDecorator przekazując jako argument $this->tags.

Metodę render() odnajdujemy w klasie \Zend\View\Renderer\PhpRenderer.

Pamiętamy, że mieliśmy kontrolę nad tym, jaki argument jest przesyłany do tej funkcji. Na samym początku ciała funkcji jest sprawdzenie czy $nameOrModel (czyli nasz argument) jest instancją klasy Model. Zadbamy o to, by tak nie było, a więc pomińmy ten blok kodu i patrzymy dalej:

Wykonana zostanie funkcja  addTemplate()

… która po prostu dopisuje do tablicy $this->__templates argument, który został do niej przekazany.

Wracamy do  render():

Pobierany jest ostatni element z tablicy $this->templates, a następnie jest on przekazywany do metody resolver(). Zobaczmy czym ta metoda się zajmuje.

Tym razem wywoływana jest metoda resolve() na obiekcie kryjącym się pod $this->__templateResolver. Tradycyjnie szukalibyśmy jakiejś ciekawej klasy zawierającej resolve(), wyjątkowo jednak zastosujemy inne podejście… Gdybyśmy pod $this->__templateResolver schowali instancję tej samej klasy, na którą teraz patrzymy (a więc \Zend\View\Renderer\PhpRenderer), silnik PHP nie odnalazłby metody resolve() – bowiem nie jest ona zdefiniowana – dlatego wywołałby magiczną metodę __call().

Mamy wykonanie funkcji call_user_func_array(), w której mamy kontrolę nad pierwszym parametrem, co oznacza, że możemy wywołać dowolną metodę w dowolnej klasie. Szczęśliwie – ze względu na wcześniejszy przepływ programu – mamy też kontrolę nad pierwszym argumentem przekazywanym do tej funkcji!

Wydawałoby się, że w tym momencie moglibyśmy po prostu odpalić system() lub assert(), ale, z przyczyn nie do końca dla mnie jasnych, takie rozwiązanie nie zadziałało.

Z ratunkiem przychodzi jednak sam Zend i klasa \Zend\Serializer\Adapter\PhpCode z interesującą metodą unserialize():

Nie mogło być lepiej – wywoływany jest eval() z argumentem przekazywanym do funkcji.

Tym samym udało nam się odnaleźć ciąg klas w Zendzie, który doprowadził do wykonania dowolnego kodu PHP.

Teraz należy połączyć wszystkie klocki w jedno i napisać kod, który zbuduje odpowiedni łańcuch klas, zgodnie z powyższym opisem. W poniższym przykładzie exploit wykona funkcję phpinfo().

Aby upewnić się, że kod rzeczywiście zadziała, wziąłem prostą, przykładową aplikację w Zendzie (ZF2 Skeleton Application) i dopisałem w niej jedną linię kodu…

Na (Rys 1.) pokazuję zaś, że w istocie phpinfo() zostanie wykonane w wyniku wykorzystania Object Injection.

Rys 1. phpinfo() przez deserializację

Rys 1. phpinfo() przez deserializację

Podsumowanie

Pokazaliśmy, że podatność PHP Object Injection w aplikacji korzystając z frameworka Zend może zostać wykorzystana do wykonania dowolnego kodu PHP po stronie serwera.

Należy zaznaczyć, że nie jest to problem bezpieczeństwa samego Zenda – klasy, które zostały użyte w łańcuchu są używane w frameworku do realizacji jego zwykłych funkcjonalności. To programiści aplikacji webowych muszą pamiętać, aby nigdy nie uruchamiać funkcji unserialize() na niezaufanych danych.

Michał Bentkowski, realizuje testy penetracyjne w Securitum.

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



Komentarze

  1. Q

    Warto dodać że to dotyczy wszystkich języków programowania (no prawie wszystkich) i leniwych „programistów”. Dlatego warto serializować/deserializować dane do formatów typu JSON/XML/CHGW za pomocą metod do tego przeznaczonych.

    Odpowiedz
  2. Sebastian

    A kto napisał ten art?

    Odpowiedz

Odpowiedz