Basic Java Deserialization with ObjectInputStream readObject

tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 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. 构造函数 不会 被执行 – 因此小工具链完全依赖于之前的回调。

在该链中的任何方法如果最终调用了攻击者控制的数据(命令执行、JNDI 查找、反射等),将使反序列化例程变成 RCE 小工具。

让我们看一个 Person 类 的示例,该类是 可序列化的。这个类 重写了 readObject 函数,因此当 该类的任何对象反序列化 时,这个 函数 将被 执行
在这个示例中,Person 类的 readObject 函数调用了它的宠物的 eat() 函数,而 Dog 的 eat() 函数(出于某种原因)调用了 calc.exe我们将看到如何序列化和反序列化一个 Person 对象以执行这个计算器:

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

结论(经典场景)

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


2023-2025:Java 反序列化攻击的新动态

  • 2023 – CVE-2023-34040:当 checkDeserExWhen* 标志启用时,Spring-Kafka 反序列化错误记录头允许从攻击者发布的主题构造任意 gadget。已在 3.0.10 / 2.9.11 中修复。¹
  • 2023 – CVE-2023-36480:Aerospike Java 客户端的受信任服务器假设被破坏——恶意服务器回复包含被客户端 反序列化 的序列化有效负载 → RCE。²
  • 2023 – CVE-2023-25581:pac4j-core 用户配置文件属性解析接受 {#sb64} 前缀的 Base64 blob,并在 RestrictedObjectInputStream 的情况下反序列化它们。升级 ≥ 4.0.0。
  • 2023 – CVE-2023-4528:JSCAPE MFT Manager Service(端口 10880)接受 XML 编码的 Java 对象,导致以 root/SYSTEM 身份的 RCE。
  • 2024 – 多个新的 gadget 链被添加到 ysoserial-plus(mod),包括 Hibernate5、TomcatEmbed 和 SnakeYAML 2.x 类,这些类绕过了一些旧的过滤器。

现代缓解措施

  1. JEP 290 / 序列化过滤(Java 9+) 添加类的允许列表或拒绝列表:
bash
# 仅接受您的 DTO 和 java.base,拒绝其他所有内容
-Djdk.serialFilter="com.example.dto.*;java.base/*;!*"

编程示例:

java
var filter = ObjectInputFilter.Config.createFilter("com.example.dto.*;java.base/*;!*" );
ObjectInputFilter.Config.setSerialFilter(filter);
  1. JEP 415(Java 17+)上下文特定过滤器工厂 – 使用 BinaryOperator<ObjectInputFilter> 根据执行上下文(例如,每个 RMI 调用、每个消息队列消费者)应用不同的过滤器。
  2. 不要通过网络暴露原始 ObjectInputStream – 更倾向于没有代码执行语义的 JSON/二进制编码(Jackson 在禁用 DefaultTyping 后,Protobuf、Avro 等)。
  3. 深度防御限制 – 设置最大数组长度、深度、引用:
bash
-Djdk.serialFilter="maxbytes=16384;maxdepth=5;maxrefs=1000"
  1. 持续的 gadget 扫描 – 在您的 CI 中运行工具,如 gadget-inspectorserialpwn-cli,如果危险的 gadget 变得可达则失败构建。

更新的工具备忘单(2024)

  • ysoserial-plus.jar – 社区分支,包含 > 130 个 gadget 链:
bash
java -jar ysoserial-plus.jar CommonsCollections6 'calc' | base64 -w0
  • marshalsec – 仍然是 JNDI gadget 生成的参考(LDAP/RMI)。
  • gadget-probe – 针对网络服务的快速黑盒 gadget 发现。
  • SerialSniffer – JVMTI 代理,打印每个被 ObjectInputStream 读取的类(有助于制作过滤器)。
  • 检测提示 – 启用 -Djdk.serialDebug=true(JDK 22+)以记录过滤器决策和被拒绝的类。

安全 readObject() 实现的快速检查清单

  1. 将方法设为 private 并添加 @Serial 注解(有助于静态分析)。
  2. 切勿在方法中调用用户提供的方法或执行 I/O – 仅读取字段。
  3. 如果需要验证,请在反序列化 执行,位于 readObject() 之外。
  4. 更倾向于实现 Externalizable 并显式读取字段,而不是默认序列化。
  5. 即使对于内部服务,也要注册一个强化的 ObjectInputFilter(抗妥协设计)。

参考文献

  1. Spring Security Advisory – CVE-2023-34040 Java 反序列化在 Spring-Kafka 中(2023 年 8 月)
  2. GitHub Security Lab – GHSL-2023-044:Aerospike Java 客户端中的不安全反序列化(2023 年 7 月)

tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks