Java反序列化和集合之间的渊源
本文首发于安全客:https://www.anquanke.com/post/id/251220。
0x01 前言
如果分析过ysoserial的同学应该经常会遇到将HashMap、HashSet、PriorityQueue等Java集合作为反序列化载体的情况,本次分析的重点就是Java集合和Java反序列化漏洞的关系。
在ysoserial中,用集合作为反序列化载体的总结如下:
反序列化载体 | Gadget |
---|---|
HashMap | Clojure、Hibernate1、Hibernate2、JSON1、Myfaces1、Myfaces2、ROME、URLDNS |
HashSet | AspectJWeaver、CommonsCollections6 |
PriorityQueue | BeanShell1、Click1、CommonsCollections2、CommonsCollections4、Jython1 |
LinkedHashSet | Jdk7u21 |
Hashtable | CommonsCollections7 |
0x02 Gadget总结
1、HashMap
先来看下各个Gadget中涉及HashMap的部分:
Clojure
1 | HashMap.readObject() -> HashMap.hash() -> AbstractTableModel$ff19274a.hashCode() -> ... |
Hibernate1和Hibernate2
1 | HashMap.readObject() -> HashMap.hash() -> org.hibernate.engine.spi.TypedValue.hashCode() -> ... |
JSON1
1 | HashMap.readObject() -> HashMap.putVal() -> javax.management.openmbean.TabularDataSupport.equals() -> ... |
Myfaces1和Myfaces2
1 | HashMap.readObject() -> HashMap.hash() -> org.apache.myfaces.view.facelets.el.ValueExpressionMethodExpression.hashCode() -> ... |
ROME
1 | HashMap.readObject() -> HashMap.hash() -> com.sun.syndication.feed.impl.ObjectBean.hashCode() -> com.sun.syndication.feed.impl.EqualsBean.beanHashCode() -> ... |
URLDNS
1 | HashMap.readObject() -> HashMap.hash() -> java.net.URL.hashCode() -> ... |
无外乎两种:
1 | HashMap.readObject() -> HashMap.hash() -> XXX.hashCode() |
那我们看看这几个方法,HashMap.readObject()中恢复HashMap时调用HashMap.putVal()插入键值对,并且调用HashMap.hash()将返回值作为参数。
1 | private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { |
HashMap.hash()用于计算key的hash值,会先获取key.hashCode的值,再对 hashcode 进行无符号右移操作,再和 hashCode 进行异或 ^ 操作。
1 | static final int hash(Object key) { |
在HashMap.putVal()中多次调用key.equals(k)进行比较,保证HashMap的键唯一的特点。
1 | final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { |
2、HashSet
ysoserial中以HashSet为入口的是AspectJWeaver 和CommonsCollections6,这两个都是通过HashSet.readObject()调用TiedMapEntry.hashCode():
1 | HashSet.readObject() -> HashMap.put() -> HashMap.hash() -> org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode() -> ... |
HashSet底层是HashMap,readObject()反序列化恢复HashSet实例时需要创建HashMap,将其元素恢复并调用HashMap.put()插入元素,因为HashSet是Object的集合,而HashMap是键值对的集合,put插入时统一以e作为key,PRESENT
作为value。HashMap.put是用HashMap.putVal()实现的,所以后续的调用和HashMap的一样。
1 | // HashSet.readObject() |
3、LinkedHashSet
ysoserial中仅Jdk7u21用到了LinkedHashSet:
1 | LinkedHashSet.readObject() -> (HashSet)LinkedHashSet.add() -> HashMap.put() -> HashMap.hash() -> TemplatesImpl.hashCode() -> ... |
LinkedHashSet继承了HashSet,底层也是HashMap,内部没有直接定义readObject方法,但是可以调用HashSet.readObject(),跟HashSet的调用一样。
4、PriorityQueue
之前的文章里分析过了,详细不再赘述。
1 | PriorityQueue.readObject() -> java.util.PriorityQueue.heapify() -> java.util.PriorityQueue.siftDown() -> PriorityQueue.siftDownUsingComparator() -> XXXComparator.compare() |
5、Hashtable
ysoserial中CommonsCollections7用到了Hashtable,Hashtable和HashMap类似,不过Hashtable是支持同步的。
1 | Hashtable.readObject() -> Hashtable.reconstitutionPut()-> org.apache.commons.collections.map.AbstractMapDecorator.equals() -> ... |
Hashtable反序列化时创建Entry数组,将key和value通过Hashtable.reconstitutionPut()插入数组。
1 | private void readObject(java.io.ObjectInputStream s) |
Hashtable.reconstitutionPut()为了计算hash和保证键唯一,也调用了hashCode和equals(),CommonsCollections7中用到的是equals()动态加载。
1 | private void reconstitutionPut(Entry<?,?>[] tab, K key, V value) throws StreamCorruptedException |
0x03 Java集合为什么备受青睐?
Java所有类都继承于Object。既然都继承于Object,那么所有的类都是有共性的,只要是java对象都可以调用或者重写父类Object的方法。
集合可以理解为一个容器,可以储存任意类型的对象。在集合中经常会有比较或者计算Hash的操作,那么自然会频繁使用equals()和hashCode()方法,而equals()和hashCode()都是Object中定义的方法,在不同的类中也进行了重写。为了实现Gadget的动态加载,自然会用到这些方法进行连接。
这就能解释为什么Java集合会备受Gadget青睐。
此时我们可以拓展下,在PriorityQueue中比较时用的是Comparator或Comparable,Comparator是Java中一个重要的接口,被应用于比较或者排序,Comparator也在很多类中实现了。
除了equals()和hashCode(),上文没有提到的toString也是Object中定义的方法,在Gadget中也常被用到,道理都一样。
1 | public boolean equals(Object obj) { |
因此我们可以总结如下,在挖掘漏洞时可以作为反序列化的载体,当然除了这些可以以类似的思路进行拓展。
1 | HashMap.readObject() -> ... -> XXX.hashCode() |