专栏名称: 合天网安实验室
为广大信息安全爱好者提供有价值的文章推送服务!
目录
相关文章推荐
南京日报  ·  “碰一下钱就没了”?支付宝紧急回应 ·  昨天  
南京日报  ·  “碰一下钱就没了”?支付宝紧急回应 ·  昨天  
长城新媒体  ·  支付宝紧急声明! ·  2 天前  
长城新媒体  ·  支付宝紧急声明! ·  2 天前  
51好读  ›  专栏  ›  合天网安实验室

DSL-JSON参数走私浅析

合天网安实验室  · 公众号  ·  · 2024-08-06 17:21

正文

0x00 前言

DSL-JSON 是一个为 JVM(Java 虚拟机)平台设计的高性能 JSON 处理库,支持 Java、Android、Scala 和 Kotlin 语言。它被设计为比任何其他 Java JSON 库都快,与最快的二进制 JVM 编解码器性能相当。

在 DSL-JSON 库中, deserialize 方法和 newReader 都与 JSON 数据的反序列化有关。

  • com.dslplatform.json.DslJson#deserialize可以直接将 JSON 字符串反序列化为指定的 Java 对象类型。这个方法通常用于简单场景,其中 JSON 数据可以直接映射到一个 POJO。

  • com.dslplatform.json.DslJson#newReader会返回一个 JsonReader 对象,这个对象可以用来反序列化 JSON 数据。使用 JsonReader 提供了更细粒度的控制,允许你逐个处理 JSON 元素,而不是直接映射整个 JSON 文档到一个对象。

下面简单看看具体的JSON解析过程。

0x01 DSL-JSON解析过程

不论是 deserialize 还是 newReader 的方式,在反序列化时,都会先通过 typeLookup 查找与类型对应的 ReadObject 反序列化器。然后调用对应的read方法进行处理:

在tryFindReader方法中,首先会在 readers 映射中查找是否已经存在与 manifest 对应的 ReadObject 反序列化器。如果存在,则直接返回,否则则调用 extractActualType 方法获取 manifest 的实际类型 actualType,这里一般是对自定义类型进行处理:

以HashMap的类型为例,对应的反序列化器为com.dslplatform.json.ObjectConverter,其主要支持以下数据类型:

查看其read方法的调用逻辑,主要是在deserializeMap方法进行处理:

在deserializeMap方法中,首先检查当前的 JSON 标记是否为 { (表示映射的开始)。如果不是,则抛出解析异常。如果下一个标记是 } ,则创建一个空的 LinkedHashMap 并返回。否则,创建一个新的 LinkedHashMap 对象 res等待进行内容(key-value)的解析:

首先会调用com.dslplatform.json.JsonReader#readKey方法对键进行解析:

具体的解析逻辑主要在com.dslplatform.json.JsonReader#parseString进行处理,首先检查当前字符是否以 " (表示字符串的开始)。如果不是,则抛出解析异常:

然后进入循环流程,从 JSON 数据流中读取字符,并将其复制到 _tmp 数组中。当遇到双引号 " (表示字符串的结束),并返回复制的字符数。当遇到反斜杠 \ (转义字符)时。会先退出循环,进入转义字符处理逻辑:

对于转义字符,会根据后续字符的值进行不同的处理,包括普通转义字符、Unicode等:

对于 \x61 的场景,DSL-JSON明显是不支持的,会抛出 Invalid escape combination detecte 异常。

最后返回对应的length,获得当前的键属性。然后调用deserializeObject方法获取对应的值,这里会根据 JSON 值的类型,调用相应的反序列化逻辑,将 JSON 值转换为相应的 Java 对象,例如如果是 " 开头的话,会调用com.dslplatform.json.JsonReader#readString进行处理,如果均匹配不上,会调用NumberConverter.deserializeNumber当成数字进行处理,整个过程包含了一些错误处理逻辑,确保在遇到非法 JSON 数据时能够正确地抛出异常:

获取完对应的值后,如果此时的标记是逗号 , ,则继续读取下一个键值对,并将其存储到 res 中:

最后检查最后一个标记是否为右大括号 } ,并返回前面填充的解析内容:

以上是DSL-JSON大致的解析过程。

0x02 参数走私场景

在前面的分析过程中,DSL-JSON在调用deserializeMap处理时,会创建一个新的 LinkedHashMap 对象 res对JSON内容的解析结果进行存储:

这里的res数据类型是LinkedHashMap,也就是说,如果在put操作时使用了已存在的键,则新值会替换旧值,原有的键值对会被新的键值对覆盖。 默认情况下在反序列化时,会取重复键值的后者

下面结合JavaWeb中常见的JSON解析库的解析特性,看看其重复键值对情况下潜在的参数走私场景。

2.1 Unicode解码差异

在前面分析的时候提到,在tryFindReader方法中,首先会在 readers 映射中查找是否已经存在与 manifest 对应的 ReadObject 反序列化器。如果存在,则直接返回,否则则调用 extractActualType 方法获取 manifest 的实际类型 actualType。这里获取到的反序列化器的解析方式是有区别的。例如这里指定序列化成自定义的User对象:

DslJson dslJson = new DslJson();
JsonReader jsonReader = dslJson.newReader(buff);
User user = jsonReader.next(User.class);

跟进解析过程,可以看到获取到的反序列化器是跟ObjectFormatDescription相关的:

在其bind方法中,会调用bindContent方法对JSON内容进行处理封装:

可以看到当满足WeakHash的匹配时,会调用User类的set方法对对应的属性进行赋值,value的获取是通过com.dslplatform.json.JsonReader#readString对JSON进行处理,实际上还是通过com.dslplatform.json.JsonReader#parseString进行解析:

简单看看ObjectFormatDescription#bindContent的逻辑,看看WeakHash的具体含义。在ObjectFormatDescription#bindContent的逻辑中,首先检查当前的 JSON 标记是否为 } ,如果是,则检查是否有必填属性未被赋值:

否则进入JSON的解析,进入一个循环,遍历所有需要绑定的属性。在循环中,对于每个属性,计算属性名称的WeakHash,并与预计算的WeakHash进行比较。若两者匹配,则进一步比较属性名称是否完全匹配,若匹配则对对应的属性进行赋值,如果下一个标记是逗号 , ,则继续读取下一个属性。否则,退出循环:

也就是说,WeakHash主要跟反序列化过程中匹配的属性有关。在fillNameWeakHash中,主要是通过calcWeakHash方法来计算Weakhash的,查看具体的计算方式:

在calcWeakHash方法中,首先还是判断是否以 " 开头,然后进入一个循环,从 JSON 数据流中读取属性名称的字节,并将它们累加到 hash 中:

  • 如果遇到反斜杠 \ (表示转义字符),则跳过下一个字节

  • 如果遇到双引号 " (表示属性名称的结束),则退出循环

  • 如果读取到数据流的末尾,则调用 calcWeakHashAndCopyName 方法计算最终的哈希值并复制属性名称

这里有一个比较关键的节点是,当遇到 反斜杠 \ 时,不会进一步对类似Unicod等字符进行额外的处理,直接跳过下一个字节 。那么是否说明当使用这种方式进行JSON解析时,无法识别Unicode编码的key呢?

这里从debug信息可以看到,以属性activity为例,预计算的WeakHash为1050:

若经过Unicode编码处理后,获取到的WeakHash为1269,此时由于两者不一致,导致不会进一步调用对应属性的set方法,设置对应的内容:

也就是说,跟基础类型Map相比,类似 User User = jsonReader.next(User.class); 自定义类型的解析,DSL-JSON仅仅支持值的Unicode编码,不像fastjson/jackson等也支持Key的Unicode编码。







请到「今天看啥」查看全文


推荐文章
长城新媒体  ·  支付宝紧急声明!
2 天前
长城新媒体  ·  支付宝紧急声明!
2 天前
电影派  ·  这部19禁韩版洛丽塔真的太脏了
8 年前