前言
CommonCollections系列的gadgets我们在反序列化利用中用的很多,下面剖析一下ysoserial中CommonCollections1的调用链和原理。
实验环境:jdk1.7.0_80、commons-collections:commons-collections:3.1
代码分析
1 | at org.apache.commons.collections.functors.ChainedTransformer.transform |
以上是关键的比较难理解的调用链堆栈。一般来说CommonCollections系列调用到ChainedTransformer.transform的话就已经可以完成代码执行了。下面具体分析。
我们先看一个方法
InvokerTransformer.class-> transform(Object input)
1 | public Object transform(Object input) { |
因此,这个方法赋予了我们调用任意类中任意方法的权力,但是我们如果想调用java.lang.runtime.getRuntime().exec的话,只调用一次可不够,有的人说我input直接设置为java.lang.runtime.getRuntime(),method设置为exec不就行了?可我们要知道此处我们写完之后是需要把exp进行序列化的,java.lang.runtime.getRuntime()得到的类无法序列化,因此我们就不能这么写,只能一步步的从java.lang.runtime这个类不断反射调用到exec方法。
然后我们就找到了ChainedTransformer.class->transform(Object object)
1 | public Object transform(Object object) { |
该方法将调用this.iTransformers[i].transform(object)方法,并将其返回值作为下一次循环的参数。恰好iTransformers是Transformer类型的,可以存放InvokerTransformer。理想中我们只要把this.iTransformers[]中存放一系列InvokerTransformer对象就可以了,但观察yso的CommonCollections的exp并非如此,其数组放置的是一个new ConstantTransformer(Runtime.class),当然ConstantTransformer也是Transformer类型的,当然可以放到里面,但为什么要这么做呢。我放置一个InvokerTransformer,把input设置为Runtime.class不就可以了吗?带着这个问题我们看下ConstantTransformer这个类
ConstantTransformer.class->transform(Object input)
1 | public Object transform(Object input) { |
可以看到,这个类的transformer做用是,不管输入什么,都返回当前类的iConstant属性。这就很方便了。也就是说我只要把this.iConstant设置为Runtime.class,那么我的触发条件可以是transform(1),transform(2),transform(10086),不管是什么,都会返回Runtime.class这个对象。而如果我们使用InvokerTransformer放在数组首的,则必须其transform方法的参数要是Runtime.class。这样对调用链的要求更加苛刻了。
拿着exp对照重新梳理一遍
1 | final String[] execArgs = new String[] { command }; //command是要执行的命令 |
上面是部分exp,在这条exp中,我们后面只要把 transformerChain的iTransformers方法设置为transformers,然后能调用到transformerChain.transform(xxx)即可。而我们要是数组首使用了InvokerTransformer,那么就必须能调用到transformerChain.transform(Runtime.class)才行,要求更加苛刻。
那么接下来的任务就是找到哪里能调用到transformerChain.transform(xxx),yso运用了LazyMap类达成目的。
LazyMap.class->get(Object key)
1 | public Object get(Object key) { |
可以看到如果get的参数key不在map的key中,就会调用到this.factory.transform(key),如果this.factory的值为transformerChain,那么就可以调用到transformerChain.transform(key)。接下来的任务转化为如何调用到lazymap.get。到这里就可以对照文章开头给出的调用链看了,我们发现lazymap.get的前一跳是AnnotationInvocationHandler.invoke(Object var1, Method var2, Object[] var3)。部分代码摘抄如下:
1 | case 0: |
所以我们只要把this.memberValues设置为lazymap就可以调用到其get方法。首先我们看到AnnotationInvocationHandler实现了InvocationHandler,玩过动态代理的人都知道,如果一个类想实现动态代理,一种方式就是实现InvocationHandler。然后调用Proxy.newProxyInstance(Gadgets.class.getClassLoader(), allIfaces, ih) //ih就是那个继承了InvocationHandler的类。该调用方法的到的返回值调用其任何方法都会触发继承了InvocationHandler那个类的invoke方法。所以我们这里把ih直接设置为AnnotationInvocationHandler好了,接下来只要(Map)Proxy.newProxyInstance生成的代理类调用自己的任意方法,就能触发AnnotationInvocationHandler的invoke。
这里最后作者把生成的proxy作为参数实例化到了AnnotationInvocationHandler中,作为AnnotationInvocationHandler类的this.memberValues参数。然后把该类返回,这个类经过序列化反序列化后就可以触发整个调用链。经过反序列化后可以触发调用链,那么关键肯定在readObject。
1 | private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException { |
yso关键exp代码全文
1 | public InvocationHandler getObject(final String command) throws Exception { |
为什么高版本得jdk无法使用CommonCollections1?
我测试得1.8.0_211版本jdk,AnnotationInvocationHandler中得readObject最后多出了这些内容。
1 | AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3); |
而var7为
1 | LinkedHashMap var7 = new LinkedHashMap(); |
并且后面无对var7赋值操作,导致我们的memberValues属性内容为空,因此就无法调用到lazyMap了,故调用链中断。
利用条件(网上找到,并未验证):commonscollections<=3.2.1 、jdk< 8u71
后记
最后关于为什么jdk1.8高版本无法使用cc1的原因纠结了很久……还是不熟练