Hotdry.

Article

UTF-16 代理对验证缺失:从字符串截断到 JSON 乱码的安全因果链

解析 UTF-16 解析器中无效代理对(0xD800-0xDFFF)的验证缺失如何导致字符串截断、JSON 乱码与安全漏洞的因果链路,并给出工程化防护参数。

2026-05-16systems

UTF-16 作为一种变长编码格式,在处理超出基本多语言平面(BMP,U+0000 至 U+FFFF)的字符时,需要借助代理对(Surrogate Pair)机制。代理对由两个 16 位码元组成:高代理(High Surrogate,范围 0xD800–0xDBFF)和低代理(Low Surrogate,范围 0xDC00–0xDFFF)。这两个范围与 BMP 字符完全不相交,形成自同步特性 —— 解析器仅凭单个码元的数值即可判断其类型。这一设计看似优雅,却在工程实践中埋下了深层的验证陷阱。

代理对验证的结构性缺陷

UTF-16 代理对验证的核心问题在于大多数解析器仅检查码元是否落在有效范围内,却忽略了两个关键约束:高代理必须后跟低代理,且低代理必须前接高代理。具体而言,无效序列包括三类:孤立高代理(0xD800–0xDBFF 后无低代理)、孤立低代理(0xDC00–0xDFFF 前无高代理)、以及错误顺序的相邻代理(低代理出现在高代理之前)。

这种验证缺失的根源在于代理区域的特殊性。0xD800–0xDFFF 范围内的码元本身并非非法,它们只是不能单独存在。解析器若将其作为普通字符处理,会导致两个层面的问题:字符串长度计算错误,以及跨边界数据交换时的编码转换失败。Python 2 时代的 unicode 对象、Java 早于 1.5 版本的 String 实现、以及 Windows API 中的宽字符函数,都长期受到这一缺陷的困扰。即使在 2020 年代的现代代码库中,缺乏严格代理对验证的库仍然广泛存在。

因果链路:从截断到任意代码执行

无效代理对引发的安全问题并非孤立的编码错误,而是一条清晰的因果链路。

第一步:字符串截断与长度误判。 当解析器遇到孤立高代理时,许多实现选择直接跳过该码元或将其截断处理。例如,一个包含 \uD800 的字符串在计算 length 属性时可能返回错误值,导致下游应用以错误的偏移量切片字符串。在流式处理场景中,这种截断会进一步引发后续数据错位,形成级联错误。

第二步:JSON 序列化与反序列化乱码。 JSON 规范要求所有字符串必须是有效的 Unicode 序列。当 UTF-16 数据中存在无效代理对时,序列化器可能直接输出原始码元,而反序列化器在尝试将其转换为 UTF-8 或其他编码时失败。ujson 库的 CVE-2022-31116 漏洞正是这一问题的典型案例:攻击者通过构造包含非法转义代理字符的 JSON 字符串,触发解析器错误解码,导致字符串损坏。更危险的是,这种损坏可能造成字典键混淆与值覆盖,攻击者借此操纵数据结构。

第三步:安全边界突破。 JavaScript 的 JSON.stringify 实现同样存在代理对验证缺失问题。当字符串中包含不成对的代理码元时,序列化结果可能包含无效的 UTF-16 序列,导致跨语言数据交换(如 Node.js 后端与浏览器前端)时出现解析失败或数据篡改。这类问题在微服务架构中尤为突出:不同服务可能使用不同的字符编码处理逻辑,未验证的代理对在传递过程中可能绕过输入过滤,触发后端处理逻辑的异常行为。

工程化防护参数

针对代理对验证缺失问题,工程师应在三个层次建立防御。

输入边界层:严格模式验证。 在数据入口处启用代理对严格验证模式:检测到孤立高代理或孤立低代理时,立即拒绝处理并记录审计日志。建议阈值:任意序列中出现超过 0 个孤立代理码元时触发告警,持续时间超过 1ms 时降级服务。配置示例:以 STRICT_SURROGATE_CHECK=true 启用验证函数,返回错误码 E_ILL_FORMED_SURROGATE

转换层:替换而非忽略。 在编码转换环节(如 UTF-16 转 UTF-8),不应静默跳过无效代理,而应替换为 Unicode 替换字符(U+FFFD)。这一策略避免了截断风险,同时保证数据流的连续性。参数建议:surrogate_policy=replace,替换字符 U+FFFD,最大连续替换数阈值 100。

序列化层:JSON 安全编码。 JSON 编码器在输出字符串前应执行代理对完整性检查:确保每个高代理后跟低代理,每个低代理前接高代理。不符合条件的高代理应转义为 \uXXXX 形式,不符合条件的低代理应替换为 U+FFFD。生产环境建议使用已修复 CVE-2022-31116 的 ujson 5.4.0+ 或等效库,并在 CI 阶段集成代理对模糊测试。

验证与监控清单

工程团队应建立以下检查机制:静态代码扫描规则检测直接操作 charCodeAt 或类似低层 API 的代码段;集成测试覆盖包含表情符号(需要代理对编码)如 U+1F600(😀)和代理对边界情况如 \uD800 单独出现的场景;监控面板追踪 E_ILL_FORMED_SURROGATE 错误率,基线建议为 0%,任何非零值均需人工审查。

代理对验证看似底层,但其缺失引发的字符串截断、JSON 乱码与数据篡改风险贯穿整个数据处理管道。在处理国际化文本或来自 Windows 平台的数据时,工程师应将代理对验证视为安全边界的重要组成部分,而非单纯的编码细节。

参考资料

  • Unicode Consortium: UTF-16 Encoding Form (Unicode 15.0)
  • CVE-2022-31116: ujson - Incorrect handling of invalid surrogate pair characters (NVD)
  • TC39 ecma262 Issue #944: JSON.stringify produces invalid UTF-16

systems

内容声明:本文无广告投放、无付费植入。

如有事实性问题,欢迎发送勘误至 i@hotdrydog.com