Ruby 安全¶ ↑
Ruby 编程语言庞大而复杂,新手和经验丰富的 Ruby 程序员都经常会遇到许多安全陷阱。
本文档旨在讨论其中的许多陷阱,并在适用的情况下提供更安全的替代方案。
请查看公开已知 CVE 的完整列表以及如何正确报告安全漏洞,网址为:www.ruby-lang.org/en/security/ 日语版本在此:www.ruby-lang.org/ja/security/
安全漏洞应通过电子邮件报告至 security@ruby-lang.org(PGP 公钥),这是一个私有邮件列表。报告的问题将在修复后发布。
Marshal.load
¶ ↑
Ruby 的 Marshal
模块提供了用于将 Ruby 对象树序列化和反序列化为二进制数据格式的方法。
切勿使用 Marshal.load
反序列化不受信任的或用户提供的数据。由于 Marshal
可以反序列化为几乎任何 Ruby 对象,并且可以完全控制实例变量,因此可以精心设计恶意负载,该负载在反序列化后不久执行代码。
如果您需要反序列化不受信任的数据,则应使用 JSON
,因为它只能返回诸如字符串、数组、哈希、数字和 nil 之类的“原始”类型。如果需要反序列化其他类,则应手动处理。切勿反序列化为用户指定的类。
YAML
¶ ↑
YAML
是一种流行的、人类可读的数据序列化格式,许多 Ruby 程序使用它来进行配置和 Ruby 对象树的数据库持久化。
与 Marshal
类似,它也能够反序列化为任意 Ruby 类。例如,以下 YAML
数据将在反序列化时使用 `unsafe_load` 方法创建一个 ERB
对象。
!ruby/object:ERB src: puts `uname`
因此,许多适用于 Marshal
的安全注意事项也适用于 YAML
。请勿使用 YAML
反序列化不受信任的数据。
符号¶ ↑
符号通常被视为简单字符串的语法糖,但它们起着更为关键的作用。MRI Ruby 实现内部使用符号来表示方法、变量和常量名称。这样做的原因是符号只是附加了名称的整数,因此它们在哈希表中查找速度更快。
从 2.2 版本开始,大多数符号都可以进行垃圾回收;这些符号称为可回收符号。您创建的大多数符号(例如,通过调用 to_sym
)都是可回收的。
另一方面,不可回收符号永远不会进行垃圾回收。它们是在修改代码时创建的
-
定义方法(例如使用
define_method
), -
设置实例变量(例如使用
instance_variable_set
), -
创建变量或常量(例如使用
const_set
)
尚未更新且仍在调用“SYM2ID”的 C 扩展将创建不可回收符号。2.2.0 中的错误:send
和 __send__
也创建了不可回收符号,并且使用关键字参数调用方法也可能创建一些符号。
不要从用户输入创建不可回收符号。否则,这将允许用户通过用唯一的字符串填充应用程序来发起拒绝服务攻击,这将导致内存无限增长,直到 Ruby 进程被杀死或导致系统速度减慢至停止。
虽然使用用户输入调用这些方法可能不是一个好主意,但以前容易受到攻击的方法,例如 to_sym
、respond_to?
、method
、instance_variable_get
、const_get
等,不再构成威胁。
正则表达式¶ ↑
与其他语言相比,Ruby 的正则表达式语法有一些细微的差异。在 Ruby 中,^
和 $
锚点不是指字符串的开头和结尾,而是指行的开头和结尾。
这意味着,如果您使用像 /^[a-z]+$/
这样的正则表达式来将字符串限制为仅包含字母,则攻击者可以通过传递一个包含字母、换行符以及他们选择的任何字符串的字符串来绕过此检查。
如果您想在 Ruby 中匹配整个字符串的开头和结尾,请使用锚点 \A
和 \z
。
eval
¶ ↑
切勿将不受信任的或用户控制的输入传递给 eval
。
除非您正在实现像 irb
或 pry
这样的 REPL,否则 eval
几乎肯定不是您想要的。请勿尝试在将用户输入传递给 eval
之前对其进行过滤 - 这种方法充满了危险,并且很可能会使您的应用程序面临严重的远程代码执行漏洞。
send
¶ ↑
Ruby 中的“全局函数”(puts
、exit
等)实际上是 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
配置适当的安全策略。