Ruby 2.4

Ruby Security

Ruby 安全

Ruby 编程语言庞大而复杂,新手和经验丰富的 Rubyists 经常遇到许多安全缺陷。

本文档旨在讨论这些缺陷,并在适用的情况下提供更安全的替代方案。

请查看公共已知CVE的完整列表以及如何正确报告安全漏洞,网址为:www.ruby-lang.org/en/security/ 日文版本位于:www.ruby-lang.org/ja/security /

应通过电子邮件将安全漏洞报告给 security@ruby-lang.orgPGP 公钥),这是一个私人邮件列表。报告的问题将在修复后发布。

$SAFE

Ruby 提供了一种机制来限制 Ruby 代码以$SAFE变量的形式执行哪些操作。

但是,$SAFE不提供执行不可信代码的安全环境。

如果您需要执行不受信任的代码,则应使用操作系统级别的沙盒机制。在Linux上,ptrace或LXC可用于沙盒可能的恶意代码。每个主要操作系统都有其他类似的机制。

Marshal.load

Ruby的Marshal模块提供了用于序列化和反序列化二进制数据格式的 Ruby 对象树的方法。

切勿使用Marshal.load反序列化不可信或用户提供的数据。因为Marshal可以反序列化几乎所有的 Ruby 对象,并且可以完全控制实例变量,所以可以在反序列化之后立即构造一个执行代码的恶意负载。

如果您需要反序列化不可信数据,则应该使用 JSON,因为它只能返回“原始”类型,如字符串,数组,哈希,数字和零。如果你需要反序列化其他类,你应该手动处理。切勿反序列化为用户指定的类。

YAML

YAML 是一种流行的人类可读数据序列化格式,被许多 Ruby 程序用于 Ruby 对象树的配置和数据库持久性。

类似的Marshal,它可以反序列化成任意的 Ruby 类。例如,以下 YAML 数据将ERB在反序列化时创建一个对象:

!ruby/object:ERB src: puts `uname`

因此,适用于元帅的许多安全考虑事项也适用于 YAML。不要使用 YAML 来反序列化不可信数据。

符号

符号通常被视为简单字符串的语法糖,但它们扮演着非常关键的角色。MRI Ruby 实现在方法,变量和常量名称内部使用符号。其原因是符号只是名称附加的整数,所以它们在哈希表中查找速度更快。

从版本2.2开始,大多数符号可以被垃圾收集; 这些被称为平凡的符号。你创建的大多数符号(例如通过调用to_sym)都是致命的。

另一方面,不朽的符号永远不会被垃圾收集。修改代码时会创建它们:

  • 定义一个方法(例如define_method),

  • 设置一个实例变量(例如with instance_variable_set),

  • 创建一个变量或常量(例如与const_set

尚未更新且仍在呼叫的 C 扩展SYM2ID将创建不朽的符号。2.2.0中的错误:send+ __ send __ +也创建了不朽的符号,并且调用具有关键字参数的方法也可以创建一些。

不要从用户输入中创建不朽的符号。否则,这将允许用户通过使用唯一字符串填充对应用程序的拒绝服务攻击,这会导致内存无限增长,直到 Ruby 进程死亡或导致系统减速停止。

虽然它可能不是一个好主意,与用户输入,曾经是脆弱的,如方法调用这些to_symrespond_to?methodinstance_variable_getconst_get,等不再是一个威胁。

常用表达

与其他语言相比,Ruby 的正则表达式语法有一些细微差别。在R uby中,^$锚不是指字符串,而开始和结束的开始和结束

这意味着如果您使用正则表达式/^[a-z]+$/来将字符串限制为仅字母,攻击者可以通过传递包含字母,换行符和任意字符串的字符串来绕过此检查。

如果你想匹配 Ruby 中整个字符串的开头和结尾,使用锚\A\z

eval

切勿将不可信或用户控制的输入传递给eval

除非你实现像 REPL irb或者pryeval是几乎可以肯定不是你想要的。不要试图在传递给用户输入之前过滤用户输入eval- 这种方法充满了危险,并且很可能会将您的应用程序打开到严重的远程代码执行漏洞。

send

Ruby 中的“全局函数”(putsexit等)实际上是私有的实例方法Object。这意味着send即使调用send具有明确的接收者,也可以调用这些方法。

例如,以下代码片段将 “Hello world” 写入终端:

1.send(:puts, "Hello world")

您不应该send使用用户提供的输入作为第一个参数。这样做可能会导致拒绝服务漏洞:

foo.send(params[:bar]) # params[:bar] is "exit!"

如果攻击者可以控制前两个参数send,则可以执行远程代码:

# params is { :a => "eval", :b => "...ruby code to be executed..." } foo.send(params[:a], params[:b])

根据用户输入调度方法调用时,请仔细验证方法名称。如果可能的话,请根据安全方法名称的白名单进行检查。

请注意,使用public_send也是危险的,因为send它本身是公开的:

1.public_send("send", "eval", "...ruby code to be executed...")

DRb

由于 DRb 允许远程客户端调用任意方法,因此不适合暴露给不受信任的客户端。

使用 DRb 时,尽量避免将其暴露在网络上。如果这不可行并且您需要将 DRb 公开给世界,则必须使用相应的安全策略进行配置DRb::ACL