Podstawowa deserializacja Java z ObjectInputStream readObject
Reading time: 6 minutes
tip
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)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.
W tym poście zostanie wyjaśniony przykład użycia java.io.Serializable
i dlaczego nadpisywanie readObject()
może być niezwykle niebezpieczne, jeśli strumień przychodzący jest kontrolowany przez atakującego.
Serializable
Interfejs Java Serializable
(java.io.Serializable
) jest interfejsem znacznikowym, który Twoje klasy muszą implementować, jeśli mają być serializowane i deserializowane. Serializacja obiektów Java (zapisywanie) odbywa się za pomocą ObjectOutputStream
, a deserializacja (odczyt) za pomocą ObjectInputStream
.
Przypomnienie: Które metody są wywoływane automatycznie podczas deserializacji?
readObject()
– logika odczytu specyficzna dla klasy (jeśli zaimplementowana i prywatna).readResolve()
– może zastąpić deserializowany obiekt innym.validateObject()
– za pomocą wywołań zwrotnychObjectInputValidation
.readExternal()
– dla klas implementującychExternalizable
.- Konstruktory nie są wykonywane – dlatego łańcuchy gadżetów polegają wyłącznie na poprzednich wywołaniach zwrotnych.
Każda metoda w tym łańcuchu, która kończy się na wywołaniu danych kontrolowanych przez atakującego (wykonywanie poleceń, wyszukiwania JNDI, refleksja itp.) przekształca rutynę deserializacji w gadżet RCE.
Zobaczmy przykład z klasą Person, która jest serializowalna. Ta klasa nadpisuje funkcję readObject, więc gdy jakikolwiek obiekt tej klasy jest deserializowany, ta funkcja zostanie wykonana.
W przykładzie funkcja readObject klasy Person wywołuje funkcję eat()
jego zwierzaka, a funkcja eat()
psa (z jakiegoś powodu) wywołuje calc.exe. Zobaczymy, jak serializować i deserializować obiekt Person, aby wykonać ten kalkulator:
Poniższy przykład pochodzi z https://medium.com/@knownsec404team/java-deserialization-tool-gadgetinspector-first-glimpse-74e99e493649
import java.io.Serializable;
import java.io.*;
public class TestDeserialization {
interface Animal {
public void eat();
}
//Class must implements Serializable to be serializable
public static class Cat implements Animal,Serializable {
@Override
public void eat() {
System.out.println("cat eat fish");
}
}
//Class must implements Serializable to be serializable
public static class Dog implements Animal,Serializable {
@Override
public void eat() {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("dog eat bone");
}
}
//Class must implements Serializable to be serializable
public static class Person implements Serializable {
private Animal pet;
public Person(Animal pet){
this.pet = pet;
}
//readObject implementation, will call the readObject from ObjectInputStream and then call pet.eat()
private void readObject(java.io.ObjectInputStream stream)
throws IOException, ClassNotFoundException {
pet = (Animal) stream.readObject();
pet.eat();
}
}
public static void GeneratePayload(Object instance, String file)
throws Exception {
//Serialize the constructed payload and write it to the file
File f = new File(file);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(instance);
out.flush();
out.close();
}
public static void payloadTest(String file) throws Exception {
//Read the written payload and deserialize it
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
Object obj = in.readObject();
System.out.println(obj);
in.close();
}
public static void main(String[] args) throws Exception {
// Example to call Person with a Dog
Animal animal = new Dog();
Person person = new Person(animal);
GeneratePayload(person,"test.ser");
payloadTest("test.ser");
// Example to call Person with a Cat
//Animal animal = new Cat();
//Person person = new Person(animal);
//GeneratePayload(person,"test.ser");
//payloadTest("test.ser");
}
}
Wnioski (klasyczny scenariusz)
Jak widać w tym bardzo podstawowym przykładzie, "vulnerability" tutaj pojawia się, ponieważ metoda readObject() wywołuje inny kod kontrolowany przez atakującego. W rzeczywistych łańcuchach gadżetów tysiące klas zawartych w zewnętrznych bibliotekach (Commons-Collections, Spring, Groovy, Rome, SnakeYAML itp.) mogą być nadużywane – atakujący potrzebuje tylko jednego dostępnego gadżetu, aby uzyskać wykonanie kodu.
2023-2025: Co nowego w atakach deserializacji Java?
- 2023 – CVE-2023-34040: Deserializacja nagłówków rekordów błędów Spring-Kafka, gdy flagi
checkDeserExWhen*
są włączone, umożliwiła dowolne konstruowanie gadżetów z tematów publikowanych przez atakujących. Naprawiono w 3.0.10 / 2.9.11. ¹ - 2023 – CVE-2023-36480: Założenie zaufanego serwera klienta Java Aerospike zostało złamane – złośliwe odpowiedzi serwera zawierały zserializowane ładunki, które zostały zdeserializowane przez klienta → RCE. ²
- 2023 – CVE-2023-25581: Analiza atrybutu profilu użytkownika
pac4j-core
akceptowała zprefiksowane{#sb64}
bloby Base64 i zdeserializowała je pomimoRestrictedObjectInputStream
. Uaktualnij ≥ 4.0.0. - 2023 – CVE-2023-4528: Usługa JSCAPE MFT Manager (port 10880) akceptowała obiekty Java zakodowane w XML, co prowadziło do RCE jako root/SYSTEM.
- 2024 – Dodano wiele nowych łańcuchów gadżetów do ysoserial-plus(mod), w tym klasy Hibernate5, TomcatEmbed i SnakeYAML 2.x, które omijają niektóre stare filtry.
Nowoczesne środki zaradcze, które powinieneś wdrożyć
- JEP 290 / Filtrowanie serializacji (Java 9+) Dodaj listę dozwolonych lub zablokowanych klas:
# Akceptuj tylko swoje DTO i java.base, odrzucaj wszystko inne
-Djdk.serialFilter="com.example.dto.*;java.base/*;!*"
Przykład programowy:
var filter = ObjectInputFilter.Config.createFilter("com.example.dto.*;java.base/*;!*" );
ObjectInputFilter.Config.setSerialFilter(filter);
- JEP 415 (Java 17+) Fabryki filtrów specyficznych dla kontekstu – użyj
BinaryOperator<ObjectInputFilter>
, aby zastosować różne filtry w zależności od kontekstu wykonania (np. na każde wywołanie RMI, na każdego konsumenta kolejki wiadomości). - Nie wystawiaj surowego
ObjectInputStream
przez sieć – preferuj kodowania JSON/Binary bez semantyki wykonania kodu (Jackson po wyłączeniuDefaultTyping
, Protobuf, Avro itp.). - Ograniczenia obrony w głębokości – Ustaw maksymalną długość tablicy, głębokość, odniesienia:
-Djdk.serialFilter="maxbytes=16384;maxdepth=5;maxrefs=1000"
- Ciągłe skanowanie gadżetów – uruchamiaj narzędzia takie jak
gadget-inspector
lubserialpwn-cli
w swoim CI, aby przerwać budowę, jeśli niebezpieczny gadżet stanie się dostępny.
Zaktualizowana ściągawka narzędziowa (2024)
ysoserial-plus.jar
– fork społeczności z > 130 łańcuchami gadżetów:
java -jar ysoserial-plus.jar CommonsCollections6 'calc' | base64 -w0
marshalsec
– nadal odniesienie do generacji gadżetów JNDI (LDAP/RMI).gadget-probe
– szybkie odkrywanie gadżetów czarnej skrzynki przeciwko usługom sieciowym.SerialSniffer
– agent JVMTI, który drukuje każdą klasę odczytaną przezObjectInputStream
(przydatne do tworzenia filtrów).- Wskazówka dotycząca wykrywania – włącz
-Djdk.serialDebug=true
(JDK 22+), aby rejestrować decyzje filtrów i odrzucone klasy.
Szybka lista kontrolna dla bezpiecznych implementacji readObject()
- Uczyń metodę
private
i dodaj adnotację@Serial
(pomaga w analizie statycznej). - Nigdy nie wywołuj metod dostarczonych przez użytkownika ani nie wykonuj operacji I/O w metodzie – tylko odczytuj pola.
- Jeśli walidacja jest potrzebna, wykonaj ją po deserializacji, poza
readObject()
. - Preferuj implementację
Externalizable
i wykonuj jawne odczyty pól zamiast domyślnej serializacji. - Zarejestruj wzmocniony
ObjectInputFilter
nawet dla usług wewnętrznych (projekt odporny na kompromitację).
Odnośniki
- Spring Security Advisory – CVE-2023-34040 Deserializacja Java w Spring-Kafka (sierpień 2023)
- GitHub Security Lab – GHSL-2023-044: Niebezpieczna deserializacja w kliencie Java Aerospike (lipiec 2023)
tip
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)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.