Ruby 安全¶ ↑
Ruby 编程语言庞大而复杂,许多安全陷阱经常被新手和经验丰富的 Ruby 开发者遇到。
本文档旨在讨论许多这些陷阱,并在适用情况下提供更安全的替代方案。
请查看公开已知的 CVE 的完整列表以及如何正确报告安全漏洞,网址为:www.ruby-lang.org/en/security/ 日语版本在此:www.ruby-lang.org/ja/security/
安全漏洞应通过电子邮件报告给 [email protected] (PGP 公钥),这是一个私人邮件列表。报告的问题将在修复后发布。
Marshal.load
¶ ↑
Ruby 的 Marshal
模块提供用于将 Ruby 对象树序列化和反序列化为二进制数据格式的方法。
切勿使用Marshal.load
反序列化不可信或用户提供的數據。因为Marshal
可以反序列化为几乎任何Ruby对象,并完全控制实例变量,因此可以制作在反序列化后立即执行代码的恶意有效负载。
如果您需要反序列化不可信数据,则应使用JSON,因为它只能返回“原始”类型,例如字符串、数组、哈希、数字和nil。如果您需要反序列化其他类,则应手动处理。切勿反序列化到用户指定的类。
YAML¶ ↑
YAML是一种流行的人类可读数据序列化格式,许多Ruby程序使用它来配置和持久化Ruby对象树。
与Marshal
类似,它能够反序列化为任意Ruby类。例如,以下YAML数据将在反序列化时创建一个ERB
对象
!ruby/object:ERB src: puts `uname`
因此,适用于Marshal
的许多安全注意事项也适用于YAML。不要使用YAML反序列化不可信数据。
符号¶ ↑
符号通常被视为简单字符串的语法糖,但它们起着更为重要的作用。MRI Ruby实现内部使用符号来表示方法、变量和常量名称。这样做的原因是符号只是带有名称的整数,因此它们在哈希表中查找起来更快。
从版本2.2开始,大多数符号可以被垃圾回收;这些被称为可回收符号。您创建的大多数符号(例如通过调用to_sym
)都是可回收的。
另一方面,不可回收符号永远不会被垃圾回收。它们是在修改代码时创建的
-
定义方法(例如使用
define_method
), -
设置实例变量(例如使用
instance_variable_set
), -
创建变量或常量(例如使用
const_set
)
尚未更新的C扩展仍然调用“SYM2ID”将创建不可回收符号。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
配置适当的安全策略。