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

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?

  1. readObject() – logika odczytu specyficzna dla klasy (jeśli zaimplementowana i prywatna).
  2. readResolve() – może zastąpić deserializowany obiekt innym.
  3. validateObject() – za pomocą wywołań zwrotnych ObjectInputValidation.
  4. readExternal() – dla klas implementujących Externalizable.
  5. 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

java
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 pomimo RestrictedObjectInputStream. 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ć

  1. JEP 290 / Filtrowanie serializacji (Java 9+) Dodaj listę dozwolonych lub zablokowanych klas:
bash
# Akceptuj tylko swoje DTO i java.base, odrzucaj wszystko inne
-Djdk.serialFilter="com.example.dto.*;java.base/*;!*"

Przykład programowy:

java
var filter = ObjectInputFilter.Config.createFilter("com.example.dto.*;java.base/*;!*" );
ObjectInputFilter.Config.setSerialFilter(filter);
  1. 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).
  2. Nie wystawiaj surowego ObjectInputStream przez sieć – preferuj kodowania JSON/Binary bez semantyki wykonania kodu (Jackson po wyłączeniu DefaultTyping, Protobuf, Avro itp.).
  3. Ograniczenia obrony w głębokości – Ustaw maksymalną długość tablicy, głębokość, odniesienia:
bash
-Djdk.serialFilter="maxbytes=16384;maxdepth=5;maxrefs=1000"
  1. Ciągłe skanowanie gadżetów – uruchamiaj narzędzia takie jak gadget-inspector lub serialpwn-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:
bash
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ą przez ObjectInputStream (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()

  1. Uczyń metodę private i dodaj adnotację @Serial (pomaga w analizie statycznej).
  2. Nigdy nie wywołuj metod dostarczonych przez użytkownika ani nie wykonuj operacji I/O w metodzie – tylko odczytuj pola.
  3. Jeśli walidacja jest potrzebna, wykonaj ją po deserializacji, poza readObject().
  4. Preferuj implementację Externalizable i wykonuj jawne odczyty pól zamiast domyślnej serializacji.
  5. Zarejestruj wzmocniony ObjectInputFilter nawet dla usług wewnętrznych (projekt odporny na kompromitację).

Odnośniki

  1. Spring Security Advisory – CVE-2023-34040 Deserializacja Java w Spring-Kafka (sierpień 2023)
  2. 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