反序列化攻击是 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 垃圾回收器的增强。兄弟们有啥问题随时问,鹏磊会尽量解答。