安全模型
为了安全地运行CGI等程序,Ruby设置了安全结构。
Ruby的安全模型由“对象的污染”和“安全级别”构成。
对象的污染
Ruby有时会认为对象“遭到了污染”,这主要有两种用途。
第一,以不安全的输入为基础制成的对象就是“受污染”的对象,不能用作“危险操作”的参数。这主要是为了防止恶意数据导致程序作出一些意外的危险动作。
第二,可以使安全对象(未遭污染的对象)得到保护,免遭不安全对象的威胁。若安全级别为4,则对未受污染的对象进行操作时就会受到很多限制,这正体现了对于安全方面的考虑。
与对象的污染有关的方法
- Object#taint
污染对象
- Object#tainted?
若对象受到了污染就返回真
- Object#untaint
消除对象受到的污染
安全级别
每个线程都有特有的“安全级别”。安全级别越高,操作受到的限制也就越多。线程局部变量$SAFE标明了安全级别。
[ruby-list:37415]
$SAFE的相关规则
- 程序开始时$SAFE的值为0
- 各线程在生成时继承父线程的$SAFE值
- 不能降低现有的$SAFE值
从原则上讲,低安全等级时的限制也适用于高安全等级。例如,若某操作在1级就被禁止的话,在2级就更不可能通过了。
0级
默认的安全级别。
被污染对象
可从IO、环境变量或命令行参数(ARGV)中获得的字符串
(只有环境变量PATH例外)
环境变量PATH比较特殊,只有当其值中含有危险路径时才会受到污染。
这时所说的危险路径是指,谁都可以变更或写入的路径。从根目录起层层检查,若包含谁都可以更改的地方的话,该路径就是危险的。
禁止的操作
- 没有
1级
特指以安全程序处理不安全数据的情况。适合于用CGI等处理用户的输入。
被污染对象
- 与0级相同
禁止的操作
- 下列以受污染字符串为参数的操作
- Dir, IO, File、FileTest的类方法、方法
- 使用FileTest操作符、比较文件的更新时间
- 执行外部命令(system, exec, ``)
- eval (参考4级的说明)
- 加载顶层(若使用第二参数进行wrap则可以执行)
- require
- trap
- 执行外部命令(只有当环境变量PATH中包含危险路径时)
2级
被污染对象
- 与1级相同
禁止的操作
在1级限制的基础上,以下操作也被禁止。
- Dir.chdir Dir.chroot Dir.mkdir Dir.rmdir
- File.chown File.chmod File.umask File.truncate File#lstat File#chmod File#chown File#delete File#unlink File#truncate File#flock 以及FileTest模块的方法
- IO#ioctl, IO#fcntl
- Process.fork Process.setpgid Process.setsid Process.setpriority Process.egid= Process.kill
- 使用危险路径load
- 以被污染字符串为参数的load(即使被wrap也不行)
- syscall
- exit!
- trap
3级
所有生成的对象都被污染。适于为在4级状态下运行程序提供环境。
被污染对象
- 所有生成的对象
禁止的操作
在2级限制的基础上,以下操作也被禁止。
- Object#untaint
4级
执行不安全程序时等级。
此时,3级时禁止的“受污染字符串的eval”却被解禁。(这是因为用eval时,所有的危险操作都已经被禁止了。)
被污染对象
- 与3级相同。
禁止的操作
在3级限制(如上所述,不包括eval)的基础上,以下操作也被禁止。
- Object#taint
- 改变顶层的定义(autoload, load, include)
- 对既存方法的再定义
- 改变Object类的定义
- 改变未被污染的类和模块的定义或改变类变量
- 改变未被污染的对象的状态
- 改变未被污染的全局变量
- 使用未被污染的IO及File的处理
- 输出到IO
- 程序的终结(exit, abort)(且out of memory也不fatal)
- 对其他线程造成影响的Thread类的操作以及其他线程的Thread#[]
- ObjectSpace._id2ref
- ObjectSpace.each_objectruby 1.7 feature
- 改变环境变量
- srand
其他的安全级别相关信息
- 当$SAFE = 0时才执行require
- 若超过Level1的话,启动时会有下列不同
- 不把环境变量RUBYLIB添加到$:之中
- 不把当前目录添加到$:之中
- 不处理环境变量RUBYOPT
- 不能使用下列开关 -s -S -e -r -i -I -x (就算脚本被setgid, setuid也是如此)
- 不会从标准输入读入程序 (就算脚本被setgid, setuid也一样)
- 被setuid, setgid的脚本将在超过$SAFE = 1的状态下运行。
- 在3级以上的环境中生成的Proc将会记下该时刻的安全级别。若受污染的Proc对象被call的话,它将以记忆的安全级别来运行。
- 若受污染的Method对象被call的话,将以4级状态运行。
- 若将受污染的字符串指定为trap/trace_var的第二参数时,将以4级状态运行ruby 1.7 feature:在 version 1.7中,若将受污染的字符串指定为第二参数而运行trap/trace_var的话,马上就会引发异常SecurityError。
- 超过4级的话,即使out of memory也不会fatal。
- 根据您安装情况的不同,Fixnum Symbol true false nil可能不会被污染。但请注意Bignum Float可能会受到污染。
实例
$SAFE级别一旦升高就不能调低了。如下所示,可以使用线程将程序的一部分置入高安全级别状态下运行。
例:
def safe(level) result = nil Thread.start { $SAFE = level result = yield }.join result end safe(4) { puts "hello" } # 因为是$SAFE所以例外 puts "world" # 外部不受影响
扩展库中的应对
- 在扩展库中,有必要对对象的污染状态进行适当的传播。
- 改变全局状态或与外部联系之前,有必要检查安全级别。
[ruby-list:37407]