17、JDK 17 新特性:上下文特定的反序列化过滤器实战:防止反序列化攻击

反序列化攻击是 Java 应用的一大安全隐患,鹏磊见过太多因为反序列化漏洞被攻击的案例。攻击者通过构造恶意的序列化数据,在反序列化时执行恶意代码,导致系统被攻击。JDK 17 的上下文特定的反序列化过滤器能有效防止这类攻击,让你能为不同的反序列化上下文设置不同的安全策略。

防止反序列化攻击是应用安全的关键,需要为不同的反序列化上下文设置合适的过滤器。网络反序列化要用严格模式,文件反序列化可以相对宽松,RMI 反序列化需要专门策略。这玩意儿虽然配置可能稍微复杂一点,但能有效防止反序列化攻击,保护应用安全。

常见的反序列化攻击

常见的反序列化攻击包括:

攻击一:代码执行

通过反序列化执行恶意代码:

import java.io.*;

// 危险:可能被攻击的代码(不要在生产环境使用)
public class VulnerableDeserialization {
    public static void main(String[] args) {
        // 攻击者可能构造恶意的序列化数据
        // 包含 java.lang.Runtime 或 java.lang.ProcessBuilder
        // 在反序列化时执行恶意命令
        
        // 示例:攻击者可能发送这样的数据
        // 反序列化后会执行系统命令
        // Runtime.getRuntime().exec("rm -rf /")
    }
}

代码执行攻击是最危险的反序列化攻击,需要严格防护。

攻击二:拒绝服务

通过构造大量数据导致内存溢出:

import java.io.*;

// 危险:可能被攻击的代码(不要在生产环境使用)
public class DoSAttack {
    public static void main(String[] args) {
        // 攻击者可能构造大量嵌套对象
        // 导致内存溢出
        // 或者构造深度很大的对象图
        // 导致栈溢出
    }
}

拒绝服务攻击会导致应用崩溃,需要限制数据大小和深度。

攻击三:信息泄露

通过反序列化获取敏感信息:

import java.io.*;

// 危险:可能被攻击的代码(不要在生产环境使用)
public class InformationLeak {
    public static void main(String[] args) {
        // 攻击者可能通过反序列化
        // 获取应用内部的敏感信息
        // 比如配置、密钥等
    }
}

信息泄露攻击会暴露敏感信息,需要严格控制可反序列化的类。

防护策略

策略一:拒绝危险类

拒绝所有可能执行代码的类:

import java.io.*;
import java.util.function.*;

// 拒绝危险类的过滤器
public class RejectDangerousClasses {
    public static void main(String[] args) {
        // 创建严格过滤器,拒绝所有危险类
        ObjectInputFilter strictFilter = ObjectInputFilter.Config.createFilter(
            "java.base.**;" +  // 允许 java.base 包
            "!java.lang.Runtime;" +  // 拒绝 Runtime
            "!java.lang.ProcessBuilder;" +  // 拒绝 ProcessBuilder
            "!java.lang.Process;" +  // 拒绝 Process
            "!java.lang.ClassLoader;" +  // 拒绝 ClassLoader
            "!java.lang.reflect.*;" +  // 拒绝反射类
            "!*"  // 拒绝其他所有类
        );
        
        // 使用过滤器
        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(new byte[0]))) {
            ois.setObjectInputFilter(strictFilter);  // 设置过滤器
            // 反序列化操作
        } catch (IOException e) {
            e.printStackTrace();  // 处理异常
        }
    }
}

拒绝危险类能防止代码执行攻击。

策略二:限制数据大小

限制反序列化数据的大小:

import java.io.*;

// 限制数据大小的过滤器
public class LimitDataSize {
    public static void main(String[] args) {
        // 创建自定义过滤器,限制数据大小
        ObjectInputFilter sizeFilter = new ObjectInputFilter() {
            private static final long MAX_STREAM_BYTES = 10_000_000;  // 最大流大小:10MB
            private static final long MAX_ARRAY_LENGTH = 1_000_000;  // 最大数组长度:100万
            private static final int MAX_DEPTH = 100;  // 最大深度:100
            
            @Override
            public Status checkInput(FilterInfo filterInfo) {
                // 检查流大小
                if (filterInfo.streamBytes() > MAX_STREAM_BYTES) {
                    return Status.REJECTED;  // 拒绝
                }
                
                // 检查数组长度
                if (filterInfo.arrayLength() > MAX_ARRAY_LENGTH) {
                    return Status.REJECTED;  // 拒绝
                }
                
                // 检查深度
                if (filterInfo.depth() > MAX_DEPTH) {
                    return Status.REJECTED;  // 拒绝
                }
                
                // 检查引用数
                if (filterInfo.references() > MAX_ARRAY_LENGTH) {
                    return Status.REJECTED;  // 拒绝
                }
                
                // 检查类
                Class<?> clazz = filterInfo.serialClass();  // 获取类
                if (clazz != null) {
                    String className = clazz.getName();  // 获取类名
                    
                    // 拒绝危险类
                    if (className.equals("java.lang.Runtime") ||
                        className.equals("java.lang.ProcessBuilder")) {
                        return Status.REJECTED;  // 拒绝
                    }
                }
                
                return Status.UNDECIDED;  // 未决定
            }
        };
        
        // 使用过滤器
        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(new byte[0]))) {
            ois.setObjectInputFilter(sizeFilter);  // 设置过滤器
            // 反序列化操作
        } catch (IOException e) {
            e.printStackTrace();  // 处理异常
        }
    }
}

限制数据大小能防止拒绝服务攻击。

策略三:白名单模式

只允许特定的类反序列化:

import java.io.*;

// 白名单模式过滤器
public class WhitelistFilter {
    public static void main(String[] args) {
        // 创建白名单过滤器
        ObjectInputFilter whitelistFilter = ObjectInputFilter.Config.createFilter(
            "com.example.model.**;" +  // 只允许应用模型类
            "java.lang.String;" +  // 允许 String
            "java.lang.Integer;" +  // 允许 Integer
            "java.util.ArrayList;" +  // 允许 ArrayList
            "java.util.HashMap;" +  // 允许 HashMap
            "!*"  // 拒绝其他所有类
        );
        
        // 使用过滤器
        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(new byte[0]))) {
            ois.setObjectInputFilter(whitelistFilter);  // 设置过滤器
            // 反序列化操作
        } catch (IOException e) {
            e.printStackTrace();  // 处理异常
        }
    }
}

白名单模式是最安全的策略,只允许已知安全的类。

实际应用:网络服务防护

网络服务是最容易受到攻击的场景:

import java.io.*;
import java.net.*;
import java.util.function.*;

// 网络服务防护示例
public class NetworkServiceProtection {
    public static void main(String[] args) {
        // 创建严格的网络过滤器
        ObjectInputFilter networkFilter = ObjectInputFilter.Config.createFilter(
            "java.base.**;" +  // 允许 java.base 包
            "!java.lang.Runtime;" +  // 拒绝 Runtime
            "!java.lang.ProcessBuilder;" +  // 拒绝 ProcessBuilder
            "!java.lang.Process;" +  // 拒绝 Process
            "!java.lang.ClassLoader;" +  // 拒绝 ClassLoader
            "!java.lang.reflect.*;" +  // 拒绝反射类
            "!*"  // 拒绝其他所有类
        );
        
        try (ServerSocket serverSocket = new ServerSocket(8080)) {  // 创建服务器套接字
            System.out.println("服务器启动,监听端口 8080");  // 输出提示
            
            while (true) {
                Socket socket = serverSocket.accept();  // 接受连接
                
                // 为每个连接创建线程
                new Thread(() -> {
                    try (ObjectInputStream ois = new ObjectInputStream(socket.getInputStream())) {
                        // 设置网络过滤器
                        ois.setObjectInputFilter(networkFilter);  // 设置过滤器
                        
                        // 反序列化对象
                        Object obj = ois.readObject();  // 读取对象
                        System.out.println("接收到对象: " + obj);  // 输出对象
                        
                        // 处理对象
                        processObject(obj);  // 处理对象
                    } catch (InvalidClassException e) {
                        System.err.println("拒绝反序列化: " + e.getMessage());  // 输出错误
                    } catch (IOException | ClassNotFoundException e) {
                        e.printStackTrace();  // 处理异常
                    } finally {
                        try {
                            socket.close();  // 关闭套接字
                        } catch (IOException e) {
                            e.printStackTrace();  // 处理异常
                        }
                    }
                }).start();  // 启动线程
            }
        } catch (IOException e) {
            e.printStackTrace();  // 处理异常
        }
    }
    
    private static void processObject(Object obj) {
        // 处理对象的逻辑
        System.out.println("处理对象: " + obj.getClass().getName());  // 输出类名
    }
}

网络服务用严格过滤器,防止攻击。

实际应用:REST API 防护

REST API 也需要防护:

import java.io.*;
import java.util.function.*;

// REST API 防护示例
public class RESTAPIProtection {
    // 创建 REST API 过滤器
    private static final ObjectInputFilter restFilter = ObjectInputFilter.Config.createFilter(
        "com.example.dto.**;" +  // 只允许 DTO 类
        "java.lang.String;" +  // 允许 String
        "java.lang.Integer;" +  // 允许 Integer
        "java.lang.Long;" +  // 允许 Long
        "java.util.ArrayList;" +  // 允许 ArrayList
        "java.util.HashMap;" +  // 允许 HashMap
        "!*"  // 拒绝其他所有类
    );
    
    public static Object deserializeFromAPI(byte[] data) throws IOException, ClassNotFoundException {
        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data))) {
            // 设置 REST API 过滤器
            ois.setObjectInputFilter(restFilter);  // 设置过滤器
            
            // 反序列化对象
            return ois.readObject();  // 读取对象
        }
    }
    
    public static void main(String[] args) {
        try {
            // 模拟从 REST API 接收数据
            byte[] data = new byte[0];  // 示例数据
            Object obj = deserializeFromAPI(data);  // 反序列化
            System.out.println("反序列化成功: " + obj);  // 输出结果
        } catch (InvalidClassException e) {
            System.err.println("拒绝反序列化: " + e.getMessage());  // 输出错误
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();  // 处理异常
        }
    }
}

REST API 用白名单过滤器,只允许 DTO 类。

实际应用:消息队列防护

消息队列也需要防护:

import java.io.*;
import java.util.function.*;

// 消息队列防护示例
public class MessageQueueProtection {
    // 创建消息队列过滤器
    private static final ObjectInputFilter mqFilter = ObjectInputFilter.Config.createFilter(
        "com.example.message.**;" +  // 只允许消息类
        "java.lang.String;" +  // 允许 String
        "java.util.*;" +  // 允许 java.util 包
        "!java.lang.Runtime;" +  // 拒绝 Runtime
        "!java.lang.ProcessBuilder;" +  // 拒绝 ProcessBuilder
        "!*"  // 拒绝其他所有类
    );
    
    public static Object deserializeFromQueue(byte[] data) throws IOException, ClassNotFoundException {
        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data))) {
            // 设置消息队列过滤器
            ois.setObjectInputFilter(mqFilter);  // 设置过滤器
            
            // 反序列化对象
            return ois.readObject();  // 读取对象
        }
    }
    
    public static void main(String[] args) {
        try {
            // 模拟从消息队列接收数据
            byte[] data = new byte[0];  // 示例数据
            Object obj = deserializeFromQueue(data);  // 反序列化
            System.out.println("反序列化成功: " + obj);  // 输出结果
        } catch (InvalidClassException e) {
            System.err.println("拒绝反序列化: " + e.getMessage());  // 输出错误
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();  // 处理异常
        }
    }
}

消息队列用专门的过滤器,只允许消息类。

实际应用:文件存储防护

文件存储可以相对宽松,但也要拒绝危险类:

import java.io.*;
import java.nio.file.*;
import java.util.function.*;

// 文件存储防护示例
public class FileStorageProtection {
    // 创建文件过滤器(相对宽松)
    private static final ObjectInputFilter fileFilter = ObjectInputFilter.Config.createFilter(
        "java.**;" +  // 允许 java 包
        "com.example.**;" +  // 允许应用包
        "!java.lang.Runtime;" +  // 拒绝 Runtime
        "!java.lang.ProcessBuilder;" +  // 拒绝 ProcessBuilder
        "!java.lang.Process;" +  // 拒绝 Process
        "!java.lang.ClassLoader;" +  // 拒绝 ClassLoader
        "!*"  // 拒绝其他所有类
    );
    
    public static Object deserializeFromFile(Path filePath) throws IOException, ClassNotFoundException {
        try (ObjectInputStream ois = new ObjectInputStream(
                Files.newInputStream(filePath))) {  // 创建输入流
            // 设置文件过滤器
            ois.setObjectInputFilter(fileFilter);  // 设置过滤器
            
            // 反序列化对象
            return ois.readObject();  // 读取对象
        }
    }
    
    public static void main(String[] args) {
        try {
            // 从文件反序列化
            Path filePath = Paths.get("data.ser");  // 文件路径
            Object obj = deserializeFromFile(filePath);  // 反序列化
            System.out.println("反序列化成功: " + obj);  // 输出结果
        } catch (InvalidClassException e) {
            System.err.println("拒绝反序列化: " + e.getMessage());  // 输出错误
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();  // 处理异常
        }
    }
}

文件存储用相对宽松的过滤器,但也要拒绝危险类。

综合防护方案

综合防护方案结合多种策略:

import java.io.*;
import java.util.function.*;

// 综合防护方案
public class ComprehensiveProtection {
    // 创建综合过滤器
    private static final ObjectInputFilter comprehensiveFilter = new ObjectInputFilter() {
        private static final long MAX_STREAM_BYTES = 10_000_000;  // 最大流大小:10MB
        private static final long MAX_ARRAY_LENGTH = 1_000_000;  // 最大数组长度:100万
        private static final int MAX_DEPTH = 100;  // 最大深度:100
        
        // 危险类列表
        private static final String[] DANGEROUS_CLASSES = {
            "java.lang.Runtime",
            "java.lang.ProcessBuilder",
            "java.lang.Process",
            "java.lang.ClassLoader",
            "java.lang.reflect.",
            "javax.script.",
            "org.apache.commons.collections."
        };
        
        @Override
        public Status checkInput(FilterInfo filterInfo) {
            // 检查流大小
            if (filterInfo.streamBytes() > MAX_STREAM_BYTES) {
                return Status.REJECTED;  // 拒绝
            }
            
            // 检查数组长度
            if (filterInfo.arrayLength() > MAX_ARRAY_LENGTH) {
                return Status.REJECTED;  // 拒绝
            }
            
            // 检查深度
            if (filterInfo.depth() > MAX_DEPTH) {
                return Status.REJECTED;  // 拒绝
            }
            
            // 检查类
            Class<?> clazz = filterInfo.serialClass();  // 获取类
            if (clazz != null) {
                String className = clazz.getName();  // 获取类名
                
                // 检查危险类
                for (String dangerousClass : DANGEROUS_CLASSES) {
                    if (className.startsWith(dangerousClass)) {
                        return Status.REJECTED;  // 拒绝
                    }
                }
            }
            
            return Status.UNDECIDED;  // 未决定
        }
    };
    
    public static Object deserialize(byte[] data) throws IOException, ClassNotFoundException {
        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data))) {
            // 设置综合过滤器
            ois.setObjectInputFilter(comprehensiveFilter);  // 设置过滤器
            
            // 反序列化对象
            return ois.readObject();  // 读取对象
        }
    }
    
    public static void main(String[] args) {
        try {
            // 反序列化
            byte[] data = new byte[0];  // 示例数据
            Object obj = deserialize(data);  // 反序列化
            System.out.println("反序列化成功: " + obj);  // 输出结果
        } catch (InvalidClassException e) {
            System.err.println("拒绝反序列化: " + e.getMessage());  // 输出错误
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();  // 处理异常
        }
    }
}

综合防护方案结合多种策略,提供全面保护。

日志和监控

记录过滤器的决策,便于监控和审计:

import java.io.*;
import java.util.logging.*;

// 带日志的过滤器
public class LoggingFilter {
    private static final Logger logger = Logger.getLogger(LoggingFilter.class.getName());  // 日志记录器
    
    // 创建带日志的过滤器
    private static final ObjectInputFilter loggingFilter = new ObjectInputFilter() {
        @Override
        public Status checkInput(FilterInfo filterInfo) {
            Class<?> clazz = filterInfo.serialClass();  // 获取类
            Status status = Status.UNDECIDED;  // 默认状态
            
            if (clazz != null) {
                String className = clazz.getName();  // 获取类名
                
                // 检查危险类
                if (className.equals("java.lang.Runtime") ||
                    className.equals("java.lang.ProcessBuilder")) {
                    status = Status.REJECTED;  // 拒绝
                    logger.warning("拒绝反序列化危险类: " + className);  // 记录警告
                } else {
                    status = Status.ALLOWED;  // 允许
                    logger.info("允许反序列化类: " + className);  // 记录信息
                }
            }
            
            return status;  // 返回状态
        }
    };
    
    public static Object deserialize(byte[] data) throws IOException, ClassNotFoundException {
        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data))) {
            // 设置带日志的过滤器
            ois.setObjectInputFilter(loggingFilter);  // 设置过滤器
            
            // 反序列化对象
            return ois.readObject();  // 读取对象
        }
    }
}

记录日志帮助监控和审计。

注意事项

注意事项一:性能影响

过滤器可能影响性能:

// 性能影响
// 过滤器会在每次反序列化时调用
// 复杂的过滤逻辑可能影响性能
// 建议使用简单的模式匹配
// 避免在过滤器中执行耗时操作

过滤器可能影响性能,建议使用简单的模式匹配。

注意事项二:配置顺序

过滤器的配置顺序很重要:

// 配置顺序
// 1. 先设置过滤器工厂
// 2. 再设置默认过滤器
// 3. 最后设置上下文特定的过滤器
// 错误的顺序可能导致过滤器不生效

配置顺序很重要,要按照正确的顺序设置。

注意事项三:测试

充分测试过滤器配置:

// 测试过滤器
// 1. 测试允许的类
// 2. 测试拒绝的类
// 3. 测试边界情况
// 4. 测试攻击场景

充分测试确保过滤器配置正确。

最佳实践

最佳实践一:使用白名单

优先使用白名单模式:

// 白名单模式
// 只允许已知安全的类
// 拒绝其他所有类
// 这是最安全的策略

白名单模式是最安全的策略。

最佳实践二:拒绝危险类

始终拒绝危险类:

// 拒绝危险类
// java.lang.Runtime
// java.lang.ProcessBuilder
// java.lang.Process
// java.lang.ClassLoader
// java.lang.reflect.*

始终拒绝危险类,防止代码执行。

最佳实践三:限制数据大小

限制反序列化数据的大小:

// 限制数据大小
// 限制流大小
// 限制数组长度
// 限制深度
// 限制引用数

限制数据大小能防止拒绝服务攻击。

总结

上下文特定的反序列化过滤器实战是防止反序列化攻击的关键,需要为不同的反序列化上下文设置合适的过滤器。网络反序列化要用严格模式,文件反序列化可以相对宽松,RMI 反序列化需要专门策略。这玩意儿虽然配置可能稍微复杂一点,但能有效防止反序列化攻击,保护应用安全。

建议在实际项目中为所有反序列化操作设置过滤器,特别是网络反序列化,要使用严格模式。虽然配置可能需要一些工作,但能让应用更安全。下一篇文章咱就聊聊 JDK 17 性能优化,看看 ZGC 和 Shenandoah 垃圾回收器的增强。兄弟们有啥问题随时问,鹏磊会尽量解答。

本文章最后更新于 2025-11-28