1 | 这个利用链小兄弟们都分析过了,所以我大概了解下。主要是解决我看到这个利用链的一些疑惑。这条利用链的POC如下。 |
1 | //1.客户端构建攻击代码 |
问题一:什么是Transformer?
Transformer顾名思义是转换器
,Transformer提供了一个transform方法,可以将我们传入的对象转换成其他对象。当然,Transformer仅仅是一个接口,具体转换 。
在commons-collections
中,有多个Transformer的实现类,不同的Transformer实现不同的转换逻辑。
问题二:POC中的Transformer是做什么的?
ConstantTransformer
在构造方法上,接收传入的对象并保存到iConstant中,当调用transform时,无论传入什么对象,均返回iConstant存储的对象。
InvokerTransformer
InvokerTransformer则是漏洞最终触发的原因,在InvokerTransformer的transform方法中,通过反射调用任意类的任意非private方法。
假如我们想通过反射调用Runtime类的exec方法执行命令,可以使用下面的代码。
1 | InvokerTransformer inv=new InvokerTransformer("exec",new Class[] {String.class},new Object[]{"calc.exe"}); |
ChainedTransformer
ChainedTransformer接收一个transformer数组,通过for循环调用每个transformer的transform方法,并将前一个transformer的转换结果作为后一个转换的输入。
问题三:如何调用到InvokerTransformer的transform方法?
思路一:直接找到某个重写了readObject的类,并且这个类里调用了transform方法
我找了commons-collections
中所有的readObject方法,并没有找到一个调用了transform方法的实现。
思路二:找到一个间接调用了transform方法的类,再去看有没有readObject方法调用了那个类对应的方法
首先看哪些方法里调用了transform方法,并且调用transform方法的方法名不能是transform。
找到了下面的方法。
1 | /java/Commons-collection/commons-collections-3.1/src/java/org/apache/commons/collections/functors/TransformerPredicate.java:72 |
1 | D:/程序源码/java/Commons-collection/commons-collections-3.1/src/java/org/apache/commons/collections/BeanMap.java:746 |
1 | D:/程序源码/java/Commons-collection/commons-collections-3.1/src/java/org/apache/commons/collections/CollectionUtils.java:625 |
1 | D:/程序源码/java/Commons-collection/commons-collections-3.1/src/java/org/apache/commons/collections/comparators/TransformingComparator.java:70 |
1 | D:/程序源码/java/Commons-collection/commons-collections-3.1/src/java/org/apache/commons/collections/functors/TransformedPredicate.java:79 |
1 | D:/程序源码/java/Commons-collection/commons-collections-3.1/src/java/org/apache/commons/collections/functors/TransformerClosure.java:71 |
太多了,实在不想手工去搞这件事情。其实这里思路总的来说还比较清晰,但是一个一个去找并且手工递归,确实比较浪费时间,终于知道为什么要搞利用链自动挖掘的工具了。如果要搞的话,直接找从transform到readObject的链,分析其中的链就好了。网上已经有大佬开发好了自动找调用链的工具https://github.com/wh1t3p1g/tabby
根据需求修改就好了。
问题四:为什么不能直接调用InvokerTransformer触发?
在commons-collection1利用链中,作者找到了org.apache.commons.collections.map.TransformedMap#checkSetValue--》sun.reflect.annotation.AnnotationInvocationHandler#readObject
的利用链。
checkSetValue方法代码如下,当我们给valueTransformer传入InvokerTransformer对象,并给value传入Runtime.getRuntime()
即可触发漏洞。
给valueTransformer赋值可通过org.apache.commons.collections.map.TransformedMap#TransformedMap
构造方法实现。value的值取决于调用checkSetValue方法传入的参数。
在org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry#setValue
调用了checkSetValue,并且传入了value的值。parent的值可以通过org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry#MapEntry
构造器来设置。所以可以创建一个TransformedMap对象,设置到MapEntry中。
如何调用到MapEntry的setValue方法?这条利用链的作者最终找到sun.reflect.annotation.AnnotationInvocationHandler#readObject
,这里虽然var5的值可以通过memberValues属性获取,但是setValue的参数是AnnotationTypeMismatchExceptionProxy
类型,这显然和我们的预期不符。所以不能通过给setValue传值的方式来利用。
当然还有另一个原因,再回头看看直接使用InvokerTransformer执行命令的代码,要执行命令需要得到Runtime对象,而Runtime对象并没有实现Serializable
接口,是不能通过序列化进行传输的。所以要想通过反序列化漏洞触发Commons-Collection1利用链,只能让程序在反序列化的过程中动态去生成Runtime对象进行命令执行。
1 | InvokerTransformer inv=new InvokerTransformer("exec",new Class[] {String.class},new Object[]{"calc.exe"}); |
问题五:为什么不能直接new一个AnnotationInvocationHandler对象?
这里AnnotationInvocationHandler构造方法为defalut权限,只有在类内部和同一个包下,才可以调用该构造方法,因此无法通过new的方式创建AnnotationInvocationHandler对象。
问题六:为什么要使用ChainedTransformer?
首先,这个漏洞最终能够造成命令执行,是通过反射调用了Runtime.exec方法,而且通过前面的分析,无法直接获取Runtime对象。只能在程序反序列化的过程中,生成Runtime对象并执行对应的exec方法。首先了解下正常情况下,如何通过反射获取Runtime对象并执行exec方法。
1 | try { Class<Runtime> run=Runtime.class; Method method=run.getMethod("getRuntime"); Runtime r=(Runtime) method.invoke(null); Method method2=run.getMethod("exec", String.class); String s="calc.exe"; method2.invoke(r,s); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } |
再想想org.apache.commons.collections.functors.InvokerTransformer#transform
执行命令的方法。我们的输入是一个Runtime对象才能直接造成命令执行,但在反序列化中又不能传递Runtime对象,所以需要通过反射来得到Runtime对象,而我们目前能控制参数、调用方法、调用类的反射只有org.apache.commons.collections.functors.InvokerTransformer#transform
方法,所以我们需要找到一种方式,能够将通过org.apache.commons.collections.functors.InvokerTransformer#transform
反射调用得到的Runtime对象又作为输入传递给org.apache.commons.collections.functors.InvokerTransformer#transform
,也就是可以循环调用transform的方法。
搜索了整个Commons-Collections项目,只有ChainedTransformer满足这个条件,所以可以使用ChainedTransformer将命令执行的代码串起来。
问题七:如何使用ChainTransformer得到Runtime对象?
首先虽然Runtime对象不能通过序列化传输,但是Runtime的class对象是可以作为参数传输的,所以我们可以以Runtime.class为一个起点,下一步是通过什么方式传递Runtime的class对象,再想想,我们在反序列化的过程中,可以直接控制变量的属性,至于执行某个方法的参数,我们是不能直接传进去的,只能通过控制变量的属性来间接影响调用方法传递的参数,所以我们需要找到一个transformer,可以让我们通过控制它的属性作为初始化参数。通过寻找找到org.apache.commons.collections.functors.ConstantTransformer#ConstantTransformer
,可以通过构造函数对iConstant属性赋值,并在transform方法调用时返回该对象, 所以可以使用ConstantTransformer来进行初始化操作。
所以已经找到一个给org.apache.commons.collections.functors.InvokerTransformer#transform
进行初始化的方法了,下一步需要通过调用Runtime的getRuntime方法来得到Runtime对象。当传入一个Class对象,我们只能调用这个Class对象里的任意方法,所以可以先调用Class对象的getMethod方法,获取getRuntime方法的Method对象,再去调用这个Method对象的Invoke方法,就可以拿到Runtime对象,再通过调用Runtime对象的exec方法执行命令,所以ChainTransformer可以这样构造。
1 | Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), //作为初始化得到Runtime的Class对象。 new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),//调用Class对象的getMethod方法获取getRuntime的Method对象。 new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }), //通过调用Method的Invoke方法得到Runtime对象。 new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"}) }; |
问题八:为什么最终还可以通过setValue来进行触发?
在问题四中我们分析过,setValue虽然可以控制调用的对象,但是setValue的参数无法控制,因此站在我们的角度来讲已经放弃了这条链,但是为什么可以打通呢?因为在调用ConstantTransformer的transform时,transform传入什么值,返回的都是ConstantTransformer构造方法中传递的属性,所以transform传入的参数对我们来说是无关的。
问题九:如何构造POC?
首先ChainedTransformer对象我们在问题七里已经构建好了。
1 | Transformer[] transformers = new Transformer[] { |
参考问题四的分析,接下来我们要创建一个TransformedMap对象,并将Chains设置到该对象中,但是TransformedMap的构造方法并非Public类型,所以不能直接调用构造方法来 为valueTransformer赋值。
但在decorate方法中,会调用该构造方法,所以可以通过调用decorate来间接调用TransformedMap的构造方法。
并且调用decorate还需要传递一个Map对象,所以我们需要提前创建好一个实现了Serializable接口的Map对象,这里随便找了个IdentityHashMap。
1 | Map innerMap = new IdentityHashMap();Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); |
在看看sun.reflect.annotation.AnnotationInvocationHandler#readObject
方法,我们需要将我们构建好的Map赋值给AnnotationInvocationHandler的memberValue属性,并且要给Map赋值,否则在进行遍历的时候会出错。所以还要给map赋值
1 | outerMap.put("test666", "test666"); |
下面我们要创建AnnotationInvocationHandler对象,之前已经分析过,不能直接通过New来创建对象,并且也没有找到调用AnnotationInvocationHandler构造方法的方法,所以 只能通过反射调用来创建AnnotationInvocationHandler对象。
1 | Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); //得到AnnotationInvocationHandler的Class对象 |
问题十:如何给MapEntry的parent属性赋值?
通过之前的分析,这个漏洞还有一个重点在于给MapEntry的parent属性赋值为我们构造好的TransformedMap,但是在我们构造的POC中并没有给parent属性赋值的操作,是什么时候给parent属性赋值的。
在sun.reflect.annotation.AnnotationInvocationHandler#readObject
中,有如下代码Iterator var4 = this.memberValues.entrySet().iterator();
会对我们传入的TransformedMap对象做处理。
首先调用了TransformedMap的entrySet()方法,而TransformedMap并没有定义entrySet方法,因此会调用TransformedMap父类,也就是AbstractInputCheckedMapDecorator的entrySet方法,这个方法会将TransformedMap对象传递给EntrySet的构造方法的第二个参数中。EntrySet会将TransformedMap对象设置到this.parent中。
当调用org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.EntrySet#iterator
方法时,会将TransformedMap传递到EntrySetIterator的构造方法中,给this.patent
赋值、
当调用next方法时,会创建MapEnry并将我们构造好的TransformedMap对象赋值给MapEnry的parent属性。
问题十一:这条利用链为什么不能再JDK8中使用?
查看JDK8中sun.reflect.annotation.AnnotationInvocationHandler#readObject
方法,在该方法中,已经不会去调用setValue方法了,所以这条利用链就失效了。