Jackson中Json隐式数据类型转换与参数走私浅析
字数 1310 2025-08-20 18:17:59

Jackson中Json隐式数据类型转换与参数走私分析

0x00 前言

在Java中处理JSON数据时,隐式数据类型转换通常由JSON处理库自动完成。这些库会根据JSON值的格式和上下文推断并转换数据类型。本文以Jackson库为例,深入分析其隐式类型转换机制及可能导致的参数走私问题。

0x01 Jackson隐式转换(String->int)过程

基本示例

考虑以下User类定义:

public class User {
    private int roleId;

    public int getRoleId() {
        return roleId;
    }

    public void setRoleId(int roleId) {
        this.roleId = roleId;
    }
}

通过@RequestBody传递User对象时,Spring Boot默认使用Jackson处理:

@RequestMapping(value = "/create")
public User demo(@RequestBody User user){
    return user;
}

Jackson在默认情况下,即使JSON中的数字值被引号包围(表示字符串),也能成功转换为int类型的roleId。

转换过程分析

  1. 初始化阶段

    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    User user = objectMapper.readValue(body, User.class);
    
  2. 解析流程

    • 调用_readMapAndClose方法初始化解析器
    • 创建DefaultDeserializationContext上下文对象
    • 读取JsonToken并根据类型处理数据
  3. 反序列化处理

    • 通过BeanDeserializerdeserialize方法处理
    • vanillaDeserialize方法中遍历JSON字段
    • 对于int类型属性,使用IntegerDeserializer#deserialize处理
  4. 字符串到整数的转换

    • 检查JSON Token类型
    • 对于字符串Token(VALUE_STRING),调用_parseIntPrimitive方法
    • 处理前会调用trim()方法清理空格
    • 最终通过NumberInput.parseInt完成转换

0x02 参数走私案例

场景描述

应用使用User类的roleId(int类型)表示用户角色类型,同时在拦截器中使用Fastjson处理请求体:

Map<String, Object> params = JSON.parseObject(body, HashMap.class);
if(params.get("roleId").equals("1")){
    // 鉴权逻辑
}

利用差异

  1. 空格处理差异

    • Jackson会忽略字符串中的空格
    • Fastjson不会忽略空格
    String body = "{\"roleId\":\"1 \"}";
    // Jackson解析结果为1
    // Fastjson解析结果为"1 ",不等于"1"
    
  2. 前导零处理

    • Jackson会忽略数字字符串前导的零
    • Fastjson会保留原始字符串形式
    String body = "{\"roleId\":\"00001\"}";
    // Jackson解析结果为1
    // Fastjson解析结果为"00001"
    

攻击向量

通过构造特殊格式的JSON,利用不同解析器的处理差异绕过安全检测:

{
    "roleId": "1 "  // 带空格的字符串
}

{
    "roleId": "00001"  // 带前导零的字符串
}

0x03 其他类型处理

long类型处理

_parseLongPrimitive方法同样会:

  • 忽略字符串中的空格
  • 处理前导零
  • 支持从字符串到long的隐式转换

double类型处理

类似地,double类型的处理也会:

  • 自动清理字符串中的空格
  • 完成字符串到double的转换

Gson的类似行为

Gson库也存在类似的隐式转换逻辑:

String body = "{\"roleId\":\"123456789 \"}";
Gson gson = new Gson();
User user = gson.fromJson(body, User.class);
// 解析结果为123456789

防御建议

  1. 统一JSON解析器:确保应用各层使用相同的JSON处理库
  2. 严格类型检查:在拦截器中明确检查数据类型
  3. 输入规范化:对输入值进行trim等规范化处理
  4. 使用严格模式:配置解析器不自动进行类型转换
  5. 白名单验证:对关键字段进行严格的白名单验证

总结

Jackson等JSON库的隐式类型转换机制虽然提供了便利,但也带来了潜在的安全风险。特别是在混合使用不同JSON库或存在安全检测逻辑时,可能被利用进行参数走私攻击。开发者应当充分了解所使用的JSON库的行为特性,并在安全关键路径上进行严格的数据验证。

Jackson中Json隐式数据类型转换与参数走私分析 0x00 前言 在Java中处理JSON数据时,隐式数据类型转换通常由JSON处理库自动完成。这些库会根据JSON值的格式和上下文推断并转换数据类型。本文以Jackson库为例,深入分析其隐式类型转换机制及可能导致的参数走私问题。 0x01 Jackson隐式转换(String->int)过程 基本示例 考虑以下User类定义: 通过 @RequestBody 传递User对象时,Spring Boot默认使用Jackson处理: Jackson在默认情况下,即使JSON中的数字值被引号包围(表示字符串),也能成功转换为int类型的roleId。 转换过程分析 初始化阶段 : 解析流程 : 调用 _readMapAndClose 方法初始化解析器 创建 DefaultDeserializationContext 上下文对象 读取 JsonToken 并根据类型处理数据 反序列化处理 : 通过 BeanDeserializer 的 deserialize 方法处理 在 vanillaDeserialize 方法中遍历JSON字段 对于int类型属性,使用 IntegerDeserializer#deserialize 处理 字符串到整数的转换 : 检查JSON Token类型 对于字符串Token(VALUE_ STRING),调用 _parseIntPrimitive 方法 处理前会调用 trim() 方法清理空格 最终通过 NumberInput.parseInt 完成转换 0x02 参数走私案例 场景描述 应用使用User类的roleId(int类型)表示用户角色类型,同时在拦截器中使用Fastjson处理请求体: 利用差异 空格处理差异 : Jackson会忽略字符串中的空格 Fastjson不会忽略空格 前导零处理 : Jackson会忽略数字字符串前导的零 Fastjson会保留原始字符串形式 攻击向量 通过构造特殊格式的JSON,利用不同解析器的处理差异绕过安全检测: 或 0x03 其他类型处理 long类型处理 _parseLongPrimitive 方法同样会: 忽略字符串中的空格 处理前导零 支持从字符串到long的隐式转换 double类型处理 类似地,double类型的处理也会: 自动清理字符串中的空格 完成字符串到double的转换 Gson的类似行为 Gson库也存在类似的隐式转换逻辑: 防御建议 统一JSON解析器 :确保应用各层使用相同的JSON处理库 严格类型检查 :在拦截器中明确检查数据类型 输入规范化 :对输入值进行trim等规范化处理 使用严格模式 :配置解析器不自动进行类型转换 白名单验证 :对关键字段进行严格的白名单验证 总结 Jackson等JSON库的隐式类型转换机制虽然提供了便利,但也带来了潜在的安全风险。特别是在混合使用不同JSON库或存在安全检测逻辑时,可能被利用进行参数走私攻击。开发者应当充分了解所使用的JSON库的行为特性,并在安全关键路径上进行严格的数据验证。