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