Ruby 安全¶ ↑
Ruby 编程语言庞大而复杂,并且有很多安全陷阱,新手和经验丰富的 Rubyist 经常会遇到这些陷阱。
本文档旨在讨论其中许多陷阱,并在适用情况下提供更安全的替代方案。
请查看公开已知的 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 版本开始,大多数符号都可以进行垃圾回收;这些符号称为mortal 符号。您创建的大多数符号(例如通过调用 to_sym
)都是 mortal 符号。
另一方面,immortal 符号永远不会被垃圾回收。它们是在修改代码时创建的
-
定义方法(例如使用
define_method
), -
设置实例变量(例如使用
instance_variable_set
), -
创建变量或常量(例如使用
const_set
)
尚未更新且仍在调用“SYM2ID”的 C 扩展将创建 immortal 符号。2.2.0 中的错误:send
和 __send__
也创建了 immortal 符号,并且使用关键字参数调用方法也可能创建一些 immortal 符号。
不要从用户输入中创建不朽的符号。否则,这将允许用户通过用唯一字符串淹没您的应用程序来对您的应用程序发起拒绝服务攻击,这将导致内存无限增长,直到 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
配置适当的安全策略。