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 数据将在使用“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
配置适当的安全策略。