概要
Rubyで外部コマンドを実行するときの関数について、それぞれ実行して試しました。
自分用メモです。
動機
安全なWebアプリケーションの作り方 のOSコマンド・インジェクションの項で、
Rubyの場合どうなるか興味を持った
実行環境
Ruby2.5.1
試してみた
- Kernel.#system
systemはsystem(コマンド)のように直接コマンドを打つことと、
system(コマンド, パラメータ)のように指定することができる
子プロセスが終了ステータス 0 で終了すると成功とみなし true を返します。それ以外の終了ステータスの場合は false を返します。コマンドを実行できなかった場合は nil を返します。
引数のコマンドをサブプロセスで実行するのが特徴。
$system("echo test")
test
=> true
$system("echo test ; echo test2")
test
test2
=> true
$system("/bin/echo","test")
test
=> true
$system("/bin/echo","test; echo test2")
test; echo test2
=> true
後者の場合、パラメータ内のセミコロンが文字列として処理されている。
OSコマンドインジェクションの対策に有効。
- Kernel.#exec
systemと似ている。
プロセスの実行コードはそのコマンド(あるいは shell)になるので、起動に成功した場合、このメソッドからは戻りません。
こちらは実行中のプロセスが置き換わるので、返り値を持たない。
$exec("echo test ; echo test2")
test
test2
$exec("/bin/echo","test; echo test2")
test; echo test2
外部コマンドが実行できる(できてしまう)例
他の関数で外部コマンドを実行することはあるのだろうか?
- Kernel.#open
$open('|/bin/echo test;').read
=> "test\n"
このように、引数の中にパイプを組み込むことで外部コマンドを実行できてしまう。
File.openを使えばエラーが出る。
$File.open('|/bin/echo test;').read
Errno::ENOENT: No such file or directory @ rb_sysopen - |/bin/echo test;
まとめ
systemやexecといった関数を使うときはOSコマンドインジェクションに配慮して実装をする
→外部から入力された文字列をコマンドラインのパラメータに渡さない
→system関数に第二引数を追加する
open関数は外部コマンドを実行される危険があるので、File.openに置き換えるのが望ましい
参考ページ
Ruby リファレンスマニュアル
https://docs.ruby-lang.org/ja/latest/doc/index.html
Rails セキュリティガイド
https://railsguides.jp/security.html