使用 ObjectInputStream readObject 的基本 Java 反序列化

Tip

学习并实践 AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
学习并实践 GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
学习并实践 Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) 浏览用于评估路线的 完整 HackTricks Training 目录ARTA/GRTA/AzRTA)以及 Linux Hacking Expert (LHE)

支持 HackTricks

在这篇文章中将解释一个使用 java.io.Serializable 的示例,以及为什么如果输入流由攻击者控制,覆盖 readObject() 会极其危险

Serializable

Java 的 Serializable 接口 (java.io.Serializable) 是一个标记接口,如果类要被序列化反序列化,必须实现它。Java 对象的序列化(写入)由 ObjectOutputStream 完成,反序列化(读取)由 ObjectInputStream 完成。

提醒:在反序列化期间隐式调用了哪些方法?

  1. readObject() – 类特定的读取逻辑(如果已实现且为 private)。
  2. readResolve() – 可以用另一个对象替换反序列化得到的对象。
  3. validateObject() – 通过 ObjectInputValidation 回调进行。
  4. readExternal() – 用于实现 Externalizable 的类。
  5. 构造函数不会被执行 – 因此 gadget chains 完全依赖于上述回调。

在该链中,任何最终调用攻击者控制的数据(命令执行、JNDI 查找、反射等)的方法,都会将反序列化过程变成一个 RCE gadget。

Lets see an example with a class Person which is serializable. This class overwrites the readObject function, so when any object of this class is deserialized this function is going to be executed.
In the example, the readObject function of the class Person calls the function eat() of his pet and the function eat() of a Dog (for some reason) calls a calc.exe. We are going to see how to serialize and deserialize a Person object to execute this calculator:

以下示例来自 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");
}
}

结论(经典场景)

如上这个非常基础的示例所示,这里的“漏洞”出现是因为 readObject() 方法在调用其他由攻击者控制的代码。在现实世界的 gadget 链中,外部库(Commons-Collections、Spring、Groovy、Rome、SnakeYAML 等)中包含的成千上万的类都可能被滥用——攻击者只需一个可达的 gadget 就能获得代码执行。


2023-2025:现实世界 Java 反序列化 漏洞 有何变化?

最近的案例提醒我们,ObjectInputStream 漏洞不再只是“将 .ser 文件上传到遗留的 HTTP 端点”这类情况:

  • Broker / queue consumers:Spring-Kafka (CVE-2023-34040) 表明,如果 consumer 启用了不常见的 checkDeserExWhen* 标志,从攻击者控制的 topic 反序列化异常头就足以成问题。
  • Client-side trust of remote servers:Aerospike Java client (CVE-2023-36480) 会对从服务器接收的对象进行反序列化。厂商的响应值得注意:更新的客户端通过移除 Java 运行时的序列化/反序列化支持来修复,而不是试图靠弱过滤器来保留它。
  • “Restricted” streams are often still too broadpac4j-core (CVE-2023-25581) 试图用 RestrictedObjectInputStream 保护反序列化,但接受的类集合仍然足够大,从而使 gadget 滥用成为可能。

进攻面的教训是,危险的信任边界通常不是“用户上传一个 blob”,而是“开发者认为受信任的某个组件可以注入字节流,这些字节最终会到达 readObject()”。

如果在投入时间做完整 gadget 研究前需要低噪声的可达性检查,请使用专门的 Java 页面:

Java DNS Deserialization, GadgetProbe and Java Deserialization Scanner

readObject() 仍会创建 gadget 入口点的反模式

即便你的类本身不是明显的 RCE gadget,以下模式在攻击者控制的对象被嵌入对象图时也足以使其可利用:

  1. readObject() 调用可被重写的方法或接口方法(上面 PoC 中的 pet.eat() 是经典示例)。
  2. 在反序列化期间执行查找、反射、类加载、表达式求值或 JNDI 操作。
  3. 迭代攻击者控制的集合或 map,可能会触发 hashCode()equals()、比较器或 transformer 作为副作用。
  4. 注册 ObjectInputValidation 回调来执行危险的后处理。
  5. 假设“private readObject()” 足够保护。它只控制分派语义;并不会使反序列化安全。

你应该部署的现代缓解措施

  1. JEP 290 / Serialization Filtering (Java 9+)
    使用允许列表和显式的图限制:
-Djdk.serialFilter="com.example.dto.*;java.base/*;maxdepth=5;maxrefs=1000;maxbytes=16384;!*"
  1. 对每个不受信任的流都应用过滤,而不仅仅是全局设置:
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
    当同一 JVM 有多个反序列化上下文(RMI、cache replication、message consumers、仅管理员的导入)且每个上下文需要不同的允许列表时,优先使用此方案。
  2. 保持 readObject() 简洁
    只调用 defaultReadObject() / 显式字段读取,然后执行严格的不变量检查。不要在里面做 I/O、对可能指向攻击者控制对象的日志解引用、动态查找或对反序列化子对象的方法调用。
  3. 如果可能,在设计中移除 Java 原生序列化
    Aerospike 的修复是一个好模型:当该特性并非必需时,删除 readObject() / writeObject() 的使用通常比试图永久维持完美过滤器更安全。

检测与研究工作流

  • ysoserial 仍然是 gadget 验证以及快速 RCE/URLDNS 探测的基线工具。
  • marshalsec 在 sink 转向 JNDI/LDAP/RMI 场景时仍然有用。
  • GadgetInspector 在你拥有目标 jars 并需要查找应用特定 gadget 链时很有帮助。
  • Java 17 添加了 jdk.Deserialization Flight Recorder 事件,用于查看 ObjectInputStream 的实际使用位置以及是否正在应用过滤器。

面向安全的 readObject() 快速检查清单

  1. 将方法声明为 private,并用 @Serial 注释序列化钩子,以便编译器能捕获声明错误的签名。
  2. 除非有充分理由手动读取整个对象图,否则优先调用 defaultReadObject()
  3. 在验证前,将每个嵌套对象视为攻击者控制。
  4. 切勿在 readObject() 内部调用反序列化协作者的方法。
  5. 将代码审查与 ObjectInputFilter 审查配对;“看起来安全的 readObject() 代码”不足以保证安全,如果流仍然接受任意类。

References

Tip

学习并实践 AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
学习并实践 GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
学习并实践 Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) 浏览用于评估路线的 完整 HackTricks Training 目录ARTA/GRTA/AzRTA)以及 Linux Hacking Expert (LHE)

支持 HackTricks