Deserialization ya Msingi ya Java na ObjectInputStream readObject

Tip

Jifunze na fanya mazoezi ya AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Jifunze na fanya mazoezi ya GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Jifunze na fanya mazoezi ya Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks

Katika chapisho hiki kutafafanuliwa mfano unaotumia java.io.Serializable na kwa nini kuandika upya readObject() kunaweza kuwa hatari sana ikiwa stream inayokuja inadhibitiwa na mshambulizi.

Serializable

Interface ya Java Serializable (java.io.Serializable) ni interface ya alama ambayo madarasa yako lazima yaitekeleze ikiwa yatawekwa kuwa serialized na deserialized. Ku-serialize (kuandika) vitu vya Java hufanywa na ObjectOutputStream na deserialization (kusoma) hufanywa na ObjectInputStream.

Kumbusho: Ni methods gani huitishwa kwa njia ya kimyakimya wakati wa deserialization?

  1. readObject() – mantiki ya kusoma maalum kwa darasa (ikiwa imetekelezwa na private).
  2. readResolve() – inaweza kubadilisha object iliyodeserialized na nyingine.
  3. validateObject() – kupitia callbacks za ObjectInputValidation.
  4. readExternal() – kwa madarasa yanayotekeleza Externalizable.
  5. Constructors hazitekelezwi – kwa hiyo gadget chains zinategemea tu callbacks zilizotajwa hapo juu.

Method yoyote katika mfuatano huo inayofikia kutumia attacker-controlled data (command execution, JNDI lookups, reflection, etc.) inageuza taratibu ya deserialization kuwa RCE gadget.

Tuchunguze mfano wa class Person ambayo ni serializable. Darasa hili linaandika upya function ya readObject, hivyo wakati kitu chochote cha darasa hili kitakapokuwa deserialized, function hii itaendeshwa.
Katika mfano, function ya readObject ya class Person inaita function eat() ya mnyama wake wa kipenzi na function eat() ya Dog (kwa sababu fulani) inaita calc.exe. Tutaona jinsi ya ku-serialize na ku-deserialize object ya Person ili kuendesha calculator hii:

Mfano ufuatao unatoka kwa 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");
}
}

Hitimisho (senario la jadi)

Kama unavyoona katika mfano huu wa msingi kabisa, “vulnerability” hapa inaonekana kwa sababu njia ya readObject() inaitisha code nyingine iliyodhibitiwa na mshambulizi. Katika real-world gadget chains, maelfu ya classes zilizomo katika maktaba za nje (Commons-Collections, Spring, Groovy, Rome, SnakeYAML, etc.) zinaweza kutumika vibaya – mshambulizi anahitaji tu one reachable gadget ili kupata code execution.


2023-2025: What changed in real-world Java deserialization bugs?

Matukio ya hivi karibuni ni ukumbusho mzuri kwamba ObjectInputStream bugs si tena tu “upload a .ser file to a legacy HTTP endpoint”:

  • Broker / queue consumers: Spring-Kafka (CVE-2023-34040) ilionyesha kwamba deserializing exception headers kutoka kwa attacker-controlled topics inatosha ikiwa consumer inawezeshwa vigezo visivyo vya kawaida checkDeserExWhen*.
  • Client-side trust of remote servers: the Aerospike Java client (CVE-2023-36480) ilideserialize objects zilizopokelewa kutoka kwa server. Jibu la vendor lilikuwa muhimu: clients mpya waliondoa Java runtime serialization/deserialization support badala ya kujaribu kuihifadhi nyuma ya filter dhaifu.
  • “Restricted” streams are often still too broad: pac4j-core (CVE-2023-25581) ilijaribu kulinda deserialization kwa RestrictedObjectInputStream, lakini seti ya darasa zilizokubaliwa bado ilikuwa kubwa vya kutosha kufanya gadget abuse iwezekanavyo.

Somo la kushambulia ni kwamba mpaka wa kuamini hatari mara nyingi si “user uploads a blob”, bali “some component the developer considered trusted can inject bytes into a stream that eventually reaches readObject()”.

Ikiwa unahitaji low-noise reachability checks kabla ya kutumia muda kwenye utafiti wa gadget kamili, tumia kurasa maalum za Java kwa:

Java DNS Deserialization, GadgetProbe and Java Deserialization Scanner

readObject() anti-patterns that still create gadget entrypoints

Hata kama class yako yenyewe sio gadget ya wazi ya RCE, mifumo ifuatayo inatosha kufanya iwe exploitable wakati objects zinazodhibitiwa na mshambulizi zikiingizwa ndani ya graph:

  1. Kuita overridable methods au interface methods kutoka readObject() (pet.eat() katika PoC hapo juu ni mfano wa kawaida).
  2. Kufanya lookups, reflection, class loading, expression evaluation, au JNDI operations wakati wa deserialization.
  3. Kupitia attacker-controlled collections au maps, ambazo zinaweza kusababisha hashCode(), equals(), comparators, au transformers kama athari za pembeni.
  4. Kusajili ObjectInputValidation callbacks ambazo hufanya post-processing hatarishi.
  5. Kudhani “private readObject()” inatosha kama ulinzi. Inadhibiti tu dispatch semantics; haifanyi deserialization kuwa salama.

Modern mitigations you should deploy

  1. JEP 290 / Serialization Filtering (Java 9+) Tumia allow-list na mipaka ya graph ya wazi:
-Djdk.serialFilter="com.example.dto.*;java.base/*;maxdepth=5;maxrefs=1000;maxbytes=16384;!*"
  1. Apply a filter on every untrusted stream, not just globally:
try (var ois = new ObjectInputStream(input)) {
var filter = ObjectInputFilter.Config.createFilter(
"com.example.dto.*;java.base/*;maxdepth=5;maxrefs=1000;!*"
);
ois.setObjectInputFilter(filter);
return (Message) ois.readObject();
}
  1. JEP 415 (Java 17+) Context-Specific Filter Factories Pendelea hii pale JVM moja inapo kuwa na muktadha kadhaa za deserialization (RMI, cache replication, message consumers, admin-only imports) na kila mmoja anayehitaji allow-list tofauti.
  2. Keep readObject() boring Wito tu defaultReadObject() / explicit field reads, kisha fanya ukaguzi mkali wa invariant. Usifanye I/O, logging inayoreferensa objects zinazodhibitiwa na mshambulizi, dynamic lookups, au method calls kwenye sub-objects zilizodeserialize.
  3. If possible, remove Java native serialization from the design The Aerospike fix ni mfano mzuri: wakati feature haisemi lazima, kuondoa matumizi ya readObject() / writeObject() mara nyingi ni salama zaidi kuliko kujaribu kudumisha filters kamili milele.

Detection and research workflow

  • ysoserial bado ni msingi kwa validation ya gadget na probes za haraka za RCE/URLDNS.
  • marshalsec bado inafaida wakati sink inageuka katika eneo la JNDI/LDAP/RMI.
  • GadgetInspector inafaa unapokuwa na target jars na unahitaji kutafuta application-specific gadget chains.
  • Java 17 iliongeza jdk.Deserialization Flight Recorder event, ambayo inasaidia kuona mahali ObjectInputStream inatumiwa kweli na kama filters zinawekwa.

Quick checklist for secure readObject() implementations

  1. Fanya method iwe private na weka annotations za serialization hooks kwa @Serial ili compilers wazipate signatures zisizotangazwa vizuri.
  2. Piga defaultReadObject() kwanza isipokuwa kama una sababu thabiti ya kusoma mwenyewe object graph nzima.
  3. Tumia kila nested object kama iliyo attacker-controlled hadi itakapothibitishwa.
  4. Kamwe usiite methods kwenye collaborators zilizodeserialize kutoka ndani ya readObject().
  5. Panga code review pamoja na ukaguzi wa ObjectInputFilter; “safe-looking readObject() code” haitoshi ikiwa stream bado inakubali classes za aina yoyote.

References

Tip

Jifunze na fanya mazoezi ya AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Jifunze na fanya mazoezi ya GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Jifunze na fanya mazoezi ya Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks