セキュリティモデル
RubyにはCGI等のプログラミングを安全に行うことを助ける為に、セキュリティ 機構が備わっています。
Rubyのセキュリティモデルは「オブジェクトの汚染」と「セーフレベル」という 仕組みによってなりたっています。
オブジェクトの汚染
Rubyではオブジェクトは「汚染されている」とみなされることがあります。この しくみは大きく分けて二つの使われ方をします。
ひとつ目は、信用できない入力をもとに作られたオブジェクトを「汚染されてい る」とみなし、「危険な操作」の引数として使えないようにすることです。悪意 あるデータによって、プログラムが意図しない動作をする事を防ぐことを目的と しています。
もうひとつは、信用しているオブジェクト(汚染されていないオブジェクト)を 信用できないプログラムから守るという使い方です。セーフレベル4で汚染されて いないオブジェクトへの操作が大幅に制限されるのはこの事を意図しています。
オブジェクトの汚染に関連するメソッド
- Object#taint
-
オブジェクトを汚染する
- Object#tainted?
-
オブジェクトが汚染されている場合に真を返す
- Object#untaint
-
オブジェクトの汚染を取り除く
セーフレベル
各スレッドは固有の「セーフレベル」を持っています。セーフレベルが高くなるほ ど、行える操作は制限されます。セーフレベルはスレッドローカル変数 $SAFE で 設定します。
$SAFE に関するルール
- プログラム開始時の$SAFEの値は0
- 各スレッドは作られた時点での親スレッドの$SAFEの値を引き継ぐ
$SAFE = 1 th = Thread.new{ p $SAFE #=> 1 $SAFE = 3 } th.join p $SAFE #=> 1
- $SAFE の値を現在の値より小さく変更する事はできない
$ ruby -e '$SAFE = 1; $SAFE = 0' -e:1: tried to downgrade safe level from 1 to 0 (SecurityError)
原則として、各セキュリティレベルにはそれ以下のセキュリティレベルの制限も 適用されます。たとえばレベル1で許されない操作はレベル2でも行えません。
レベル 0
デフォルトのセーフレベルです。
汚染されるオブジェクト
- IOや環境変数、コマンドライン引数(ARGV)から得られた文字列
$ ruby -e 'p ARGV[0].tainted?' hoge true
環境変数PATHだけは例外で、値に危険なパスを含む場合のみ汚染されます。
ここでは危険なパスとは誰でも変更/書き込みが可能なパスをいいます。 ルートディレクトリから階層が順番にチェックされ、一箇所でも誰でも 変更可能な個所があればそのパスは危険とみなされます。
なお、DOSISH(DOSとWindows)、cygwin では権限をチェックしません。
禁止される操作
- 特になし
レベル 1
信用しているプログラムで信用できないデータを処理する為のレベルです。 CGI等でユーザからの入力を処理するのに適しています。
汚染されるオブジェクト
- レベル0と同様
禁止される操作
- 汚染された文字列を引数とした以下の操作
$ ruby -e '$SAFE = 1; open(ARGV[0])' hoge -e:1:in `initialize': Insecure operation - initialize (SecurityError) from -e:1
- ファイルテスト演算子の使用、ファイルの更新時刻比較
- 外部コマンド実行 (Kernel.#system, Kernel.#exec, Kernel.#`, Kernel.#spawn など)
- Kernel.#eval (レベル 4 の説明も参照)
- トップレベルへの Kernel.#load (第二引数を指定してラップすれば実行可能)
- Kernel.#require
- Kernel.#trap
レベル 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
- 危険なパスからの Kernel.#load
- 汚染された文字列を引数にしての Kernel.#load (ラップされていても)
- Kernel.#syscall
- Kernel.#exit!
- Kernel.#trap
レベル 3
生成される全てのオブジェクトが汚染されます。レベル4でプログラムを実行す る環境を作り上げるのに適しています。
汚染されるオブジェクト
- 生成される全てのオブジェクト
禁止される操作
レベル2の制限に加え、以下の操作が禁止されます。
レベル 4
信用することのできないプログラムを実行するためのレベルです。
レベル4は信頼できないプログラムによる危険な操作をほぼ全て検出できますが、 完全な安全性は保証されません。
このレベルではレベル3では禁止されている「汚染された文字列のeval」が許可 されています。(Kernel.#eval で実行すると危険な操作は全て禁止されているからです。)
汚染されるオブジェクト
- レベル3と同様
禁止される操作
レベル3の制限(上記のとおりevalは除く)に加え、以下の操作が禁止されます。
- Object#taint
- トップレベルの定義の変更(Kernel.#autoload, Kernel.#load, Module#include)
- 既存のメソッドの再定義
- Object クラスの定義の変更
- 汚染されていないクラスやモジュールの定義の変更およびクラス変数の変更
- 汚染されていないオブジェクトの状態の変更
- グローバル変数の変更
- 汚染されていない IO や File を使用する処理
- IO への出力
- プログラムの終了(Kernel.#exit, Kernel.#abort) (なお out of memory でも fatal にならない)
- 他のスレッドに影響が出る Thread クラスの操作および他のスレッドの Thread#[]
- ObjectSpace.#_id2ref
- ObjectSpace.#each_object
- 環境変数の変更
- Kernel.#srand
セーフレベルに関するその他の詳細
- requireは$SAFE = 0で実行される
- Level 1以上では起動時に以下の違いがある
* 環境変数 RUBYLIB を $: に加えない * カレントディレクトリを $: に加えない * 環境変数 RUBYOPT を処理しない * 以下のスイッチを使用できない -s -S -e -r -i -I -x (スクリプトがsetgid, setuidされている時も同様) * 標準入力からのプログラム読み込みを行わない (スクリプトがsetgid, setuidされている時も同様)
- setuid, setgid されたスクリプトは $SAFE = 1 以上で実行される。
- Proc はその時点でのセーフレベルを記憶する。 その Proc オブジェクトが call されると、記憶していたセーフレベルで実行される。
- 汚染された Method オブジェクトが call されるとレベル4で実行される。
- 汚染された文字列を第二引数に指定して Kernel.#trap/Kernel.#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" # 外側は影響を受けない
拡張ライブラリでの扱い
- 拡張ライブラリではオブジェクトの汚染状態を適切に伝播させる必要があります。
- グローバルな状態を変更する場合や外部とのやりとりの前にセキュリティレベルを チェックする必要があります。
@see [ruby-list:37407]