Apache Commons Collections 3.1链初探
前言
Commons Collections利用链简称CC链,是Java反序列化学习中的重要一环。
本文初探最开始的一条链子,进行分析。
环境搭建
jdk1.7.080 + Apache Commons Collections 3.1
jar包可以在mvnrepository找到,https://mvnrepository.com/artifact/commons-collections/commons-collections/3.1
使用网上公开的payload编译运行,弹出计算器
1 | import org.apache.commons.collections.Transformer; |
接着就要分析利用链
InvokerTransformer.class
这个漏洞产生的根源在于org.apache.commons.collections.functors.InvokerTransformer类中,存在这样一个反射调用的方法
不难看出,如果我们控制了这四个参数,利用反射,我们就可以调用任意方法,执行任意命令
input参数不必说,为传入的对象
iMethodName为获取的方法名称,iParamTypes为获取的方法参数类型
iParamTypes为调用方法时的参数
构造方法中可以直接传入
如此我们可以测试一下:
但是这只局限于本地,要在远程,反序列化后调用恶意方法,得修改代码,因为远程不可能有最后transform调用传入Runtime实例的这一行。
ChainedTransformer.class
Runtime 类并没有继承 Serializable 接口 , 因此我们无法直接传入实例对象
对此,采用通过 Java 反射机制来构建 java.lang.Runtime.getRuntime().exec()
方法调用链
但是要拿到Runtime实例要调用多种反射方法
因此要调用 getMethod() , getRuntime() , exec() , invoke() 这四个方法
org.apache.commons.collections.functors.ChainedTransformer类中,构造函数能传入一个transformer数组,然后transform方法会便利这个数组,调用数组中每个元素的transform方法,然后把获取到的方法返回,递归作为下一次transform方法的参数
我们就可以利用这个类来创建反射调用链
1 | org.apache.commons.collections.Transformer[] transformers = new Transformer[]{ |
ConstantTransformer.class
如何在开始获取到Runtime实例对象,来getMethod,ConstantTransformer.class类中提供了方法
可以在构造函数里传入Runtime对象,然后ConstantTransformer.transform会返回它
就可以这样构造
如此一来,无论transform方法中传入什么参数,都将执行我们构造的恶意链中的内容
攻击链
上面虽然成功构造了反射命令执行链子,但是仍需要手动触发transform方法,实际情况中很少,所以要解决这个问题
我们想要在反序列化,也就是readObject方法的时候就调用恶意链,根据上一章,我们可以中重写此方法,实现defaultReadObject()方法,添加恶意功能,他仍然能被正常序列化和反序列化.
综上所述,
要在一个重写了的readObject方法中找到transform方法,且调用这个方法的实例可控。
TransformedMap 攻击链
主要围绕org.apache.commons.collections.map.TransformedMap类
简单阅读下这个类信息
checkSetValue中调用了transform方法
this.valueTransFormer可以在构造方法的时候传入,我们可控!
它的构造方法是私有的,通过调用decorate方法返回一个新的TransformedMap类
因为decorate方法第一个参数要传入一个Map类型的对象,直接创建一个hashmap是最快的方法
1 | //初始一个hashmap |
这样就调用了decorate方法
下面问题就是怎么调用checkSetValue方法
AbstractInputCheckedMapDecorator$MapEntry.setValue()
这个类是TransformedMap的父类
其中静态类MapEntry的setValue方法调用了我们的目标方法checkSetValue
也就是说要调用setValue方法
1 | static class MapEntry extends AbstractMapEntryDecorator { |
将this.parent指向TransformedMap实例对象,就可以调用目标方法
然而这个静态类的构造函数也是私有的
AbstractInputCheckedMapDecorator$EntrySetIterator.next()
静态类EntrySetIterator中的next方法调用了这个构造函数,返回MapEntry实例对象
这就是我们要找的,用来调用上面setValue的对象
因为构造函数中的this.parent我们还是不可控,所以继续更进
来自于EntrySetIterator()中,更进看哪里调用了这个构造函数
AbstractInputCheckedMapDecorator$EntrySet.iterator()
可以看到在EntrySet静态类中的iterator调用了上面的EntrySetIterator(),它返回一个AbstractInputCheckedMapDecorator$EntrySetIterator实例对象
但是同样this.parent还是不可控,继续更进
AbstractInputCheckedMapDecorator.entrySet()
这里会根据 isSetValueChecking() 方法的返回值决定是否调用
AbstractInputCheckedMapDecorator.EntrySet() 方法 ,返回上面我们需要的对象
抽象类AbstractInputCheckedMapDecorator是TransformedMap 的父类
因此这里我们可以直接将 this 指向 TransformedMap
也就是用TransformedMap对象直接调用entrySet方法
最后是isSetValueChecking()方法,它要返回一个true
略过
总结TransformedMap攻击链
这样一来利用链就到头了
我们不需要直接调用transform方法,只要通过entrySet=>iterator=>next=>setValue就可以调用到transform方法
而我们一开始调用entrySet的对象,是我们可控构造的TransformedMap对象,里面包含了可控的,上面构造的反射调用链子
这样一来便可以达到我们的最终目的:在下面重写readObject,反序列化的时候就可以自动触发攻击
1 | //初始化TransformMap,传入构造的反射执行链 |
sun.reflect.annotation.AnnotationInvocationHandler
现在的目标:找到一个重写的readObject方法,里面调用到了setValue(),也就是上面反序列化链子的入口
jdk1.7的sun.reflect.annotation.AnnotationInvocationHandler类的readObject()中调用了目标方法
对这个方法进行分析,自己先看了一下发现,原来自己的分析方式有点不够好,这里跟着大佬的分析方式走
1.
我们的目标当然是调用 AbstractInputCheckedMapDecorator$MapEntry.setValue()
首先有这几个条件:
var5为AbstractInputCheckedMapDecorator$MapEntry
Entry var5 = (Entry)var4.next();
所以只要让var4为AbstractInputCheckedMapDecorator$EntrySetIterator即可
var7.isInstance(var8)为false且var7!=null
B.Class.isInstance(a):
a 是否能强转为 B 类型
var6为var5的键值,var7为(Class)var3.get(var6)的返回值
var8是var5的值
var8 instanceof ExceptionProxy为false
2.
接着往上看var3和var4
1 | Iterator var4 = this.memberValues.entrySet().iterator(); |
我们希望this.memberValues指向AbstractInputCheckedMapDecorator下的Transformer类
1 | Map var3 = var2.memberTypes(); |
跟进memberTypes方法
1 | public Map<String, Class<?>> memberTypes() { |
1 | this.memberTypes = new HashMap(var2.length + 1, 1.0F); |
因此var3是一个HashMap,这样也就可以得到(Class)var3.get(var6)调用的是Map的get方法,也就是复制
3.
var2
1 | var2 = AnnotationType.getInstance(this.type); |
所以现在要确认的就是
this.type,和this.memberValues
跟到构造函数中
var1是个注解类,可以传入java.lang.annotation.Retention
var2为Map类型,可以传入TransformedMap实例对象
构造AnnotationInvocationHandler.AnnotationInvocationHandler()
1 | Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); |
断点调试,可以看到
4.
因为特定了键值为value,所以构造HashMap的时候要指定键值为value
完整反序列化poc
1 | public class EvilTest1 { |
LazyMap攻击链
LazyMap.get()
org.apache.commons.collections.map.LazyMap类中的get方法,其中调用了transform方法
且this.factory可控,还是TransFormer对象
因为get方法是public类型,所以直接可以构造利用
1 | public class EvilTest2 { |
因为暂未找到在readObject方法中调用get的类,所以跟踪get方法的调用栈,看哪些方法调用了get,并且他们有可能在readObject中被调用
TiedMapEntry.getValue()
可以看到getValue方法中调用了get,this.key也由构造方法中传入,我们可控
将this.map指向上面的LazyMap实例对象即可
TiedMapEntry.toString()
这个类的toString()方法中调用了getValue,我们只要将this指向TiedMapEntry即可
javax.management.BadAttributeValueExpException.readObject()
javax.management.BadAttributeValueExpException类的readObject方法调用了toString()
我们把valObj指向上面的TiedMapEntry就可以触发
逆推看看valObj是咋来的
gf.get(“val”, null)就是拿到反序列化类中的val属性
val是BadAttributeValueExpException的一个私有属性
因为我们想要控制的这个val是个私有属性
可以通过反射对私有属性赋值
1 | Map lazyMap = LazyMap.decorate(map, chainedTransformer); |
这样就构建完成了,接着把ins进行序列化反序列化看看能不能触发
成功成功
这条链子相对来说比上面哪个要简易一点
总结
- 反序列化漏洞:一般是触发点,在重写的readObject里有特定方法,为整条链子的入口
- 反射调用方法是最根本的漏洞成因
- 对于实例中的操作,要熟用反射
- 对于java的一些知识还需要进一步学习,反射,io,动态代理,Map,强转类型时候的细节,注解
- 还是要细心