Go解析器中的意外安全陷阱
在Go应用程序中,解析不可信数据会创建危险的攻击面,这些攻击面在野外经常被利用。在我们的安全评估过程中,我们反复利用Go的JSON、XML和YAML解析器中的意外行为来绕过身份验证、规避授权控制并从生产系统中窃取敏感数据。
这些不是理论问题——它们导致了有记录的漏洞,如CVE-2020-16250(Google的Project Zero发现的Hashicorp Vault身份验证绕过)以及我们在客户参与中的许多高影响发现。
本文通过三个攻击场景来阐述这些意外的解析器行为,每个安全工程师和Go开发人员都应该了解:
- (解)封送意外数据:Go解析器如何暴露开发人员意图保持私密的数据
- 解析器差异:当多个服务解析相同输入时,解析器之间的差异如何使攻击者能够绕过安全控制
- 数据格式混淆:解析器如何处理跨格式payload并产生令人惊讶且可利用的结果
我们将用真实世界的例子演示每个攻击场景,并以更安全地配置这些解析器的具体建议作为结束,包括补偿Go标准库中安全漏洞的策略。
解析器行为安全状态摘要
| 行为 | JSON | XML | YAML |
|---|---|---|---|
json:"-,... |
🔴 不安全 | 🔴 不安全 | 🔴 不安全 |
json:"omitempty" |
🟢 安全 | 🟢 安全 | 🟢 安全 |
| 重复键 | 🟠 可配置 | 🟠 可配置 | 🟢 安全 |
| 大小写不敏感 | 🔴 不安全 | 🟢 安全 | 🟢 安全 |
| 未知键 | 🟠 可配置 | 🟠 可配置 | 🟠 可配置 |
| 前导垃圾数据 | 🟢 安全 | 🔴 不安全 | 🟢 安全 |
| 尾随垃圾数据 | 🟠 可配置 | 🔴 不安全 | 🟢 安全 |
Go中的解析
encoding/json版本 go1.24.1encoding/xml版本 go1.24.1yaml.v3版本 3.0.1(最流行的第三方Go YAML库)
这些解析器提供两个核心功能:
- Marshal(序列化):将Go结构体转换为相应的格式字符串
- Unmarshal(反序列化):将格式字符串转换回Go结构体
Go使用结构体字段标签来自定义解析器如何处理各个字段:
|
|
攻击场景1:(解)封送意外数据
没有标签的字段
当字段没有JSON标签时,仍然可以使用字段名称进行解组:
|
|
这可能导致安全漏洞,如果开发人员错误地认为没有标签的字段不会被解析。
误用-标签
要告诉解析器不要(解)封送特定字段,必须添加特殊的- JSON标签:
|
|
这种误用会导致解析器查找输入JSON中的-键,从而意外设置字段。
误用omitempty
开发人员有时错误地将字段名称设置为omitempty:
|
|
攻击场景2:解析器差异
重复字段
当JSON输入有相同的键两次时,Go的JSON解析器总是取最后一个值:
|
|
这种差异可被利用来绕过授权检查。
大小写不敏感键匹配
Go的JSON解析器以大小写不敏感的方式解析字段名称:
|
|
这种行为与大多数其他语言的解析器不同,导致了多个高影响的安全漏洞。
攻击场景3:数据格式混淆
未知键
默认情况下,JSON、XML和YAML解析器不阻止未知字段。
前导垃圾数据
只有XML解析器接受前导垃圾数据。
尾随垃圾数据
XML解析器接受任意尾随垃圾数据,JSON解析器在使用Decoder API时也接受尾随垃圾数据。
构建多格式payload
我们可以构建一个能被Go的JSON、XML和YAML解析器解析的多格式payload,每个解析器返回不同的结果:
|
|
缓解措施
JSON严格解析函数
|
|
JSONv2
JSON v2提案解决了本文讨论的许多问题:
- 禁止重复名称
- 执行大小写敏感匹配
- 包含RejectUnknownMembers选项
- 包含UnmarshalRead函数来处理io.Reader数据
开发人员关键要点
- 默认实施严格解析:对JSON使用DisallowUnknownFields,对YAML使用KnownFields(true)
- 跨边界保持一致性:确保多个服务中的解析行为一致
- 关注JSON v2:关注Go的JSON v2库的开发
- 利用静态分析:使用Semgrep规则检测代码库中的漏洞模式
虽然我们提供了缓解和检测策略,但长期解决方案需要从根本上改变这些解析器的操作方式。在解析器库采用安全默认值之前,开发人员必须保持警惕。