上回讲到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)。

image-20220823132756796

与TransformedMap相同,LazyMap也是通过decorate方法实例化自身,factory我们传chainedTransformer就可以了,至于map我们先传一个空的。

image-20220823132938918

前面不变,分析到这一步的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即可。

image-20220823135153801

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处,

image-20220823141057434

我们查看一下当前的几个重要变量。memberValues正是exp第一次实例化传入的LazyMap,var2是触发invoke的方法名称。

image-20220823141307332

回看AnnotationInvocationHandler#readObject,这里确实调用了entrySet()方法。

image-20220823141449245

跟进get方法,这里传来的key就是方法名entrySet,显然这个空map不存在这个key,因此执行transform。

image-20220823141657649

接着就走到了ChainedTransformer#transform链式调用InvokerTransformer从而RCE。

image-20220823141853772

RCE。

image-20220823142035973

隐藏异常日志的思考

个人思考,最后也没分析出为啥。

细心的小伙伴儿会看到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()这个方法进行创建的。

image-20220823142210674

而显然他还要接着触发ConstantTransformer(),从而返回1。

image-20220823142559561

LazyMap#get返回1。

image-20220823142644704

调试的过程中发现“Object var6 = this.memberValues.get(var4);”get方法返回的var6居然是-1,百思不得其解。

image-20220823143749904

而调到这里,再往下分析就直接跳到这里,报错。

image-20220823144630811

往下分析到了ObjectStreamClass#invokeReadObject,可以看到动态代理调用invoke是通过ObjectStreamClass来调用的。

image-20220823145822137

就是在这个方法抛出了异常。

image-20220823150146149

至于他为啥会报这个,我怀疑是该invoke方法由entrySet()触发所以会报这个错误?

参考链接
  1. https://space.bilibili.com/2142877265?spm_id_from=333.337.0.0 白日梦组长