前言
每天一小步,进步一大步。Java反序列化学习之Apache Commons Collections
0x01 背景
Apache Commons Collections是Apache Commons的组件,它们是从Java API派生而来的,并为Java语言提供了组件体系结构。 Commons-Collections试图通过提供新的接口,实现和实用程序来构建JDK类。
Apache Commons包应该是Java中使用最广发的工具包,很多框架都依赖于这组工具包中的一部分,它提供了我们常用的一些编程需要,但是JDK没能提供的机能,最大化的减少重复代码的编写。
2015年11月6日FoxGlove Security安全团队的@breenmachine发布了一篇长博客,阐述了利用Java反序列化和Apache Commons Collections这一基础类库实现远程命令执行的真实案例,各大Java Web Server纷纷躺枪,这个漏洞横扫WebLogic、WebSphere、JBoss、Jenkins、OpenNMS的最新版。
InvokerTransformer
Apache Commons Collections中有一个特殊的接口Transformer,其中有一个实现该接口的类可以通过调用Java的反射机制来调用任意函数。
Transformer接口
| 1 | public interface Transformer | 
InvokerTransformer
| 1 | public class InvokerTransformer implements Transformer, Serializable { | 
在transform方法中利用java的反射机制进行任意方法的调用。
其中input参数是传入的一个反射,反射调用的是其方法。
| 1 | public InvokerTransformer(String methodName, Class paramTypes[], Object args[]) | 
iMethodName,iParamTypes,iArgs分别对应方法名,参数类型,参数,都是在实例化InvokerTransformer时传入的可控参数。因此利用这个方法我们可以调用任意对象的任意方法。
ChainedTransformer
| 1 | public class ChainedTransformer implements Transformer, Serializable { | 
commons-collections中有一个满足上面条件的类:ChainedTransformer,该类实例化传入一个Transformer类型的数组,调用其transform方法挨个调用数组中对象的transform方法,并将返回值作为下一次调用对象的参数,第一个对象调用transform方法时的参数是用户传入的。
结合InvokerTransformer可以构造出:
| 1 | package com.example.demo; | 
调试分析略。
ConstantTransformer
当我们把上述transformerChain对象进行序列化然后反序列化时很明显不会触发命令执行,除非后端代码这样写。
| 1 | InputStream iii = request.getInputStream(); | 
显然不可能有这样的代码,我们的目的是只执行readObject()就触发命令执行。
这里用到一个内置类ConstantTransformer,transform方法会把传入的实例化参数原样返回。
| 1 | public class ConstantTransformer implements Transformer, Serializable { | 
因此构造
| 1 | package com.example.demo; | 
但是这里实例化后的对象Runtime不允许序列化,所以不能直接传入实例化的对象。所以我们需要在transforms中利用InvokerTransformer反射回调出Runtime.getRuntime()。
| 1 | package com.example.demo; | 
整个调用链是((Runtime) Runtime.class.getMethod("getRuntime").invoke()).exec("calc")
现在反序列化后就可以obj.transform("随意输入");这样触发命令执行,但是一般也没有这样的代码,我们还需要继续构造。
0x02 攻击链(一)
/org/apache/commons/collections/map/TransformedMap.class
| 1 | protected Object transformValue(Object object) { | 
这里只要valueTransformer可控就可以利用上面的调用链。
构造函数如下:
| 1 | public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { | 
可以看到valueTransformer是可控的。
触发点
| 1 | public Object put(Object key, Object value) { | 
因此可以构造
| 1 | package com.example.demo; | 
要想实现反序列化RCE还需要找个重写readObject的地方,而且还需要有对Map的操作。
如果我们要实现反序列化RCE还需要找个重写readObject的地方,而且还需要有对map执行put的操作。
不过还有一处checkSetValue同样调用了transform
| 1 | protected Object checkSetValue(Object value) { | 
在他的父类AbstractInputCheckedMapDecorator中有个MapEntry静态类,调用了AbstractInputCheckedMapDecorator.checkSetValue
| 1 | static class MapEntry extends AbstractMapEntryDecorator { | 
我们需要找一个readObject中对map执行setValue的地方。
在jdk小于1.7的时候/reflect/annotation/AnnotationInvocationHandler.class中,readObject中有对map的修改功能。
| 1 | private void readObject(java.io.ObjectInputStream s) | 
payload如下:
| 1 | package com.example.demo; | 
调试就不贴了。
0x03 攻击链(二)
看/org/apache/commons/collections/map/LazyMap.java中的get方法
| 1 | public Object get(Object key) { | 
factory同样可控,key任意值不会影响结果。
| 1 | protected LazyMap(Map map, Transformer factory) { | 
现在我们需要想办法触发get方法。
/org/apache/commons/collections/keyvalue/TiedMapEntry.class
getValue调用了map实例的get方法。
| 1 | public Object getValue() { | 
而toString方法会调用getValue,java中的toString和php一样,都是当对象被当做字符串处理的时候会自动调用这个方法。
| 1 | public String toString() { | 
修改poc
| 1 | package com.example.demo; | 
序列化entry对象,当漏洞反序列化代码如下时触发漏洞:
| 1 | InputStream iii = request.getInputStream(); | 
这样的话 我们还需要打印这个反序列化对象,我们需要找到一个重写了readObject方法,并且对某个变量进行了字符串操作的类。
/javax/management/BadAttributeValueExpException.java
这里直接调用了valObj.toString(),而Object valObj = gf.get("val", null);,这里的val是私有变量我们可以通过反射私有变量来赋值。从而让valObj=entry。
| 1 | private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { | 
最终会调用toString,并且rce成功。
0x04 反序列化攻击RMI服务
服务器上有commons-collectons-3.1的情况下java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections5 "calc" > 1.txt
| 1 | package com.example.demo; | 
| 1 | package com.example.demo; |