学习在spring-aop中挖掘新反序列化gadget-chain


最近发布的一篇文章,感觉很有意思!

环境引入

这篇文章发布在了微信公众号银针安全上,文章链接如下:link

简单来说就是通过Aspectjweaver内的一个类能够进行反射调用任意方法来实现的。

原来的aspectjweaver的反序列化是通过rg.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap来实现任意文件写的,这个感觉比较鸡肋,原先写的分析和利用在AspectJWeaver反序列化 | Err0r233这里有所提及,可以通过反序列化写恶意类到classpath下,然后通过反序列化加载恶意类,前提是你得知道classpath的路径。所以分析来看上面写的比较鸡肋

链子探索

现在发掘了一条新路线,还是利用aspectjweaver,只不过类变成了org.springframework.aop.aspectj.AbstractAspectJAdvice,这个类有一个反射调用任意方法的函数invokeAdviceMethodWithGivenArgs(Object[] args),代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
Object[] actualArgs = args;
if (this.aspectJAdviceMethod.getParameterCount() == 0) {
actualArgs = null;
}

try {
ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
} catch (IllegalArgumentException var4) {
throw new AopInvocationException("Mismatch on arguments to advice method [" + this.aspectJAdviceMethod + "]; pointcut expression [" + this.pointcut.getPointcutExpression() + "]", var4);
} catch (InvocationTargetException var5) {
throw var5.getTargetException();
}
}

其中从args调取actualArgs,然后调用protected transient Method aspectJAdviceMethod的invoke。虽然这个method并不能够被反序列化,但是可以看到下面的readObject函数:

1
2
3
4
5
6
7
8
9
private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
inputStream.defaultReadObject();

try {
this.aspectJAdviceMethod = this.declaringClass.getMethod(this.methodName, this.parameterTypes);
} catch (NoSuchMethodException var3) {
throw new IllegalStateException("Failed to find advice method on deserialization", var3);
}
}

通过readObject能够对asjectJAdviceMethod重新进行赋值。对于一个反射调用的方法,需要Method、Object、args这三要素,因此姑且解决了Method的问题。接下来由于args是我们传入的,目前虽然不清楚这个args是否可控,但是我们可以对该场景做一个简化,也就是先暂时假设args是可控的。因此就剩最后一个Object

回看invokeAdviceMethodWithGivenArgs最后的invoke,由于method.invoke的第一个参数就是object,由此可以得到这个方法的invoke调用的object是来自于this.aspectInstanceFactory.getAspectInstance()的,跟进这个方法,发现他其实是一个接口:

1
2
3
4
5
6
7
8
9
10
11
12
package org.springframework.aop.aspectj;

import org.springframework.core.Ordered;
import org.springframework.lang.Nullable;

public interface AspectInstanceFactory extends Ordered {
Object getAspectInstance();

@Nullable
ClassLoader getAspectClassLoader();
}

因此我们需要找到一个实现AspectInstanceFactory的类,并且还需要可序列化(继承Serializable)

这里的SingletonAspectInstanceFactory就实现了我们上面的两个要求

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.aop.aspectj;

import java.io.Serializable;

import org.springframework.core.Ordered;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
* Implementation of {@link AspectInstanceFactory} that is backed by a
* specified singleton object, returning the same instance for every
* {@link #getAspectInstance()} call.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @since 2.0
* @see SimpleAspectInstanceFactory
*/
@SuppressWarnings("serial")
public class SingletonAspectInstanceFactory implements AspectInstanceFactory, Serializable {

private final Object aspectInstance;


/**
* Create a new SingletonAspectInstanceFactory for the given aspect instance.
* @param aspectInstance the singleton aspect instance
*/
public SingletonAspectInstanceFactory(Object aspectInstance) {
Assert.notNull(aspectInstance, "Aspect instance must not be null");
this.aspectInstance = aspectInstance;
}


@Override
public final Object getAspectInstance() {
return this.aspectInstance;
}

@Override
@Nullable
public ClassLoader getAspectClassLoader() {
return this.aspectInstance.getClass().getClassLoader();
}

/**
* Determine the order for this factory's aspect instance,
* either an instance-specific order expressed through implementing
* the {@link org.springframework.core.Ordered} interface,
* or a fallback order.
* @see org.springframework.core.Ordered
* @see #getOrderForAspectClass
*/
@Override
public int getOrder() {
if (this.aspectInstance instanceof Ordered) {
return ((Ordered) this.aspectInstance).getOrder();
}
return getOrderForAspectClass(this.aspectInstance.getClass());
}

/**
* Determine a fallback order for the case that the aspect instance
* does not express an instance-specific order through implementing
* the {@link org.springframework.core.Ordered} interface.
* <p>The default implementation simply returns {@code Ordered.LOWEST_PRECEDENCE}.
* @param aspectClass the aspect class
*/
protected int getOrderForAspectClass(Class<?> aspectClass) {
return Ordered.LOWEST_PRECEDENCE;
}

}

对该类内的invokeAdviceMethodWithGivenArgs进行跟踪,找到invokeAdviceMethod->invokeAdviceMethodWithGivenArgs,虽然这个方法有重构,但是无论跟踪哪一个重构都会找到类似的AspectJ?????Advice,从而找到org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(),大致的调用链如下,自己找的话也很简单,直接alt+f7来找用法即可:

1
2
3
4
org.springframework.aop.framework.ReflectiveMethodInvocation#proceed->
org.springframework.aop.aspectj.AspectJxxxAdvice#invoke->
org.springframework.aop.aspectj.AbstractAspectJAdvice#invokeAdviceMethod->
org.springframework.aop.aspectj.AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs

该proceed方法如下:

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
@Override
@Nullable
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);
}
}

一行行看吧,首先是下标到最后的时候就结束

然后获取interceptorOrInterceptionAdvice,然后对它进行判断,如果它是InterceptorAndDynamicMethodMatcher的实例就进入第一个分支。但实际上我们这个调用的链子来看,下雨不调用的应该是org.springframework.aop.aspectj.AspectJxxxAdvice,因此是一个进入else的状态(并不是class InterceptorAndDynamicMethodMatcher的实例),因此会进入else的invoke,这样会省去进入if之后很多的判断等工作

接下来来看获取interceptorOrInterceptionAdvice的逻辑:

1
2
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);

他从这个interceptorsAndDynamicMethodMatchers来获取,可以得到这个Matchers是一个List:

1
protected final List<?> interceptorsAndDynamicMethodMatchers;

List是可以被序列化的,而且this.currentInterceptorIndex是一个下标(int),因此这个interceptorOrInterceptionAdvice也是我们可控的

接下来的问题来到了这个类本身,仔细观察可以发现这个类是没有继承Serializable接口的,因此这个类本身不能够被反序列化,因此我们得找到一个能够动态创建该ReflectiveMethodInvocation的方法,继续查找:

发现在org.springframework.aop.framework.JdkDynamicAopProxy里有创建:

该方法在创建完ReflectiveMethodInvocation后就立刻调用了proceed方法,完美符合要求,因此着重注意该构造方法:

1
2
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);

需要传入proxy、target、method、args、targetClass、chain

而chain对应这个Invocation里的interceptorsAndDynamicMethodMatchers,因此要着重注意chain该如何控制传入

因此我们现在需要:

  • 控制chain的传入。先通过JdkDynamicAopProxy.invoke()触发,然后创建ReflectiveMethodInvocation,然后传入一个包含有我们AspectJxxxAdviceList(这里采用文章里的AspectJAroundAdvice),最后通过chain来走到最后的反射调用

如何控制chain的输入

往回推,可以看到在JdkDynamicAopProxychain是这样被定义的:

1
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

因此现在变成了要让this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass)能够返回一个List,并且这个List里有我们需要的对象

接下来分析这个方法:

1
2
3
4
5
6
7
8
9
10
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
MethodCacheKey cacheKey = new MethodCacheKey(method);
List<Object> cached = this.methodCache.get(cacheKey);
if (cached == null) {
cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
this, method, targetClass);
this.methodCache.put(cacheKey, cached);
}
return cached;
}

可以看到这个方法返回的cached有两种获取途径:

  • methodCache中有的话从methodCache中获取
  • 如果第一步中没有获取到的话,就会通过getInterceptorsAndDynamicInterceptionAdvice方法来获取

先看第一种获取途径,先查看这个methodCache

1
private transient Map<MethodCacheKey, List<Object>> methodCache;

它本身是一个transient,也就是不能够被序列化的,而且readObject之后是一个初始化的ConcurrentHashMap,什么也做不了

1
2
3
4
5
6
7
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
// Rely on default serialization; just initialize state after deserialization.
ois.defaultReadObject();

// Initialize transient fields.
this.methodCache = new ConcurrentHashMap<>(32);
}

因此第一条路只能放弃,只能够去看第二条途径,跟进getInterceptorsAndDynamicInterceptionAdvice会来到DefaultAdvisorChainFactorygetInterceptorsAndDynamicInterceptionAdvice。其中Advised config, Method method, @Nullable Class<?> targetClass这三个都是可控的。其中Advised config是一个AdvisedSupport实例(因为Advised查看可知它是一个接口)

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
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,关键在于中间的逻辑是怎么添加的,可以很明显地观察到下面两个分支(else ifelse)最后的interceptorList由两步组成:

1
2
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));

上面的if条件虽然复杂,但是不难看出这个list都是添加至interceptors(或者interceptors中的元素),而interceptorsregistry.getInterceptors(advisor)添加的

因此上面的方法简化之后可以看作均进入了上面提及的这两步:

1
2
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));

因此分析registry的获取方法:

1
AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();

因此registry.getInterceptors变成了DefaultAdvisorAdapterRegistry.getInterceptors。跟进DefaultAdvisorAdapterRegistry类,查看它的getInterceptors函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
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]);
}

可以发现只要adviceMethodInterceptor的实例的话就会将它加入到interceptors,然后返回。通过此处可以控制我们的chain了。但是我们需要的public class AspectJAroundAdvice extends AbstractAspectJAdvice implements MethodInterceptor, Serializable很明显只实现了MethodInterceptor,没有实现advice(Advice advice = advisor.getAdvice();这一步需要它首先是一个Advice)

因此此处可以通过JdkDynamicAopProxy来代理这个Advice类,并且设置反射调用对象是AspectJAroundAdvice即可。

要触发代理,这里选择用BadAttributeValueException触发toString然后触发代理方法即可

payload

由于最后是通过反射调用任意方法,因此可以使用templatesImpl.newTransform来实现加载字节码从而调用任意方法。

https://github.com/Ape1ron/SpringAopInDeserializationDemo1