在 Go 应用中,解析不受信任的数据会创建一个危险的攻击面,在实际中经常被利用。在安全评估期间,作者多次利用 Go 的 JSON、XML 和 YAML 解析器中的意外行为来绕过身份验证、规避授权控制以及从生产系统中泄露敏感数据。
这些不是理论问题,已导致诸如CVE-2020-16250(由 Google 的 Project Zero 发现的 Hashicorp Vault 身份验证绕过)等记录在案的漏洞,以及在客户项目中发现的许多高影响发现。
本文通过三个攻击场景来阐述这些意外的解析器行为,每个安全工程师和 Go 开发人员都应了解:
- (Un)Marshaling 意外数据:Go 解析器如何暴露开发人员原本希望是私有的数据。
- 解析器差异:当多个服务解析相同输入时,解析器之间的差异如何使攻击者绕过安全控制。
- 数据格式混淆:解析器如何处理具有令人惊讶且可利用结果的跨格式有效负载。
并通过实际示例演示每个攻击场景,最后给出更安全配置这些解析器的具体建议,包括弥补 Go 标准库中安全漏洞的策略。
下面是将检查的令人惊讶的行为总结,带有显示其安全状态的指标:
JSON | JSON v2 | XML | YAML | |
---|---|---|---|---|
json:"-,…" | YES (坏设计) | YES (坏设计) | YES (坏设计) | YES (坏设计) |
json:“omitempty” | YES (预期) | YES (预期) | YES (预期) | YES (预期) |
重复键 | YES (最后) | NO | YES (最后) | NO |
大小写不敏感 | YES | NO | NO | NO |
未知键 | YES (可缓解) | YES (可缓解) | YES | YES (可缓解) |
垃圾前导数据 | NO | NO | YES | NO |
垃圾尾随数据 | YES (使用 Decoder) | NO | YES | NO |
Go 中的解析
Go 的标准库提供 JSON 和 XML 解析器,但没有 YAML 解析器,有几个第三方替代方案。分析中关注:
- encoding/json 版本 go1.24.1
- encoding/xml 版本 go1.24.1
- yaml.v3 版本 3.0.1(最流行的第三方 Go YAML 库)
这些解析器提供两个主要函数:
Marshal
(序列化):将 Go 结构体转换为相应的格式字符串。Unmarshal
(反序列化):将格式字符串转换回 Go 结构体。
Go 使用结构体字段标签来允许自定义解析器如何处理各个字段,标签由键名和可选的逗号分隔指令组成。
攻击场景 1:(Un)Marshaling 意外数据
有时需要限制结构体的哪些字段可以被编组或解组。
- 未设置 JSON 标签时,可按字段名解组,大多数 Go 开发者知道。
- 误用“-”标签:为告诉解析器不编组或解组特定字段,必须添加“-”标签,但易出错,XML 和 YAML 解析器需前缀 XML 命名空间,且此行为易导致安全漏洞,如 Flipt 和 langchaingo 中的问题,已创建公共 Semgrep 规则帮助检测。
- 误用 omitempty:开发者可能误将字段名设置为“omitempty”,导致解析器将其作为字段名,可能影响序列化或反序列化,已创建公共 Semgrep 规则帮助检测。
攻击场景 2:解析器差异
如果用不同的 JSON 解析器解析相同的输入且结果不一致会怎样?
- 重复字段:Go 的 JSON 解析器总是取最后一个,大多数解析器取第一个,XML 解析器行为相同,YAML 解析器返回错误,这可能导致安全漏洞,如 Apache CouchDB、MacOS、Zoom 和 GitLab 中的漏洞。
- 大小写不敏感键匹配:Go 的 JSON 解析器大小写不敏感,会取最后一个匹配的键,甚至可使用 Unicode 字符,这是 Go JSON 解析器的关键缺陷,导致许多安全漏洞,且无法禁用,XML 和 YAML 解析器使用精确匹配。
攻击场景 3:数据格式混淆
如果用错误的解析器解析文件会怎样?
- 未知键:默认情况下,JSON、XML 和 YAML 解析器不阻止未知字段。
- 前导垃圾数据:仅 XML 解析器接受前导垃圾数据。
- 尾随垃圾数据:仅 XML 解析器接受任意尾随垃圾数据,JSON 解析器使用 Decoder API 时接受垃圾尾随数据。
可以构建一个多语言格式(polyglot),利用这些行为使不同解析器返回不同结果,如在 Hashicorp Vault 绕过漏洞中使用。
缓解措施
要最小化这些风险并使 JSON 解析更严格:
- 使用
DisallowUnknownFields
防止 JSON 中的未知字段,YAML 使用KnownFields(true)
,XML 虽有提案被拒绝但可类似处理。 - 创建自定义“hacky”解决方案,如
strictJSONParse
函数,但有性能差、检测不完全和采用潜力低等局限性。 - 关注 JSON v2,它将在库级别实现更安全的默认值,包括禁止重复名称、大小写敏感匹配、包含
RejectUnknownMembers
选项和UnmarshalRead
函数处理尾随垃圾数据等,但仍需正式接受和广泛采用。
开发者的关键要点
- 默认实现严格解析,使用
DisallowUnknownFields
(JSON)和KnownFields(true)
(YAML)。 - 确保跨边界的一致性,使用相同解析器或添加验证层。
- 关注 Go 的 JSON v2 库的发展。
- 利用静态分析,使用提供的 Semgrep 规则检测漏洞。
虽然提供了缓解和检测策略,但长期解决方案需要解析器库采用安全默认值,开发者必须保持警惕。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。