前言
CommonCollections系列的gadgets我们在反序列化利用中用的很多,下面剖析一下ysoserial中CommonsBeanutils1的调用链和原理。
实验环境:jdk1.8.0_211、commons-beanutils:commons-beanutils:1.9.2、commons-collections:commons-collections:3.1、commons-logging:commons-logging:1.2
代码分析
关键调用链
1 | at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.defineTransletClasses |
调用链复制了到TemplatesImpl.defineTransletClasses部分,再后面的部分之前在CommonCollections的调用链中分析过了,在这里是一样得。
入口是熟悉的PriorityQueue.readObject,改变的只是PriorityQueue实体类中comparator参数的值,之前cc链中使用的是TransformingComparator,在CommonsBeanutils中改成了BeanComparator。接下来使用断点调试看BeanComparator.compare做了什么工作。
断点直接打在com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.defineTransletClasses中,然后顺着堆栈看不清楚的方法操作。
调用了PropertyUtils.getProperty(01,this.property)
跟入
继续跟入
调用了this.getSimpleProperty(bean, name),跟入方法
贴出该方法全部代码
1 | public Object getSimpleProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { |
从上面标注的关键部分开始读起
首先获取了bean类的名为name的属性的PropertyDescriptor,如果descriptor为空抛出错误bean类中无该属性。若存在调用getReadMethod对bean和descriptor进行处理,从抛出异常来看,该方法是获取bean类中name属性的getter方法。接下来的this.invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY)从函数名来看,应该是反射调用了name属性的getter方法,具体还要跟进入看,跟入this.invokeMethod。
果不其然直接反射调用了bean类钟name属性的getter方法,并且该方法的参数为EMPTY_OBJECT_ARRAY也就是new Object[]{}也就是无参的getter方法。
而exp中填入的bean为填充了恶意代码的org.apache.xalan.xsltc.trax.TemplatesImpl,name为outputProperties,所以outputProperties的getter方法为getOutputProperties。getOutputProperties代码如下
1 | public synchronized Properties getOutputProperties() { |
上面调用了我们乐意看到的org.apache.xalan.xsltc.trax.TemplatesImpl的newTransformer方法,触发了代码执行。
而在于exp构造方面,作者也使用了一些技巧。
1、一开始BeanComparator的参数为什么是lowestSetBit,queue.add中的参数为什么是BigInteger类型?
首先我们知道在PriorityQueue的第二次add中会调用到PriorityQueue实体类的compare属性的compare函数造成提前触发,这个在cc4解析中说过。因此我们需要做的事情是让这个提前触发不会抛出异常,以让我们后面的代码还能继续执行。在执行BeanComparator的compare时会调用queueArray中内容的类的属性,属性名为BeanComparator的property参数,按照作者的写法最后会找到的BigInteger的lowestSetBit属性,并调用getLowestSetBit
我们可以看到getlowestSetBit是存在的因此不会报错,顺利的执行下去,而如果我们乱填的话,就会抛出异常,所以这里add的Biginteger和lowestSetBit作为参数都是有意义的,并不是随便写的。