apache-commons-collections6反序列化链分析
apache-commons-collections6反序列化链利用的也是通过TiedMapEntry.getValue
方法调用一系列的transform方法执行系统命令的,下面就分析分析这条链
复现环境
JDK7+CommonsCollections1
漏洞分析
看到CommonsCollections6类的getObject方法,前面是很熟悉的Transformer数组构造,数组构造和ACC5也是一样的,这里直接在实例化ChainedTransformer对象的时候把Transformer数组作为参数传入了,而不是通过反射传入,不过问题都不大
然后实例化HashMap、LazyMap对象,并把这两个对象作为参数实例化TiedMapEntry对象,这里也很熟悉,经过这些实例化操作,TiedMapEntry对象的this.map
值为LazyMap对象,
这时候只要调用到TiedMapEntry.getValue
方法就可以调用一系列的transform方法执行系统命令了,至此,前面的都和ACC5链一样,那么下面看看怎么构造才能调用到TiedMapEntry.getValue
方法
实例化一个容量为1的HashSet对象,并把foo字符串添加进去占位,再通过反射获取HashSet对象的map属性的Field对象,并赋值到f变量,然后通过get方法获取到属性值并赋值到innimpl变量
HashSet对象的map属性为一个HashMap对象,所以innimpl变量就是一个HashMap对象
之后反射获取HashMap的table属性的Field对象,并赋值给f2变量
HashMap的table属性字段为Node类,Node是HashMap的一个内部类
再反射获取Entry属性赋值到node变量,也就是node变量就是HashMap$Node
内部类
反射获取HashMap$Node
内部类的key字段的Field对象,并把这个字段的key属性赋值为TiedMapEntry对象,最后返回的是HashSet对象
上面的一系列操作就是把HashSet对象中的map属性对象(这属性是一个HashMap对象)里面的Node内部类的key属性赋值为TiedMapEntry对象(当HashSet对象中的map属性为HashMap对象时,HashSet.add
方法中添加的元素就是存储在HashMap对象的key属性中的)
跟进HashSet.readObject
方法,先调用默认的defaultReadObject方法保证反序列化操作的正常进行,然后实例化一个HashMap对象赋值给map变量,再for循环用map.put
方法对map变量赋值。
跟进put方法,注意put方法的参数e是内部Node类的key属性,也就是TiedMapEntry对象,put方法调用hash方法对TiedMapEntry对象进行处理。这里为什么参数e是内部Node类的key属性呢?跟进一下HashSet.writeObject方法中的,得知在调用HashSet.writeObject方法序列化对象的时候通过s.writeObject写入的就是TiedMapEntry对象,那么反序列化时s.readObject当然读取到的也就是TiedMapEntry对象
跟进hash方法,其调用k.hashCode
方法,也就是调用TiedMapEntry.hashCode
方法
跟进TiedMapEntry.hashCode
方法,其调用getValue方法。自此,就调用到TiedMapEntry.getValue
方法了,进而调用一系列transform方法执行系统命令
总结
在这条链中要注意的地方有两个
当HashSet的map属性为HashMap对象时,HashSet.put方法添加的元素是存储在HashSet的map属性对象的内部Node类的key属性中的,所以在HashSet.readObject方法中,map.put的参数e为key属性,也就是构造payload时指定的TiedMapEntry对象
HashSet对象的map属性是transient修饰符修饰的,但是HashSet对象的writeObject方法和readObject方法手工读取和写入了map属性,所以可以成功序列化和反序列化