Java代码审计之反序列化

image-20250823110002660

1. 概述

本文档详细介绍了Java中各种序列化/反序列化方式的漏洞成因和代码特征。

2. 序列化/反序列化方式

2.1 原生Java序列化

原生Java序列化使用 ObjectOutputStream / ObjectInputStream,基于 Serializable 接口的二进制协议。

特点:

  • Java原生支持
  • 格式是JVM私有的二进制

常见漏洞:

  • Commons Collections, Spring, JDK内置Gadget等
  • ysoserial工具里大量payload就是针对原生序列化

2.1.1 原生反序列化

1. 直接反序列化未验证输入

1
2
3
4
5
6
7
8
9
10
// 高危代码模式
public void deserializeData(InputStream inputStream) {
try {
ObjectInputStream ois = new ObjectInputStream(inputStream);
Object obj = ois.readObject(); // 直接反序列化,无任何安全控制
// 处理反序列化后的对象
} catch (Exception e) {
e.printStackTrace();
}
}

2. 缺少安全过滤器

1
2
3
4
5
6
7
8
9
// 危险:没有实现ObjectInputFilter
public class UnsafeDeserializer {
public Object deserialize(byte[] data) throws Exception {
ByteArrayInputStream bis = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bis);
// 缺少:ois.setObjectInputFilter(filter);
return ois.readObject();
}
}

3. 白名单实现不完整

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 白名单类不完整,容易被绕过
public class IncompleteWhitelistFilter implements ObjectInputFilter {
private static final Set<String> WHITELIST = new HashSet<>();
static {
WHITELIST.add("com.safe.User");
WHITELIST.add("com.safe.Order");
// 缺少:java.util.HashMap, java.util.HashSet等基础类
}

@Override
public Status checkInput(FilterInfo filterInfo) {
if (filterInfo.serialClass() != null) {
String className = filterInfo.serialClass().getName();
return WHITELIST.contains(className) ? Status.ALLOWED : Status.REJECTED;
}
return Status.UNDECIDED;
}
}

4. 黑名单更新不及时

1
2
3
4
5
6
7
8
9
// 黑名单未包含最新的危险类
public class OutdatedBlacklistFilter implements ObjectInputFilter {
private static final Set<String> BLACKLIST = new HashSet<>();
static {
BLACKLIST.add("org.apache.commons.collections.Transformer");
BLACKLIST.add("org.apache.commons.collections.functors.ChainedTransformer");
// 缺少新发现的危险类,如某些Spring框架的gadget
}
}

5. 异常处理不当

1
2
3
4
5
6
7
8
9
10
11
12
// 异常处理可能泄露信息或继续执行
public Object deserializeWithBadExceptionHandling(byte[] data) {
try {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
return ois.readObject();
} catch (Exception e) {
// 危险:打印完整堆栈信息可能泄露敏感信息
e.printStackTrace();
// 危险:返回null可能导致后续逻辑异常
return null;
}
}

2.2 基于JSON的序列化

2.2.1 Fastjson

阿里巴巴开源,支持自动类型(autoType)特性。

常见漏洞:

  • 利用autoType加载恶意类/Gadget
  • 版本不同防御机制差异很大

漏洞成因与代码特征:

1. autoType功能开启

1
2
3
4
5
6
7
// 高危:开启autoType功能
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

// 或者通过配置开启
ParserConfig config = new ParserConfig();
config.setAutoTypeSupport(true);
JSON.parseObject(jsonString, Object.class, config);

2. 白名单配置不完整

1
2
3
4
5
6
7
8
// 白名单配置不完整
ParserConfig config = new ParserConfig();
config.put("com.safe.User", User.class);
config.put("com.safe.Order", Order.class);
// 缺少必要的安全类,如java.util.HashMap等

// 使用不完整的白名单
JSON.parseObject(jsonString, Object.class, config);

3. 直接反序列化为Object类型

1
2
3
4
5
6
7
8
9
// 危险:直接反序列化为Object,可能触发任意类加载
public Object parseJson(String jsonString) {
return JSON.parseObject(jsonString, Object.class);
}

// 或者使用TypeReference
public Object parseJsonWithTypeRef(String jsonString) {
return JSON.parseObject(jsonString, new TypeReference<Object>(){});
}

4. 版本过低或配置不当

1
2
3
4
5
6
// 使用存在漏洞的旧版本
// 1.2.68及以下版本存在多个高危漏洞

// 配置不当,未启用安全特性
ParserConfig config = new ParserConfig();
// 缺少:config.setSafeMode(true); // 安全模式

2.2.2 Jackson

高性能JSON序列化/反序列化库。

常见漏洞:

  • 利用DefaultTyping特性
  • 利用可反序列化的gadget类,例如org.springframework.beans.factory.ObjectFactory

漏洞成因与代码特征:

1. DefaultTyping配置不当

1
2
3
4
5
6
7
// 高危:启用DefaultTyping
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

// 或者更危险的配置
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);

2. 多态反序列化配置

1
2
3
4
5
6
7
8
9
10
11
// 危险的多态反序列化配置JsonTypeInfo.Id.CLASS或JsonTypeInfo.Id.MINIMAL_CLASS
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) // 危险:包含类信息
public class BaseClass {
// ...
}

// 或者使用@JsonTypeInfo注解
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
public class PolymorphicClass {
// ...
}

3. 直接反序列化为Object

1
2
3
4
5
// 危险:直接反序列化为Object类型
public Object parseJson(String jsonString) throws Exception {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(jsonString, Object.class);
}

4. 缺少安全模块配置

1
2
3
4
// 缺少安全模块,如jackson-databind的安全配置
ObjectMapper mapper = new ObjectMapper();
// 缺少:mapper.activateDefaultTypingAsProperty(...)
// 缺少:mapper.setDefaultTyping(...)

2.2.3 Gson

谷歌出品,通常默认安全性较好。Gson默认情况下不支持反序列化为任意类型,不易利用,但也可能被组合攻击。

漏洞成因与代码特征:

1. 自定义TypeAdapter存在风险

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 危险的自定义TypeAdapter
public class UnsafeTypeAdapter<T> extends TypeAdapter<T> {
@Override
public T read(JsonReader in) throws IOException {
// 危险:可能执行任意代码
String className = in.nextString();
try {
Class<?> clazz = Class.forName(className);
return (T) clazz.newInstance();
} catch (Exception e) {
return null;
}
}

@Override
public void write(JsonWriter out, T value) throws IOException {
// ...
}
}

2. 反射使用不当

1
2
3
4
5
6
7
8
9
10
11
12
// 在Gson中使用反射可能带来风险
public class ReflectionBasedDeserializer {
public Object deserialize(String json, String className) {
try {
Class<?> clazz = Class.forName(className);
Gson gson = new Gson();
return gson.fromJson(json, clazz);
} catch (Exception e) {
return null;
}
}
}

2.3 基于XML的序列化

2.3.1 XStream

将对象转为XML或从XML转回对象。

特点:

  • 对类和字段结构映射直观

常见漏洞:

  • 加载可利用的gadget类
  • SSRF/任意文件读写(如果配置不当)

漏洞成因与代码特征:

1. 默认配置未修改

1
2
3
4
5
// 危险:使用默认配置,允许任意类反序列化
public Object deserializeXml(String xml) {
XStream xstream = new XStream();
return xstream.fromXML(xml); // 默认允许任意类
}

2. 白名单配置不完整

1
2
3
4
5
// 白名单配置不完整
XStream xstream = new XStream();
xstream.allowTypes(new Class[]{User.class, Order.class});
// 缺少:xstream.allowTypesByWildcard(new String[]{"com.safe.*"});
// 缺少:xstream.denyTypes(new Class[]{...});

3. 允许外部实体解析

1
2
3
4
// 危险:允许外部实体,可能导致XXE攻击
XStream xstream = new XStream();
// 缺少:xstream.setMode(XStream.NO_REFERENCES);
// 缺少:禁用外部实体解析

4. 直接反序列化用户输入

1
2
3
4
5
6
// 直接反序列化用户提供的XML
public Object processUserXml(String userXml) {
XStream xstream = new XStream();
// 缺少安全配置
return xstream.fromXML(userXml);
}

2.3.2 JAXB

Java官方标准。也可能被利用反序列化gadget攻击,但相比XStream要安全一些(要看配置)。

漏洞成因与代码特征:

1. 不安全的Unmarshaller配置

1
2
3
4
5
6
7
// 不安全的JAXB配置
public Object unmarshalXml(String xml) throws Exception {
JAXBContext context = JAXBContext.newInstance(Object.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
// 缺少:unmarshaller.setProperty("com.sun.xml.bind.v2.runtime.JAXBContextImpl.fastBoot", true);
return unmarshaller.unmarshal(new StringReader(xml));
}

2. 允许外部实体

1
2
3
// 危险:允许外部实体解析
Unmarshaller unmarshaller = context.createUnmarshaller();
// 缺少:unmarshaller.setProperty("http://apache.org/xml/properties/security-manager", null);

2.4 基于YAML的序列化

2.4.1 SnakeYAML

流行的YAML库。

常见漏洞:

  • 默认情况下可能反序列化为任意Java类型
  • 利用gadget加载恶意对象

漏洞成因与代码特征:

1. 默认构造函数使用

1
2
3
4
5
// 危险:使用默认构造函数,可能实例化任意类
public Object loadYaml(String yamlContent) {
Yaml yaml = new Yaml();
return yaml.load(yamlContent); // 默认使用Constructor.Constructor()
}

2. 不安全的Constructor配置

1
2
3
4
5
6
7
// 不安全的Constructor配置
public Object loadYamlUnsafe(String yamlContent) {
Constructor constructor = new Constructor();
// 缺少:constructor.setPropertyUtils(new SafePropertyUtils());
Yaml yaml = new Yaml(constructor);
return yaml.load(yamlContent);
}

3. 缺少类型限制

1
2
3
4
5
6
// 缺少类型限制的YAML加载
public Object loadYamlWithoutTypeRestriction(String yamlContent) {
Yaml yaml = new Yaml();
// 缺少:yaml.addTypeDescription(new TypeDescription(Object.class, "!java.lang.Object"));
return yaml.load(yamlContent);
}

4. 直接加载用户输入

1
2
3
4
5
6
// 直接加载用户提供的YAML内容
public Object processUserYaml(String userYaml) {
Yaml yaml = new Yaml();
// 缺少安全配置和类型验证
return yaml.load(userYaml);
}

2.5 基于其他格式的序列化

2.5.1 Hessian

二进制序列化协议,跨语言。部分历史版本可被反序列化利用。

漏洞成因与代码特征:

1. 使用存在漏洞的版本

1
2
3
4
5
6
7
8
// 使用存在反序列化漏洞的Hessian版本
// 如:hessian-4.0.x存在多个反序列化漏洞

public Object deserializeHessian(byte[] data) throws Exception {
ByteArrayInputStream bis = new ByteArrayInputStream(data);
Hessian2Input input = new Hessian2Input(bis);
return input.readObject(); // 可能触发反序列化漏洞
}

2. 缺少类型验证

1
2
3
4
5
6
// 缺少类型验证的Hessian反序列化
public Object deserializeWithoutValidation(byte[] data) throws Exception {
Hessian2Input input = new Hessian2Input(new ByteArrayInputStream(data));
// 缺少:input.setSerializerFactory(safeSerializerFactory);
return input.readObject();
}

2.5.2 Kryo

高性能二进制序列化库。如果允许外部输入数据,可能被利用。

漏洞成因与代码特征:

1. 不安全的Kryo配置

1
2
3
4
5
6
7
8
9
10
// 不安全的Kryo配置
public Object deserializeKryo(byte[] data) {
Kryo kryo = new Kryo();
// 缺少:kryo.setRegistrationRequired(true);
// 缺少:kryo.setDefaultSerializer(CompatibleFieldSerializer.class);

ByteArrayInputStream bis = new ByteArrayInputStream(data);
Input input = new Input(bis);
return kryo.readObject(input, Object.class);
}

2. 允许任意类反序列化

1
2
3
4
5
6
7
8
9
// 危险:允许任意类反序列化
public Object deserializeAnyClass(byte[] data, Class<?> clazz) {
Kryo kryo = new Kryo();
kryo.setRegistrationRequired(false); // 危险:不要求注册

ByteArrayInputStream bis = new ByteArrayInputStream(data);
Input input = new Input(bis);
return kryo.readObject(input, clazz);
}

2.5.3 protobuf/Thrift/Avro

通常需要配合定义好的schema。不易出现传统反序列化漏洞,但schema本身若可控也有风险。

漏洞成因与代码特征:

1. 动态Schema加载

1
2
3
4
5
6
7
// 危险:动态加载Schema
public Object deserializeWithDynamicSchema(byte[] data, String schemaDefinition) {
Schema schema = Schema.parse(schemaDefinition); // 动态解析Schema
DatumReader<Object> reader = new GenericDatumReader<>(schema);
// 如果schemaDefinition可控,可能存在风险
return reader.read(null, DecoderFactory.get().binaryDecoder(data, null));
}

2. 反射使用不当

1
2
3
4
5
6
7
8
9
10
// 在protobuf中使用反射可能带来风险
public Object deserializeWithReflection(byte[] data, String className) {
try {
Class<?> clazz = Class.forName(className);
Method parseFrom = clazz.getMethod("parseFrom", byte[].class);
return parseFrom.invoke(null, data);
} catch (Exception e) {
return null;
}
}

3. 总结

在进行Java反序列化代码审计时,需要重点关注:

  1. 反序列化入口点:识别所有可能触发反序列化的代码位置
  2. 安全控制机制:检查是否实现了有效的安全控制措施
  3. 输入验证:确保反序列化数据的来源和完整性
  4. 配置管理:验证安全配置的正确性和及时性
  5. 异常处理:避免因异常处理不当导致的安全问题

通过系统性的审计,可以有效识别和防范Java反序列化漏洞风险。


Java代码审计之反序列化
http://candyb0x.github.io/2025/08/23/Java代码审计之反序列化/
作者
Candy
发布于
2025年8月23日
更新于
2025年8月23日
许可协议