flawopen.com/Ruby YAML/Ruby YAML
对不可信输入使用 YAML.load 安全吗?
检查应用实际加载的 Psych 版本:Ruby 3.1 随附的 Psych 4 将 YAML.load 改为默认安全反序列化。更早的 Psych 版本会构造任意对象;Psych 也可以脱离 Ruby 单独升级。YAML.unsafe_load 和 Marshal.load 会反序列化 Ruby 对象,绝不能接收不可信输入。
# Psych < 4: YAML.load used unsafe # object construction by default. YAML.load(params[:config]) # Every Psych version: explicitly unsafe. YAML.unsafe_load(user_input) Psych.unsafe_load(user_input) # Marshal has no restricted safe-load API. Marshal.load(cookie_value)
# Explicit restricted types; keep this list minimal. require "yaml" require "date" data = YAML.safe_load( input, permitted_classes: [Date], permitted_symbols: [], aliases: false ) # For ordinary external data, prefer JSON and # validate its shape, values, and input size. data = JSON.parse(input)
YAML 为什么有风险
YAML 标签可由 Psych 映射为 Ruby 对象。不安全加载时,带标签的文档可以请求构造某个类,并为其序列化状态提供值;实际影响取决于可用类及其行为。这是反序列化 gadget 链的基础。对不可信 YAML 使用安全加载,并单独验证返回数据。Python pickle、PHP unserialize 和 Ruby Marshal.load 也有类似的一般风险。
Rails 等应用可能加载大量依赖,这会扩大不安全反序列化链可利用的类和行为范围。具体风险取决于部署情况,并非每个 Rails 应用或 YAML 文档都如此。
版本变化为何引发困惑
Psych 4 将 YAML.load 改为默认安全加载,并新增 unsafe_load 保留以前的对象构造行为。Ruby 3.1 随附 Psych 4,但应用也可选择其他 Psych gem 版本;应审查最终解析的依赖和运行时,而不只是 Ruby 版本。安全加载可能拒绝应用所需的类或 alias;应使用最小的显式允许列表迁移,而不是改用 unsafe_load。
升级时若引入 unsafe_load,只要输入可能被可信部署边界之外的来源影响,就应将该调用视为安全问题。优先使用 safe_load,并将 permitted_classes 限定为最少必需项;不要只为消除兼容性错误而放宽列表。
FAQ
YAML.safe_load 完全安全吗?
如果我加载自己的配置文件呢?
Marshal 是否有可接受的用途?
Ruby 中的另一项风险:Marshal.load
与 YAML 不同,Ruby 文档指出,Marshal.load 能重建进程中几乎任何已加载的类;处理不可信数据时可能导致远程代码执行。请将 Marshal 数据限制在可信边界内,绝不要反序列化用户提供或可能受攻击者影响的字节。
参考资料
- Ruby documentation — Psych (YAML)
- Psych 4.0 — Ruby 3.1 API documentation
- Ruby documentation — Marshal security considerations
- CVE-2024-35221 — YAML alias denial of service in RubyGems metadata
- CWE-502 — Deserialization of Untrusted Data
- Is Python's pickle safe for untrusted data?
- Is PHP's unserialize() safe on user input?
- Ruby 文档 — Marshal.load 安全警告