CommonsCollections1


触发POC

利用IDEA创建一个maven项目,导入依赖

 

自行构造个readObject()触发点

 

利用ysoserial生成payload:

得到

替代上述程序中的BASE64_EXP

运行后

这里是因为Java版本的问题,我用的是1.8,会发生java.lang.Override missing element entrySet的错误。

构造1.7下的poc

试了下7u80 可以成功弹出计算器

 

分析利用链

Step 0

这个条链最终是利用org.apache.commons.collections.functors.InvokerTransformer 其中的 transform()方法来调用java.lang.Runtimeexec()

可以看到method.invoke(input, this.iArgs);

method是根据this.iMethodNamethis.iParamTypes得到的,参数则是this.iArgs

我们需要控制这三个值

而这个类的构造方法便是分别对其分别赋值,这些在反序列化时都是可控的

想要任意命令执行,我们需要引入Runtime

有这么一个类org.apache.commons.collections.functors.ConstantTransformer

ConstantTransformertransform()方法直接会返回构造方法所传入参数的常量.

可以利用

来得到Runtime类,虽然能获取到

Runtime类:

又是不能直接new的,需要利用静态公有方法getRuntime()来获取

Runtime.getRuntime() 后才能再执行Runtime.getRuntime().exec() 执行命令

所以需要有能连续调用多次transform逐步执行到exec()的地方(且需要将前一次的结果当做下一次的输入)

 

Step 1

巧在有org.apache.commons.collections.functors.ChainedTransformer,关键代码如下

其作用是将内部的 iTransformers 按顺序都调用一遍.

刚刚刚好能符合条件,且this.iTransformers也是利用其构造方法赋的值,是可控的

接下来看如何触发ChainedTransformer.transform()

 

Step 2

看到org.apache.commons.collections.map.LazyMap

其中的get()方法调用了transform(key)

this.factory也是构造方法赋值:

再看哪儿调用了get()

 

Step 3

sun.reflect.annotation.AnnotationInvocationHandlerinvoke()方法中有

invoke()中,如果判断var2的函数名不为toString,hashCode或者annotationType则会触发this.memberValues.get()

同样地,this.memberValues赋值点为:

如果这里var2构造为LazyMap的实例即可触发漏洞

那么如何触发AnnotationInvocationHandler.invoke()的呢

 

Step 4

其实sun.reflect.annotation.AnnotationInvocationHandler类中本身就存在了readObject()方法

如果一个对象的readObject()方法被重载了,会优先调用重载后的readObject()

readObject()里有这么一句

看起来好像和invoke()没啥关系..

但由于AnnotationInvocationHandler 继承了 InvocationHandler,这里可以利用java的动态代理机制来构造

在动态代理(利用反射机制在运行时创建代理类)时,该机制下被代理的实例不管调用什么类方法,都会先调用invoke()方法

利用这种方法便可触发AnnotationInvocationHandler.invoke()

 

举个例子

Print.java

NewProxy.java

test.java

执行test结果:

可以看到自动执行了NewProxy.invoke()方法

而且entrySet并非等于toString,hashCodeannotationType,可以完美执行到invoke()中的this.memberValues.get()

所以我们将构造好的AnnotationInvocationHandler序列化内容传入反序列化的功能时,即可实现漏洞利用

到这里整条利用链就走通了,接下来构造exp

 

构造EXP

根据上面的分析逐步构造,最后整合

Step 0

首先是InvokerTransformer,根据上面的分析

用于

methodName 传入方法名,paramTypes是按声明顺序标识该方法形参类型,args为传入的参数

于是分别先构造

exp 0:

 

Step 1

利用ChainedTransformer,在这个类的代码中构造函数为

于是我们也利用org.apache.commons.collections.TransformerStep 0 中的几个参数作为数组串起来

exp part 1:

 

Step 2

LazyMap继承了java.util.Map

由于LazyMap的构造方法不是public,不能直接new出来操作,可以通过反射搞

或者直接利用其decorate()方法,且由于Map是一个接口不能实例化,这里利用HashMap

 

exp part 2:

 

Step 3

接下来是sun.reflect.annotation.AnnotationInvocationHandler触发this.memberValues.get()

因为构造方法不是public, 要通过反射构造出来

var1需要满足如下条件

Override,DeprecatedSuppressWarnings等类都满足条件

exp part 3:

PS.

getDeclaredConstructor()返回制定参数类型的所有构造器(包括非public)

setAccessible(true)允许访问AnnotationInvocationHandler的成员

newInstance()实例化对象

 

Step 4

上一步已经对this.memberValues赋值为了lazyMap,最后一步就是创建动态代理

exp part 4:

 

整合Exp

并将序列化数据输出到exp文件:

 

反序列化时即可收到请求