# Rubyにとってコードは単なる文字列である # Kernel#eval を使用すれば文字列をコードとして実行することが出来る array = [10, 20] element = 30 p eval("array << element") # class_eval や instance_eval ではコード文字列とブロック両方使えるが # 文字列だとコード文字列が評価されるまで構文エラーが出なかったり # コードインジェクション(後述)のような脆弱性が潜む可能性があるので # 特に理由がない限り出来るだけブロックの方がいい。 # 次のコードは if の end がないので syntax error が出る l1 = lambda { # if true # puts "true" } # eval だと文字列が実際に評価されないとエラーが出ない l2 = lambda { eval <<-EOS if true puts "true" EOS } # ここでエラー #l2.call #=> SyntaxError
[10, 20, 30]
- 参照
# Kernel#binding を使用すれば現在のスコープを(Binding)オブジェクトの形で取得することが出来る class C def m @x = 20 y = 30 binding end end b = C.new.m p b.class p eval "@x", b p eval "y", b # トップレベルのスコープが取得したい場合は Ruby の組込み定数 TOPLEVEL_BINDING を使用する。 class C2 def get_self eval "self", TOPLEVEL_BINDING end def get_b eval "b", TOPLEVEL_BINDING end end obj = C2.new p obj.get_self p obj.get_b
Binding 20 30 main #<Binding:0x1001695a8>
# eval は文字列をそのままコードとして実行することが出来る。 # 次のメソッドは Array のメソッドを使ってみるコードである。 # 入力された文字列を評価して出力している。 def explore_array(method) code = "['a', 'b', 'c'].#{method}" puts "Evaluating: #{code}" eval code end # 通常の Array のメソッドを呼ぶだけならば問題ないが、 p explore_array('find_index("b")') p explore_array('map! { |e| e.next }') # 次のような文字列を悪意のあるユーザーに入力されてしまったらどうなるだろう? # プライベート情報がだだ漏れになってしまう。 # このような脆弱性を「コードインジェクション」という p explore_array('object_id; Dir.glob("*")') # コード文字列のパースによって、このような攻撃から守ることは可能だろうか? # 基本的には「出来ない」と思っておいたほうが良い。 # 悪質なコードを書く方法は無数に存在する。 # 自分のコードにだけ eval を使えば問題はない。 # しかし、動いてるシステムで文字列が外部から来てないかを追跡するのは驚くほど難しい。 # 今回のような場合は eval を使わずに動的ディスパッチで書き直せる。 def explore_array2(method, *args) ['a', 'b', 'c'].send(method, *args) end p explore_array2('object_id') #p explore_array2('object_id; Dir.glob("*")') #=> NoMethodError
Evaluating: ['a', 'b', 'c'].find_index("b") 1 Evaluating: ['a', 'b', 'c'].map! { |e| e.next } ["b", "c", "d"] Evaluating: ['a', 'b', 'c'].object_id; Dir.glob("*") ["test.rb"] 2148222300
- 参照
# Rubyは潜在的に安全でないオブジェクト(特に外部から来たオブジェクト)に自動的に汚染の印をつける。 # 汚染オブジェクトには Webフォーム、ファイル、コマンドライン、システム変数などの文字列が含まれる。 # オブジェクトが汚染されているかどうかは Object#tainted?() メソッドで確認できる p "hoge: #{"hoge".tainted?}" print "User input: " user_input = gets() p "#{user_input}: #{user_input.tainted?}" # eval でローカル変数の定義は出来ないっぽい。既にある変数の変更は可能 # キーボード入力で x の値を変更したとする x = 0 eval user_input p "x: #{x}" # Rubyにはセーブレベルという機能がありグローバル変数 $SAFE に値を設定するとセーブレベルが変更できる。 # セーブレベルには0(デフォルト)から4まであり、高くなるほど制限が多くなる。 # デフォルトでは 0 puts "$SAFE = #{$SAFE}" $SAFE = 1 # 0より大きいセーブレベルでは汚染された文字列をevalで評価できない。 #eval user_input #=>SecurityError # 明示的に文字列の汚染を除去する場合は Object#untaint() を呼び出す。 user_input.untaint eval user_input
"hoge: false" User input: x = 5 "x = 5\n: true" "x: 5" $SAFE = 0
# セーフレベルは一度上げてしまうと戻すことが出来ない。 # 試しに以下を実行すると SecurityError が出る。 #$SAFE = 2 #$SAFE = 0 $SAFE = 1 # これだとは使いづらいので、ある箇所だけセーフレベルを変更して評価したい場合は proc や lambda を使う。 sandbox = lambda { # lamdba の定義の前の設定されたセーフレベルを引き継ぐ # lamdba の定義の後に変更したセーフレベルは反映されない。 p "sandbox before: #{$SAFE}" $SAFE = 2 p "sandbox after: #{$SAFE}" } # 既に lamdba を定義しているのでここでセーフレベルを上げても sandbox のデフォルトセーフレベルは 1 のままである。 # TODO: ここらへんを ruby のソースを引用して説明出来るようになりたい。 $SAFE = 3 p "main before: #{$SAFE}" sandbox.call p "main after: #{$SAFE}" # このようにセーフレベルが異なるeval用の特別な環境を「サンドボックス」と呼ぶ
"main before: 3" "sandbox before: 1" "sandbox after: 2" "main after: 3"
- 外部参照
最新コメント