Go 的解析器中存在意外的安全隐患 (注:“footguns”直译为“踩脚的枪”,在这里意译为“隐患”“陷阱”等意思,根据上下文灵活翻译)

在 Go 应用中,解析不受信任的数据会创建一个危险的攻击面,在实际中经常被利用。在安全评估期间,作者多次利用 Go 的 JSON、XML 和 YAML 解析器中的意外行为来绕过身份验证、规避授权控制以及从生产系统中泄露敏感数据。

这些不是理论问题,已导致诸如CVE-2020-16250(由 Google 的 Project Zero 发现的 Hashicorp Vault 身份验证绕过)等记录在案的漏洞,以及在客户项目中发现的许多高影响发现。

本文通过三个攻击场景来阐述这些意外的解析器行为,每个安全工程师和 Go 开发人员都应了解:

  1. (Un)Marshaling 意外数据:Go 解析器如何暴露开发人员原本希望是私有的数据。
  2. 解析器差异:当多个服务解析相同输入时,解析器之间的差异如何使攻击者绕过安全控制。
  3. 数据格式混淆:解析器如何处理具有令人惊讶且可利用结果的跨格式有效负载。

并通过实际示例演示每个攻击场景,最后给出更安全配置这些解析器的具体建议,包括弥补 Go 标准库中安全漏洞的策略。

下面是将检查的令人惊讶的行为总结,带有显示其安全状态的指标:

JSONJSON v2XMLYAML
json:"-,…"YES (坏设计)YES (坏设计)YES (坏设计)YES (坏设计)
json:“omitempty”YES (预期)YES (预期)YES (预期)YES (预期)
重复键YES (最后)NOYES (最后)NO
大小写不敏感YESNONONO
未知键YES (可缓解)YES (可缓解)YESYES (可缓解)
垃圾前导数据NONOYESNO
垃圾尾随数据YES (使用 Decoder)NOYESNO

Go 中的解析

Go 的标准库提供 JSON 和 XML 解析器,但没有 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函数处理尾随垃圾数据等,但仍需正式接受和广泛采用。

开发者的关键要点

  1. 默认实现严格解析,使用DisallowUnknownFields(JSON)和KnownFields(true)(YAML)。
  2. 确保跨边界的一致性,使用相同解析器或添加验证层。
  3. 关注 Go 的 JSON v2 库的发展。
  4. 利用静态分析,使用提供的 Semgrep 规则检测漏洞。

虽然提供了缓解和检测策略,但长期解决方案需要解析器库采用安全默认值,开发者必须保持警惕。

阅读 11
0 条评论