前言
每天一小步,进步一大步。从零开始java代码审计系列(一)
Apache Commons Collections反序列化漏洞
首先看/org/apache/commons/collections/functors/InvokerTransformer.class
1 | public Object transform(Object input) { |
这个transform方法里面可以看到有个反射调用return method.invoke(input,this.iArgs);
,但是只有这里的话显然并不能RCE。
继续看/org/apache/commons/collections/functors/ChainedTransformer.class
1 | public Object transform(Object object) { |
这里可以看出来是挨个遍历transformer,调用其的transform方法然后返回个object,返回的object继续进入循环,成为下一次调用的参数,怎么通过这里来执行命令呢,来看
1 | package com.example.demo; |
当传入transformers
后进行
1 | public ChainedTransformer(Transformer[] transformers) { |
当传入InvokerTransformer
后
1 | public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { |
这里都会赋值,然后这里就会调用到
1 | public Object transform(Object input) { |
到
1 | return method.invoke(Runtime.getRuntime(), new Object[] {"calc"}); |
执行命令,但是这是我们构造出来的,环境中不可能有transformerChain.transform(Runtime.getRuntime());
这样的操作,我们可以在/org/apache/commons/collections/functors/ConstantTransformer.class
找到
1 | public ConstantTransformer(Object constantToReturn) { |
传入了个Object对象,然后transform方法原样返回,看代码
1 | package com.example.demo; |
这里我将ChainedTransformer类重写了一些,方便观察调试。
因为在ConstantTransformer
中,调用transform方法时不管输入什么都不会影响返回,所以,随意输入即可。
那么能否直接这样构造进行序列化呢,编写代码试试
1 | package com.example.demo; |
结果如下:
1 | java.io.NotSerializableException: java.lang.Runtime |
可以看到实例化后的对象Runtime
不允许序列化,那么我们继续修改
1 | package com.example.demo; |
整个调用链是
1 | ((Runtime) Runtime.class.getMethod("getRuntime").invoke()).exec("calc") |
简单整理下调用,不然不是很好理解
1 | object = ConstantTransformer.transform("cc"); |
代码执行部分已经分析的差不多了,但是哪里有合适的构造点呢,根据网上的,我们来分析一下
攻击链(一)
我们来看/org/apache/commons/collections/map/TransformedMap.class
1 | protected Object transformValue(Object object) { |
这里的话只要valueTransformer
可控即可利用我们上面的调用链,
1 | protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { |
当我们初始化的时候是可以控制的,怎么触发呢,继续看
1 | public Object put(Object key, Object value) { |
当进入put方法的时候会触发,根据上面的调用链我们之后value
是可以任意值的,修改代码
1 | package com.example.demo; |
这样我们即可进行命令执行。
然后我们想要实现反序列化RCE还需要找个重写readObject的地方,而且还需要有对Map的操作。
但是我们并没有找到有对map执行put的操作
这里还有一处可以实现一样的效果,这里的实现原理跟put那是一样的
1 | protected Object checkSetValue(Object value) { |
什么时候会调用到checkSetValue
方法呢
在它所继承的父类AbstractInputCheckedMapDecorator
中
1 | static class MapEntry extends AbstractMapEntryDecorator { |
有个MapEntry
的内部类,这里面实现了setValue
,并且会触发checkSetValue,然后我们需要找一个readObject中有对map执行setValue的操作。
在jdk小于1.7的时候/reflect/annotation/AnnotationInvocationHandler.class
中,readObject中有对map的修改功能,这里我下载了jdk1.7来看下
1 | private void readObject(java.io.ObjectInputStream s) |
我们先看下payload触发的调用堆栈
1 | package com.example.demo; |
调试略。
攻击链(二)
攻击链一种的触发操作在jdk1.8是不存在的,那么我们来分析下jdk1.8中的攻击链,
我们还可以找到另外一处调transform
可控的地方,
1 | public Object get(Object key) { |
首先,map中如果不包含这个key那么就可以进入transform
,并且可以看到factory也是我们可控的
1 | protected LazyMap(Map map, Transformer factory) { |
也就是说只要让factory
为transformerChain
对象即可触发,key的值没啥影响
那么什么时候会调用get方法呢,可以找到/org/apache/commons/collections/keyvalue/TiedMapEntry.class
1 | public Object getValue() { |
在toString方法中会调用,那么java中的toString什么时候调用呢
这里的toString方法的作用其实跟php的是差不多的
现在我们我还差一步,就是哪里可以触发这个toString
进而触发getValue呢
来看/javax/management/BadAttributeValueExpException.java
中的readObject方法
1 | private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { |
这里我们并不会触发setSecurityManager0
的操作,也就是说在System.getSecurityManager()
会返回null,那么就会触发toString,然后我们只要让val个变量的值为TiedMapEntry
对象即可触发,因为这里是个私有变量,所以我们通过反射所有变量来赋值,那么整个攻击链就构造完成了。
1 | package com.example.demo; |