前言
每天一小步,进步一大步。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; |