1. 序列化 与 反序列化
-
定义:序列化指 将 对象/数据结构的状态信息 转换为可以存储或传输的形式的过程,其反过程称为反序列化。
-
作用:
-
将对象持久化到存储介质以便再次使用
-
使对象能在网络中传输
-
如序列化格式独立于语言,可以实现不同语言之间对象的传递
-
常见形式:
-
标准化格式:
-
独立于语言/平台的格式:
-
文本格式:xml,json等
-
二进制格式:protobuf等
-
特定语言规定的格式:各类编程语言自带序列化格式(如java,python)
-
私有格式:。。。
-
安全性思考:
-
一切安全问题都产生于输入,这里的输入就是攻击端序列化好后的对象,被攻击端反序列化该对象。
-
大多数数据交换格式的实现中,序列化的对象仅可以包含属性,而不可以包含方法,所以不可能在序列化的过程中写入我们自己的代码,仅能控制对象的属性。
ps. 反序列化端进程空间必须持有对象的类型信息和代码,否则无法正常的使用对象(如调用对象的方法)。
-
被攻击端反序列化该对象的过程中或者反序列化该对象之后执行了依赖该对象的代码,我们就可以一定程度通过对象的属性影响被攻击端程序的运行逻辑。
-
大多数数据交换格式的实现中,在对象反序列化的过程中,都会调用 默认的或该对象自定义的反序列化处理方法(如java中的ObjectInputStream.readObject 或 对象自定义的obj.readObject),所以反序列化漏洞大多出现在对象反序列化的过程中。
2. common-collection-1利用链分析
The Java Collections Framework was a major addition in JDK 1.2. It added many powerful data structures that accelerate development of most significant Java applications. Since that time it has become the recognised standard for collection handling in Java.
java collection 框架是jdk1.2 的主要补充,其增加了许多强大的数据结构,加速了大多数重要的java应用的开发。自那时起,它已经成为java处理集合的事实上的标准。
Commons-Collections seek to build upon the JDK classes by providing new interfaces, implementations and utilities.
Common-Collections 通过提供新的接口,实现和工具(类)扩展了jdk 中java collection的功能。
漏洞分析环境:jdk1.7 , commons-collections-3.1,Intellij IDEA
poc_1
1 |
//复制这段代码 运行并调试 |
-
poc_1 第34行打断点,运行(f9)
-
step into (f7)
-
step into (f7)
valueTransformer类型为ChainedTransformer,其中包含1个ConstantTransformer,3个InvokerTransformer。然后调用了valueTransformer.transform对传入对象进行转换。
-
step into (f7),第59行打断点
ChainedTransformer的iTransformers是一个Transformer数组,维护了其Transformer链。ChainedTransformer使用其维护的Transformer链上的Transformer依次对对象进行转换。每个Transformer.transform输入一个对象并返回转换后的对象,转换后的对象传给下一个Transformer直到所有Transformer处理完毕。
-
step into * 2 (f7 两次),进入第一个Transformer(ConstantTransformer)的实现
返回java.lang.Runtime的class对象
-
运行(f9),断在ChainedTransformer.transform的循环中
-
step into(f7),进入第二个Transformer(InvokerTransformer)的实现,运行到第61行(alt+f9)
此Transformer输入对象是java.lang.Runtime类的class对象,调用class对象的getMethod方法获取到java.lang.Runtime.getRuntime方法,返回其Method对象。
-
61行打断点(由poc知,后续所有的Transformer都是InvokerTransformer),f9两次,进入第三个Transformer(InvokerTransformer)实现
此Transformer输入对象是java.lang.Runtime.getRuntime方法的Method的对象,调用Method对象的invoke方法得到Runtime对象,返回Runtime对象。
-
f9两次,进入第四个Transformer(InvokerTransformer)实现
此Transformer输入对象是Runtime对象,调用Runtime对象的exec方法弹出计算器。
总结
-
TransformedMap对Map进行了增强,可以通过设置针对key/value的Transformer对将要存入Map的key/value对象进行转换。
-
Transformer是个接口,调用Transformer.transform方法可以对对象进行转换(传入要转换的对象,返回转换后的对象),这里只关注其以下三种实现(三种实现均支持序列化!!!)
-
ConstantTransformer,无论要转换的对象是什么,都返回一个固定的转换后的对象。
-
InvokerTransformer,调用要转换对象的一个成员方法,该成员方法返回转换后的对象。
-
ChainedTransformer,可以接受一个Transformer数组生成一个转换链。依次调用每个Transformer的transform方法对对象进行处理,前一个Transformer的输出对象作为后一个Transformer的输入。可以用ConstantTransformer初始化要转换的对象。
-
如果想要调用一个对象的方法,可以定义一个ChainedTransformer。用ConstantTransformer初始化要转换的对象,用InvokerTransformer调用要转换对象的方法。定义ChainedTransformer只是定义了一个对象,其转换逻辑只有在TransformedMap 的key/value值被初始化或改变的时候才会触发(如poc_1对TransformedMap第一个Entry调用setValue方法触发转换逻辑)。
3. 能否在目标机运行poc_1中的恶意转换逻辑?
-
需要找一个合适的输入点,将poc_1中代码插入目标程序
但是一般情况下,很难有一个输入点可以让我们将自己的代码插入。即使有也不够通用,往往是一个命令执行的漏洞,如有一个groovy(groovy兼容java语法)命令执行的输入点(但都有命令执行了还看个泡泡茶壶=_=!)。
-
所以只能利用程序中已加载的代码,通过控制代码关联的数据间接控制代码的执行逻辑
对java这种面向对象的语言来说,控制对象的属性就可以影响其方法的执行逻辑。有什么输入点可以比较方便的控制对象的属性?显然,反序列化(反序列化可以完全控制对象的属性,其他输入方式大多只能控制属性的一小部分字段)。如果目标程序存在反序列化输入点,我们就可以构造序列化好的对象发送给目标程序。
-
目标何时调用被反序列化的对象关联的代码?
-
如果该对象的类重写了readObject方法,反序列化该对象的过程中会调用该对象的readObject方法。
-
反序列化对象以后,程序可能会使用反序列化后的对象,也可能调用其方法。
-
整理一下
攻击端序列化一个对象发送给目标程序,目标程序反序列化过程中调用此对象的readObject方法,readObject触发恶意逻辑。
由于readObject是目标程序定义的方法,我们无法修改,所以触发恶意逻辑的代码必须要尽可能的短,否则很难找到合适的反序列化输入点。
好在common-collection1链的触发逻辑就非常短,(例如对绑定了恶意ChainedTransformer的TransformedMap调用put方法,或对TransformedMap的Entry调用setValue方法等)。
因此,我们需要找一个漏洞触发类满足这样的条件:
-
类在目标程序的代码空间里,且类可以序列化
-
类中包含一个Map类型的属性,我们将其赋值为绑定了恶意ChainedTransformer的TransformedMap。
-
其实现了readObject,readObject对Map属性(Map是一个接口,现在指向恶意TransformedMap)的key/value进行了更新(触发恶意逻辑)
-
攻击者实例化一个漏洞触发类的对象,将map字段填充为绑定了恶意ChainedTransformer的TransformedMap对象。序列化并发送给目标机。
-
目标收到并反序列化漏洞触发对象,调用readObject方法,对map字段的key/value进行更新,触发恶意的ChainedTransformer的转换逻辑,执行恶意代码。
显然,前种情况执行方法的时机更加稳定(反序列化过程中readObject必然被调用到),后者执行方法的时机则非常依赖程序对反序列化后对象的使用的策略(例如:可能先将反序列化后的对象维护到一个全局数据结构,很久以后才用)。所以我们优先考虑第一种情况。
于是,攻击流程是这样(代码见poc_2,poc_2中漏洞触发类是AnnotationInvocationHandler):
于是关键就是要找一个漏洞触发类,好在有巨佬(orz)已经找到了满足上述条件的类。
漏洞触发类AnnotationInvocationHandler
1 |
//poc_2中使用的漏洞触发类 |
所以,将绑定了恶意ChainedTransformer的AnnotationInvocationHandler序列化发给目标程序即可。代码如下:
poc_2
1 |
////复制这段代码 运行并调试 |
4. TransformedMap介绍
-
java.util.Map 是一个接口,简称为Map,jdk中有不同的实现,这里统称为jdk_map_impl(如HashMap),是键值对的集合
-
org.apache.commons.collections.map.TransformedMap 简称 TransformedMap ,是common-collection对Map的实现,可以对jdk中的Map的实现进行增强。
-
怎么增强?TransformedMap可以 为jdk_map_impl(如HashMap)绑定一个KEY的Transformer和一个VALUE的Transformer,在向jdk_map_impl类型的对象写入键值对之前,会先调用其绑定的KEY和VALUE的Transformer.transform转换为新的KEY’和VALUE’后再写入,代码如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60//TransformedMap最终会间接继承AbstractMapDecorator一个map字段,并在调用TransformedMap.decorate的时候初始化它。AbstractMapDecorator实现了java.util.Map接口。
public abstract class AbstractMapDecorator implements Map {
protected transient Map map;
...
}
abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator {
static class MapEntry extends AbstractMapEntryDecorator {
private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
//实现了java.util.Map.Entry接口的setValue方法。可以给Map中某一键值对赋值。
public Object setValue(Object value) {
//调用TransformedMap.checkSetValue得到转换后的value对象
value = this.parent.checkSetValue(value);
return super.entry.setValue(value);
}
}
...
}
public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {
protected final Transformer keyTransformer;
protected final Transformer valueTransformer;
//调用静态方法TransformedMap.decorate,构造出一个TransformedMap对象。decorate有三个参数,可以将一个java.util.Map和key/value的Transformer绑定。
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
protected Object transformKey(Object object) {
return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
}
protected Object transformValue(Object object) {
return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}
//调用valueTransformer.transform对value对象进行转换
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}
//TransformedMap在对map字段进行填充时,会先调用相应的Transformer接口的transform方法对key/value对象进行转换。
public Object put(Object key, Object value) {
key = this.transformKey(key);
value = this.transformValue(value);
return this.getMap().put(key, value);
}
...
}
Transformer有多种实现,这里只关注三种。
1 |
public interface Transformer { |
InvokerTransformer转换对象的流程如下(custom_method由in_obj的类自定义):
ChainedTransformer转换对象的流程如下:
poc来自:JAVA反序列化 - Commons-Collections组件
在藏青师傅的建议下,通过cc1入门了一下反序列化漏洞
分析漏洞很多思路来源于和Mmuzz师傅的讨论,果然分布式学习是第一生产力!!!
第一次写文章超级痛苦,瓜哥给了很多行文上的指导
一直没能改的比较满意,但是再拖下去要被藏青师傅打死了(才不是因为实在不知道该怎么写0.0
漏洞分析两小时,文章写一周
果然写文章是阻碍进步的究极原因啊(不是
原文始发于微信公众号(雁行安全团队):commons-collections反序列化利用链分析(1)
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论