前言
CommonCollections系列的gadgets我们在反序列化利用中用的很多,下面剖析一下ysoserial中CommonCollections6的调用链和原理。
实验环境:jdk1.8.0_211、commons-collections:commons-collections:3.1
代码分析
关键调用链如下
1 | java.io.ObjectInputStream.readObject() |
从TiedMapEntry.getValue之后还是和cc5一样,不同之处在于如何触发到TiedMapEntry.getValue。cc6使用了TiedMapEntry.hashCode()触发了TiedMapEntry.getValue(),下面是TiedMapEntry.hashCode()代码
1 | public int hashCode() { |
可以看到调用了getValue方法。
而TiedMapEntry.hashCode()由java.util.HashMap.hash()触发
1 | static final int hash(Object key) { |
此处若key的值为TiedMapEntry就和下一步串联了起来,那么我们顺着调用链继续向上,发现HashMap.hash是由HashMap.put调用的,put方法内容如下
1 | public V put(K key, V value) { |
到这里我们发现,只要我们调用了put(key,value),key为LazyMap,就可以和后面相串联。接着向上看,作者使用了HashSet的readObject方法作为出发点,调用了put(key,value)。HashSet的readObject的方法内容如下
1 | private void readObject(java.io.ObjectInputStream s) |
这样下来目标就很明确了,只要我们在HashSet的键值对中放置一个键值对,其中键为LazyMap,在反序列化后就会触发到形如map.put(LazyMap,PRESENT)的方法,从而触发调用链。
下面附上cc6 exp
1 | public Serializable getObject(final String command) throws Exception { |
cc6的map.add(“foo”)以及之后的代码的作用是什么?
大家一般对于cc6 exp疑惑的点在于map.add(“foo”)以及之后的代码的作用是什么,最朴素的想法是map.add的参数直接设置为LazyMap,这样在反序列化之后就会触发到调用链。虽然说这种做法理论上可以触发到调用链,但我们首选观察一下map.add也就是HashSet.add的代码
1 | public boolean add(E e) { |
我们看到此处也会调用map.put(e, PRESENT),从而提前触发调用链,导致抛出异,无法生成最终的反序列化payload。而作者首先add了一个无关紧要的字符串,然后通过一些反射技巧将map中的字符串修改为了LazyMap。避免了提前触发到map.put,非常精妙。