Java反序列化如何防护

wen 开源项目 24

本文目录导读:

Java反序列化如何防护

  1. 使用安全的替代方案(最推荐)
  2. 白名单验证(核心防护)
  3. 环境配置加固
  4. 代码层面的防护
  5. 监控与检测
  6. 网络层防护
  7. 完整的防护示例
  8. 核心原则

Java反序列化漏洞的防护需要从多个层面进行综合防御,以下是最有效的防护策略,按推荐程度排序:

使用安全的替代方案(最推荐)

  • JSON:使用 Jackson、Gson 等,配合 @JsonTypeInfo 的显式类型声明
  • Protocol Buffers:Google 的高效序列化方案,自带类型安全
  • Avro:Apache 的二进制序列化格式
// 使用 Jackson 替代 ObjectInputStream
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
// 或更安全的配置
mapper.activateDefaultTyping(
    BasicPolymorphicTypeValidator.builder().build(),
    ObjectMapper.DefaultTyping.NON_FINAL
);

白名单验证(核心防护)

实现 ObjectInputFilter 接口(Java 9+),只允许特定类反序列化:

// 方式1:全局设置
ObjectInputFilter.Config.setSerialFilter(filter);
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
    "com.example.*;java.base/*;!*"
);
// 方式2:针对单个流
ObjectInputStream ois = new ObjectInputStream(inputStream);
ois.setObjectInputFilter(info -> {
    Class<?> clazz = info.serialClass();
    if (clazz != null) {
        // 只允许业务类
        if (clazz.getPackage().getName().startsWith("com.example")) {
            return ObjectInputFilter.Status.ALLOWED;
        }
        return ObjectInputFilter.Status.REJECTED;
    }
    return ObjectInputFilter.Status.UNDECIDED;
});

白名单示例

  • 只允许:com.example.*java.util.*java.lang.*
  • 禁止:java.rmi.*javax.management.*com.sun.*

环境配置加固

JVM 参数防护(Java 9+):

-Djdk.serialFilter=com.example.*;java.base/*;!*
-Djdk.serialFilter=maxdepth=10;maxarray=1000;maxbytes=10240

禁用 RMI/JMX(如果不需要):

-Dcom.sun.management.jmxremote=false
-Djava.rmi.server.hostname=127.0.0.1

代码层面的防护

1 自定义 ObjectInputStream

public class SafeObjectInputStream extends ObjectInputStream {
    private static final Set<String> ALLOWED_CLASSES = Set.of(
        "java.lang.String",
        "java.util.HashMap",
        "com.example.MyBean"
    );
    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException {
        String className = desc.getName();
        if (!isAllowed(className)) {
            throw new InvalidClassException("Unauthorized deserialization", className);
        }
        return super.resolveClass(desc);
    }
    private boolean isAllowed(String className) {
        return ALLOWED_CLASSES.stream().anyMatch(className::startsWith);
    }
}

2 使用 ValidatingObjectInputStream

// Apache Commons IO 提供
ValidatingObjectInputStream vois = 
    new ValidatingObjectInputStream(inputStream);
vois.accept(User.class, Order.class); // 只接受这两个类
vois.reject("java.rmi.**"); // 拒绝危险类

监控与检测

1 日志记录

ObjectInputStream ois = new ObjectInputStream(inputStream) {
    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException {
        String clsName = desc.getName();
        logger.warn("Deserializing class: {}", clsName);
        // 异常检测
        if (isDangerousClass(clsName)) {
            logger.error("Suspicious deserialization attempt: {}", clsName);
            throw new SecurityException("Blocked dangerous class");
        }
        return super.resolveClass(desc);
    }
};

2 使用安全扫描工具

  • DeserLab:检测反序列化漏洞
  • SerialKiller:Java 反序列化防护库
  • OWASP Dependency Check:扫描第三方库漏洞

网络层防护

1 Web 应用防火墙(WAF)规则

# Nginx 规则示例
location /api {
    if ($content_type ~ "application/x-java-serialized-object") {
        return 403;
    }
}

2 网络端口限制

  • 禁用 1099(RMI Registry)
  • 禁用 4444(JMX)
  • 禁用 6666(RMI Activation)

完整的防护示例

public class SecureDeserializationService {
    private static final ObjectInputFilter FILTER = 
        createStrictFilter();
    @SuppressWarnings("unchecked")
    public <T> T deserialize(byte[] data) {
        try (ByteArrayInputStream bis = new ByteArrayInputStream(data);
             ObjectInputStream ois = new ObjectInputStream(bis)) {
            // 设置过滤器
            ois.setObjectInputFilter(FILTER);
            // 限制大小
            if (data.length > 10 * 1024 * 1024) { // 10MB限制
                throw new SecurityException("Data too large");
            }
            return (T) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException("Deserialization failed", e);
        }
    }
    private static ObjectInputFilter createStrictFilter() {
        return filterInfo -> {
            Class<?> serialClass = filterInfo.serialClass();
            if (serialClass == null) {
                return ObjectInputFilter.Status.UNDECIDED;
            }
            String name = serialClass.getName();
            // 白名单
            if (name.startsWith("com.example.allowed.") ||
                name.startsWith("java.util.") ||
                name.equals("java.lang.String")) {
                return ObjectInputFilter.Status.ALLOWED;
            }
            // 黑名单
            if (name.startsWith("java.rmi.") ||
                name.startsWith("javax.management.") ||
                name.startsWith("com.sun.")) {
                return ObjectInputFilter.Status.REJECTED;
            }
            // 默认拒绝
            return ObjectInputFilter.Status.REJECTED;
        };
    }
}

核心原则

  1. 绝不反序列化不可信数据 - 如果必须,使用白名单
  2. 优先使用JSON/Protobuf - 避免Java原生序列化
  3. 最小权限原则 - 只反序列化必要的类
  4. 持续更新 - 及时修补已知漏洞(如Log4Shell)
  5. 深度防御 - 结合代码+环境+网络多层防护

通过以上策略,可以显著降低Java反序列化漏洞的风险,但需要根据具体业务场景选择最适合的方案。

抱歉,评论功能暂时关闭!