前言
CommonCollections系列的gadgets我们在反序列化利用中用的很多,下面剖析一下ysoserial中CommonCollections7的调用链和原理。
实验环境:jdk1.8.0_211、commons-collections:commons-collections:3.1
代码分析
关键调用链:
1 | Payload method chain: |
可以看到从LazyMap.get之后的调用链都还是和之前一样的。
LazyMap前面的内容为一条新的链,接下来就分析一下这条链。我们现在java.util.Hashtable.reconstitutionPut处打上断点。
跟入reconstitutionPut中
通读上面两张图中的代码可以看出readObject是将key和value中的值反序列化之后通过reconstitutionPut进行恢复。reconstitutionPut的作用为如果在hashtable中没有出现碰撞现象,直接插入,如果出现了碰撞现象会进行一系列操作。在hashtable中出现碰撞现象时两个key的index是相同的。例如第一个插入的键值index为3,第二个插入的如果和第一个产生了碰撞,那么第二个的index也是3,这样就会进入到e.key.equals(key)中,对应调用链的org.apache.commons.collections.map.AbstractMapDecorator.equals。
接下来我们直接看ysoserial关于cc7的exp然后打断点调试。
1 | // Reusing transformer chain and LazyMap gadgets from previous payloads |
在下图位置也就是LazyMap.get下断点
跑一个测试类,开启debug
1 | import java.io.*; |
断点会进入两次,第一次不是readObject时候产生的,第二次才是readObject产生的,我们只需要看第二个即可。
通过调用链可以看到,reconstitutionPut中跳入下一个节点的位置和我们理论分析的相同,那么我们看他下一步的操作。
可以看到调用了this.map.equals(object);
然后把参数Object o转化为了m,最后调用了m.get(key)。看到这里已经差不多了,只要我们的m为LazyMap,就可以触发代码执行。我们回溯到Hashtable的reconstitutionPut方法中,只要其参数key为LazyMap即可,value无所谓。了解了这些后我们再去看exp中不好理解的部分。
1 | Map innerMap1 = new HashMap(); |
1、为什么插入了两个LazyMap?
因为为了制造碰撞,两个LazyMap的hashcode实际上是一样得,其在Hashtable中的index也一样,这样第二个LazyMap就可以进入到for循环中,调用到e.key.equals(key)。
2、为什么要调用lazyMap2.remove(“yy”)?
解答这个问题我们需要在第二个hashtable.put出下断点并跟入put
我们知道这两个LazyMap的hashcode是相同的,所以在第二个hashtable.put会进入for循环,调用到e.key.equals(key),我们进入equals方法。
然后进入到AbstractMap中
触发到LazyMap.get
最后在我们第二个LazyMap中插入了一个键值对{“yy”:”yy”}。
可以看到,这时候LazyMap2里面有两组数据了。我们先暂停这个调试,然后把LazyMap2.remove(“yy”)注释掉,在Hashtable的reconstitutionPut方法的e.key.equals(key)行下断点。再在AbstractMap的equals处下断点。
我们看到key中是两行了,然后进入equals。一直进入到AbstractMap的equals处。
直接return了false。因此exp中LazyMap2.remove(“yy”)是必要的,这样才能够触发到后面的阶段不会提前return false。