jenkins反序列漏洞跟过一遍之后,虽说梳理清楚了漏洞触发的大体流程,但是对于JAVA反序列化漏洞导致代码执行的原理仍旧不懂,因此有必要整理JAVA反序列化漏洞相关的知识点。
JAVA反序列化漏洞
反序列化漏洞的本质是反序列化机制打破了数据和对象的边界,导致攻击者注入的恶意序列化数据在反序列化过程中被还原成对象,控制了对象就可能在目标系统上面执行攻击代码,而不可信的输入和未检测反序列化对象的安全性是导致反序列化漏洞的常见原因。Java序列化常应用于RMI(Java Remote Method Invocatio, 远程方法调用), JMX(Java Management Extensions, Java管理扩展), JMS(Java Message Service, Java消息服务) 技术中。
利用Apache Commons Collections实现远程代码执行
Apache Commons Collections作为一种公用库,其中实现的一些类可以被反序列化用来实现任意代码执行。这里以以Apache Commons Collections 3.2.1为例,解释如何构造对象,能够让程序在反序列化,即调用readObject()时,就能直接实现任意代码执行。
利用反射机制执行任意代码
国外研究人员发现InvokerTransformer
类中的transform()
方法允许通过反射, 执行参数对象的某个方法,并返回执行结果。
1 | public class InvokerTransformer implements Transformer, Serializable { |


可以看到,通过transform()
方法里的反射,成功调用了StringBuffer
类的append()
方法并返回结果。
调用transform()方法
接下来就是要找到某种类,会自动调用InvokerTransformer
类中的transform()
方法,构造代码执行。明显调用transform()
方法有以下两个类:
- TransformedMap
- LazyMap
TransformedMap
Apache Commons Collections中实现TransformedMap
类,用来对Map
进行某种变换,只要调用其decorate()
方法,传入key和value的变换对象Transformer
,即可从任意Map
对象生成相应的TransformedMap
,decorate()
方法如下:
1 | public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { |
Transformer
是一个接口,其中定义的transform()
方法用来将一个对象转换成另一个对象。如下所示:
1 | public interface Transformer { |
而前面提到的InvokerTransformer
类实现了Transformer
接口,因此这里就找到了调用InvokerTransformer
类transform()
方法的途径。那么,现在需要知道的是触发TransformedMap
类调用Transformer
的条件是什么?
commons-collections 3.2.2指出当执行Map
类的put()
方法或MapEntry
类的setValue()
方法会自动调用Transformer
。另外多个Transformer
还能串起来,形成ChainedTransformer
。

从图中可以看出调用Map
类的put()
方法会自动调用InvokerTransformer
类的transform()
方法。
LazyMap
LazyMap
实现了Map
接口,其中的get(Object)
方法调用了transform()
方法,跟进函数进去
1 | public Object get(Object key) { |
这里可以看到,在调用transform()
方法之前会先判断当前Map中是否已经有该key,如果没有最终会由这里的factory.transform()
进行处理,跟踪facory
变量找到decorate()
方法。
1 | public static Map decorate(Map map, Transformer factory) { |
这里的decorate()
方法会对factory
进行初始化,同时实例化一个LazyMap
,为了能成功调用transform()
方法,找到了LazyMap
,发现在get()
方法中调用了transform()
方法,那么现在漏洞利用的核心条件就是去寻找一个类,在对象进行反序列化时会调用我们精心构造对象的get(Object)
方法。
突破限制条件
TransformedMap
虽然找到了自动调用InvokerTransformer
类的transform()
方法的途径,但是需要满足其触发条件:执行Map
类的put()
方法或MapEntry
类的setValue()
方法。显然这种方式还不够优雅,最佳条件是反序列化(调用readObject()
方法)时就自动调用InvokerTransformer
类的transform()
方法导致代码执行。
java运行库中的AnnotationInvocationHandler
类, 有一个成员变量memberValues
是Map
类型,而且readObject()
方法中对memberValues
的每一项调用了setValue()
方法。
1 | class AnnotationInvocationHandler implements InvocationHandler, Serializable { |
因此,我们只需要用前面构造的Map
来构造AnnotationInvocationHandler
,进行序列化,当触发readObject()
反序列化的时候,就能实现命令执行。
1 | // |
这段恶意代码本质上就是利用反射调用Runtime()
执行了一段系统命令,作用等同于:
1 | ((Runtime) Runtime.class.getMethod("getRuntime", null).invoke(null, null)).exec("/bin/sh -c open /Applications/Calculator.app") |
当然,反序列化时自动执行任意代码还有其他方式,具体可以分析ysoserial源码,这里就不一一叙述。采用AnnotationInvocationHandler
类也是有条件限制的,是否能成功利用与JDK的版本有关, http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/f8a528d0379d。

AnnotationInvocationHandler
类移除了对memberValue.setValue()
的调用,因此也就不能用AnnotationInvocationHandler
+TransformedMap
来构造POP链了。
LazyMap
AnnotationInvocationHandler
构造函数初始化lLazyMap
对象, 只需要找到一个memberValues.get(Object)
的方法即可触发该漏洞,可惜的是readObject()
方法里面并没有这个方法. 在invoke()
方法中memberValues.get(Object)
被调用了,如下:

AnnotationInvocationHandler
类实现了InvocationHandler
接口,所以它可以代理其他对象。这里利用这个特点,代理一个Map
对象,得到mapProxy
代理对象。然后,将mapProxy
赋值到AnnotationInvocationHandler
类中,当其调用mapProxy
方法时,便可以触发invoke()
方法。然后,便可以执行transform
链。
核心代码如下:
1 | Transformer transformerChain = new ChainedTransformer(transforms); |
参考
- http://www.angelwhu.com/blog/?p=394
- Lib之过?Java反序列化漏洞通用利用分析
- Commons Collections Java反序列化漏洞深入分析
- https://github.com/frohoff/ysoserial
- https://github.com/GrrrDog/Java-Deserialization-Cheat-Sheet
- common-collections中Java反序列化漏洞导致的RCE原理分析
- 从反序列化到命令执行 - Java 中的 POP 执行链
- http://blog.nsfocus.net/java-deserialization-vulnerability-overlooked-mass-destruction/
- http://techshow.ctrip.com/archives/1414.html