上回讲到CC1中的TransformedMap#checksetValue调用ChainedTransformer#transform这条链子,这回学习LazyMap#get这条路。CC6也用到了LazyMap这条路,因此这条链子需要细品。
分析
从transform说起,上回CC1讲解到了通过ChainedTransformer链式调用从而实现RCE,我们的目的就是找谁调用ChainedTransformer#transform,如果类的实例一般都可以通过构造方法初始化,因此我们找谁调用了transform就可以了。
Find Usages,发现LazyMap#get方法调用了transform,如果map中不包含这个key,则调用;若包含,则return map.get(key)。
与TransformedMap相同,LazyMap也是通过decorate方法实例化自身,factory我们传chainedTransformer就可以了,至于map我们先传一个空的。
前面不变,分析到这一步的exp为:
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"}),
new ConstantTransformer(1),
};
ChainedTransformer c=new ChainedTransformer(transformers);
HashMap map=new HashMap();
Map lazyMap= LazyMap.decorate(map,c); //实例化LazyMap
接下来找谁调用了LazyMap#get,我们可以看到AnnotationInvocationHandler#invoke调用了get,我们只需要把memberValues赋值lazyMap即可。
InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,AnnotationInvocationHandler继承自InvocationHandler。简而言之我们需要通过Proxy代理AnnotationInvocationHandler的一个实例,只要这个代理调用了任意一个方法,那么就会触发AnnotationInvocationHandler#invoke,显然,这个被代理的实例的membervalue要赋值lazyMap。
为此,我们首先要实例化一个被代理的annotationInvocationHandler,接着要实例化一个annotationInvocationHandler用来把这个代理对象传进去。
链子:
AnnotationInvocationHandler#readobject
Proxy(AnnotationInvocationHandler).xxx
AnnotationInvocationHandler#invoke
LazyMap#get
ChainedTransformer#transform
InvokerTransformer#transform
调试
直接断到AnnotationInvocationHandler#invoke方法中的执行get处,
我们查看一下当前的几个重要变量。memberValues正是exp第一次实例化传入的LazyMap,var2是触发invoke的方法名称。
回看AnnotationInvocationHandler#readObject,这里确实调用了entrySet()方法。
跟进get方法,这里传来的key就是方法名entrySet,显然这个空map不存在这个key,因此执行transform。
接着就走到了ChainedTransformer#transform链式调用InvokerTransformer从而RCE。
RCE。
隐藏异常日志的思考
个人思考,最后也没分析出为啥。
细心的小伙伴儿会看到exp里的Transformer数组最后多处要给new ConstantTransformer(1),这是干啥用的?
先上结论:
如果不加这一句,则报错:
Exception in thread “main” java.lang.ClassCastException: java.lang.ProcessImpl cannot be cast to java.util.Set
如果加上,则报错:
**Exception in thread “main” java.lang.ClassCastException: java.lang.Integer cannot be cast to java.util.Set **
ProcessImpl是Process抽象类的实现类,运维人员一看,这块反序列化居然会出现Process这个进程类,肯定会排查。
可以看到链式调用执行完calc弹出计算器,此时object为ProcessImpl。这是因为getRuntime.exec()实际上就是调用ProcessBuilder.start(),而processImpl是被ProcessBuilder.start()这个方法进行创建的。
而显然他还要接着触发ConstantTransformer(),从而返回1。
LazyMap#get返回1。
调试的过程中发现“Object var6 = this.memberValues.get(var4);”get方法返回的var6居然是-1,百思不得其解。
而调到这里,再往下分析就直接跳到这里,报错。
往下分析到了ObjectStreamClass#invokeReadObject,可以看到动态代理调用invoke是通过ObjectStreamClass来调用的。
就是在这个方法抛出了异常。
至于他为啥会报这个,我怀疑是该invoke方法由entrySet()触发所以会报这个错误?