关于java反序列化中jackson链子不稳定问题

jackson链子随机原理分析

首先我们知道Jackson反序列化原理就是触发toString会触发POJONode父类的父类BaseJsonNode的toString方法,其后会调用 com.fasterxml.jackson.databind.ObjectWriter#writeValueAsString 方法来将自身序列化为 json 字符串。

可以看到最后是在com.fasterxml.jackson.databind.ser.BeanPropertyWriter#serializeAsField方法中利用反射来执行其 getter 方法

Object value = this._accessorMethod == null ? this._field.get(bean) : this._accessorMethod.invoke(bean, (Object[])null)

![image-20260131221012496](assets/network-asset-image-20260131221012496-20260131225634-krapqrx.png)

往前看 com.fasterxml.jackson.databind.ser.std.BeanSerializerBase#serializeFields方法

![image-20260131221211014](assets/network-asset-image-20260131221211014-20260131225634-0wpu4kj.png)

props数组中每个值是BeanPropertyWriter 类型对象,之后循环调用其中的serializeAsField 方法来执行对应方法 。所以问题的关键就是找清楚这个props 数组的顺序,一路追踪这个变量,发现其根源是在 com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector#collectAll中调用 _addMethods(props) 方法来获取相关 getter 方法,之后将其添加到 prpos 属性中

![image-20260131221305357](assets/network-asset-image-20260131221305357-20260131225634-8qfptu1.png)

![image-20260131221326481](assets/network-asset-image-20260131221326481-20260131225634-8obuknq.png)

其中this._classDef.memberMethods()获取到的 Methods 的顺序决定了最终执行 getter 方法的顺序,继续深入后就能发现最终利用反射来获取 Methods。所以其实问题就是反射 getDeclaredMethods 方法获取到的 Method 数组顺序是不确定的

解决随机性问题

利用 org.springframework.aop.framework.JdkDynamicAopProxy 来解决jackson链子的随机性问题。我们知道java中动态代理是十分强大的,被代理对象所能调用的方法取决与我们所给的接口,其功能取决与我们所给的 handler。当我们用java的反射 getDeclaredMethods 方法去获取其所有方法时也是根据我们提供的接口去获取的

关键就是其反射获得的方法完全是根据所给的接口来的,不管被代理的类是否实现了对应的方法。动态代理中方法的实现都放在了handler中的 invoke 方法中了,调用其任意方法都会在 invoke 方法中执行,所以主要就是看所给的接口和handler就可以了

javax.xml.transform.Templates 接口其只有 newTransformergetOutputProperties 这个两个方法,让他作为我们代理所需的接口,这样最终通过 getDeclaredMethods 获取到的方法就只有 newTransformergetOutputProperties 了,那么最终获得的getter方法便只有 getOutputProperties 了。

所以这里得找这样一个 handler,它的 invoke 方法中能的执行我们所调用的方法即可。

JdkDynamicAopProxy 是 Spring 框架中的一个类,它实现了 JDK 动态代理机制,用于创建代理对象来实现面向切面编程(AOP)的功能。

这里看一下 org.springframework.aop.framework.JdkDynamicAopProxy 的具体源码,这里 advised 属性可控。

![image-20260131222035750](assets/network-asset-image-20260131222035750-20260131225634-an9rd79.png)

重点看其 invoke 方法

![image-20260131222212434]()

 Object var17 = AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
 
这里this.advised我们可以控制,我们将所需的 TemplatesImpl 的对象用 org.springframework.aop.framework.AdvisedSupport 封装即可

所以可以构造POC稳定触发getOutputProperties

 AdvisedSupport advisedSupport = new AdvisedSupport();
        advisedSupport.setTarget(templates);
        Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
        constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
        Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler);