OSコマンドのエスケープ – シェルの仕様とコマンドの実装


OSコマンドのエスケープの続きです。OSコマンドインジェクションを防ぐための、OSコマンドのエスケープはSQLのエスケープに比べるとかなり難しいです。

難しくなる理由は多くの不定となる条件に依存する事にあります。

  • OSコマンドを実行するシェルはシステムによって異なる
  • シェルはプログラミング言語+複雑なエスケープ仕様を持っている
  • WebアプリはCGIインターフェースで動作するため環境変数にインジェクションできる
  • コマンドパラメーターの取り扱いはコマンド次第である
  • 実行されるコマンドの実装により、間接インジェクションが可能になる

SQLの場合、出力先のシステムは一定です。PostgreSQL用にエスケープした文字列をMySQLで実行したり、MySQL用にエスケープした文字列をPostgreSQLで実行することはまずありません。DB抽象化レイヤーを使用し同じユーザーコードでエスケープした場合でも、それぞれのデータベース用に適切な出力となるようDB抽象化レイヤーが適切な処理となるように処理してくれます。

PHPのescapeshellarg/escapeshellcmd関数は、それら自体がDB抽象化レイヤーの様に実装されています。入力文字列は環境によってUNIX系のシェル用とWindowsのcommand.exe/cmd.exe用で別の処理となるように実装されています。PHPがどのように処理するのかは、コンパイル時に決まります。(configureスクリプトが自動的にシステムを識別)

 

UNIX系OSシェルの仕様

UNIX系OSのシェルには多くの実装があります。大きく別けてBourne Shell系とC Shell系があります。Bourne Shell系にはsh, bash, ksh, zshなどがあります。C Shell系にはcsh, tcshがあります。全て詳しく解説すると膨大な量になるので概要のみ解説します。

UNIX系のシェルはsh(Bourne Shell)の仕様を踏襲しています。文字列のエスケープ処理はほぼ同じです。シングルクォートで囲まれた文字列は変数展開や特殊文字の解釈は行われません。シングルクォートのエスケープ処理は定義されていませんが、文字列の中でエスケープはできるようになっています。

例えば、クオートなしで\’を出力した場合、’のみを出力します。

escapeshellarg関数はシェルが\’を’と出力する事を利用して、シングルクォートを無効化しています。例えば、escapeshellarg関数は Lets’ PHP を ‘Lets’\” PHP’ に変換します。
(参考:このエスケープ方法はXPath 1.0のエスケープ方法と基本的な考え方が同じです)

シェルはコマンド実行を行う為に特化しています。シェルはシェル自身の特殊文字となる文字を除き、コマンドとなる(上記の場合はecho)文字列以外は文字として扱い、コマンドのパラメーターとして渡します。

C言語でプログラムを作った事がある方なら、main関数は

というプロトタイプを持っている事を知っていると思います。argcは引数の数、argvが実際の引数となる文字列の配列です。引数は空白文字で区切られて渡されるか、シングルクォートまたはダブルクオートで囲まれた文字列が渡されます。

シェルはインタラクティブにコマンド実行を行い易くするため、コマンドライン中でシェルが持つ特殊文字をエスケープで解除できるようになっています。つまり、手動でコマンドを実行する場合は文字列をクオートで囲まなくても\でエスケープすれば特殊文字の意味を解除できます。手元のtcshマニュアルではクオートでなく\によるエスケープを奨めているくらいです。

Quoting complex strings, particularly strings which themselves contain quoting characters, can be confusing. Remember that quotes need not be used as they are in human writing! It may be easier to quote not an entire string, but only those parts of the string which need quoting, using different types of quoting to do so if appropriate.

引数の区切り文字となるスペースの意味を無くすには

などとします。

シェルはコマンドを区切る区切り文字となる文字が定義されています。コマンドを区切る文字を入力させない事が、コマンド実行を防止する為に必須となります。コマンドを分離する文字がまとめて記載されているtcshを例に解説します。tcshのマニュアルには次のように記載されています。

The shell splits input lines into words at blanks and tabs. The special characters &', |’, ;',
<’, >', (‘, and )' and the doubled characters &&’, ||', <<’ and >>' are always separate
words, whether or not they are surrounded by whitespace.

以下の文字は前後のスペースの有無に限らず、常に単語を分離すると記述されています。

&’  |'  ;’  <'  >’  ('  )’ &&' ||’  <<'  >>’

つまり、これらの文字が現れた場合、別のコマンドを実行します。しかし、これらの文字をエスケープするだけで十分かと言うとそうではありません。

例えば、シェルは変数展開をサポートしているので、変数展開を利用したインジェクションが可能です。シェルは$variable が現れると$variableの内容を展開してからコマンドに渡します。WebアプリはCGIインターフェースを利用しているので、ユーザーは環境変数を設定可能です。つまり、変数展開も無効化しなければコマンド実行が可能になる場合があります。

安全にコマンドを実行するには、コマンドラインを構成する文字列のみでなく、変数展開も考慮したエスケープ処理が必要です。PHPのescapeshellcmd関数はtcshマニュアルに記載された文字以外もエスケープ処理します。

Following characters are preceded by a backslash: #&;`|*?~<>^()[]{}$\, \x0A and \xFF. ’ and ” are escaped only if they are not paired. In Windows, all these characters plus % are replaced by a space instead.

$をエスケープする事により変数展開を無効化したり、ホームディレクトリに展開するチルダ(~)を無効化しています。セキュリティ対策的には、コマンド実行を防止するだけでなく意図しないファイルへのアクセスも禁止しなければなりません。このためチルダ展開も無効化しています。これでも、escapeshellcmd関数の動作はセキュリティ処理としては十分ではないので

Warning

escapeshellcmd() should be used on the whole command string, and it still allows the attacker to pass arbitrary number of arguments. For escaping a single argument escapeshellarg() should be used instead.

と警告文も記載されいます。

escapeshellcmd関数にはクオートされた文字列の処理にも問題がありますが、任意のオプションを指定できてしまう問題もセキュリティ上の問題となりえます。指定されては困るようなコマンドオプションがある場合、ユーザー入力をバリデーションする事無く利用すると任意のオプション設定が可能です。

Windowsのcommand.exe/cmd.exeの場合の解説は省略しますが、仕様に則ったエスケープ処理を行わないとコマンドインジェクションが可能となる事は理解できたと思います。

 

安全なコマンド実行文を作成してもコマンドインジェクションが可能になる例

最初に呼び出すコマンドを安全に実行しても、呼び出されたコマンドが更に別のコマンドを呼び出し、その実装が不十分であった場合はコマンドインジェクションが可能になります。

次の例はPHPのshell_exec関数からはコマンドインジェクションを防いでいますが、呼び出したコマンドの実装の不備によりインジェクションが可能になる例です。

PHPのコード

(ここでは任意ディレクトリが参照できる脆弱性を考慮しない)

list_dir.sh

$_GET['dir']に

と設定された場合、shell_exec関数からコマンドインジェクションは行われませんが、list_dir.shスクリプトにコマンドインジェクションが可能です。catコマンドが実行され、/etc/passwdの内容が送信されてしまいます。

list_dir.shが文字列として生成した$commandをevalで実行しているので、インジェクションが可能になります。

少なくとも手元のbashでは、以下のように引数パラメーターを直接コマンドに渡した場合、パラメーターはパラメーターとして渡されます。

コマンドとパラメーターの分離ができているので、インジェクションは行えません。

 

もう一つ例を挙げます。PHPはOpenSSHをサポートしています。Windows系OSからUNIX系OSに接続し、コマンドを実行することも可能です。この場合、Windows用にビルドされたPHPでescapeshellarg関数を利用し、UNIX系OSホスト上で実行すると脆弱になります。

Windows用PHPビルドのescapeshellarg関数はパラメーターを ’ シングルクォートでは無く ” ダブルクオートで囲みます。ここまでの説明で何故脆弱になるかは理解できると思います。

 

まとめ

UNIX系OSの場合はクオート無しのシェルエスケープは諦めて、引数のみをシングルクォート+エスケープで可変にした方が安全だと思います。シェル特殊文字のエスケープ処理が複雑過ぎて上級者でも正しくエスケープする事は非常に難しいです。多くのシェル実装が無いWindowsでも同様に、引数のみダブルクオート+危険な文字削除にする方が安全だと思います。

OSコマンドインジェクションは出力先のコマンド実装によって、安全に出力していても出力先のコマンドでインジェクションが可能になる場合があります。OSコマンドを出力する場合、出力先プログラムの安全性も確保しないとなりません。シェルスクリプトを呼び出す場合には特に注意が必要です。PHPでも同じですが、シェルスクリプトのevalの利用には細心の注意が必要です。

WindowsからUNIX系OSのシェルなど、エスケープ方式が異なる出力先に出力すると脆弱になります。サーバー環境でデフォルトシェルを変える事はほぼ無いとは思いますが、安易に一般的に利用されているシェルから特殊なシェルに変えると脆弱になる可能性があります。あまり多くないとは思いますが、同じOSコマンドとは言ってもエスケープ処理が異なるシステム間では安全性が保てない事にも注意が必要です。

WindowsネイティブでないCygwinなどでは困った事になるのでは?と思いましたが環境がないので調べていません。知っている方、教えて下さい!

 

追記

Rubyのシェルエスケープがどうなっているのか調べてみました。検索して最初に出てきたShellwordsモジュールではこうなっているようです。

PerlのShellwordsのポート(?)のようで、POSIX / SUSv3 (IEEE Std 1003.1-2001) 準拠ということらしいです。これだけだと判りませんが、信用して使うと危なそうな感じにも見えます。大丈夫なのかな?UNIX(POSIX)用、ということでWindowsの場合は手動(?)でエスケープ処理が必要なのかも知れません。Rubyistの方、コメント頂けると助かります。

他のRubyのシェルエスケープメソッドは、以下のまとめによるとこういう事のようです。
http://whynotwiki.com/Comparison_of_Escape_class_and_String.shell_escape

 

 


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です


+ 2 = 五

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">