CommonsCollections6

CommonsCollections6

摘要:CC6

0x01 链子

根据PoC中的注释,后面接的还是LazyMap链,只不过前面的入口点不一样了。

1
2
3
4
5
6
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()

1、来到HashSet.readObject()方法中,第342行,对自身的map(此时是HashMap对象),调用put,跟进。

CC61

2、发现put中对key调用了hash方法,跟进。

CC62

3、发现hash方法中调用了key的hashCode()方法,此时key为TiedMapEntry对象,跟进到其hashCode()方法。

CC63

4、发现getValue方法,后续直接跟进LazyMap链即可。

CC64

至此,该链子分析完毕。

0x02 ysoserial源码分析

补一下Field的反射知识

1
2
Class clazz = Class.forName("com.Hn13");
Field f = clazz.getFiled("age");

上面这条语句,获取Hn13类的age属性。属于对“”的操作;

1
2
Object objHn13 = clazz.newInstance();
Integer age = f.get(objHn13);

上面这条语句,获取objHn13这个Hn13对象的age属性。属于对“对象”的操作。

1
f.set(objHn13, 18);

上面这个操作,将objHn13这个Hn13对象的age属性设置为18。属于对“对象”的操作。

具体分析

根据上面对链子的调试,可以发现链子是经由HashSet.readObject()到TiedMapEntry.hashCode()最后触发LazyMap链。那么我们在构造PoC的时候就应该在HashSet中加入包裹了由Transformer链修饰过的LazyMap的TiedMapEntry,这也是ysoserial中CC6的PoC书写者做的。

了解下HashSet:

HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。

HashSet 允许有 null 值。

HashSet 是无序的,即不会记录插入的顺序。

HashSet 不是线程安全的, 如果多个线程尝试同时修改 HashSet,则最终结果是不确定的。 您必须在多线程访问时显式同步对 HashSet 的并发访问。

我们需要手动修改下HashSet中元素的key值,那么就需要了解HashSet是如何存储数据的,翻看HashSet源码,能够发现HashSet将数据存放在map属性里,这个map为HashMap对象;

CC65

CC66

再跟进到HashMap中,HashMap是将一项用户加入的数据存储到一个Node对象里,然后将这个Node对象存储到一个名为table的Node数组中。

CC67

CC68

也就是数,我们构造PoC的时候更改的应该是table里的数据,也就是将恶意的TiedMapEntry对象放入到HashSet的map(为HashMap对象)里的table中去。回头看CC6的PoC,它运用了反射机制来做到这一点:

首先是经典的Transformer链:

CC69

而后,用Transformer链修饰LazyMap后用TiedMapEntry包装:

CC610

这个地方还讲我们序列化用到的HashSet对象先初始化了,往里面加了一个foo值。接着往下就是利用Field反射,修改map中的key了,过程挺美妙的:

首先拿到map这个HashSet对象的map属性

CC611

然后拿到这个HashMap的table属性

CC612

然后再获取包含了刚刚放到里面的“foo”的Node对象,并将指针指向我们在外面新建的node,并将恶意TiedMapEntry替换这个Node对象里的key:

CC613

到此,CC6的PoC分析完毕。


评论