はじめに
日本では東京オリンピックに向けてサイバーセキュリティ強化の機運が高まっていますが、ソフトウェアの脆弱性を利用した攻撃は増加し続けています。 最近では、Apache Struts2やWordPressの脆弱性を利用した攻撃により、Webサイトの改ざんや情報漏えいなどが発生し、社会的に大きな影響を及ぼしています。
ソフトウェアの脆弱性は、(例えばバッファオーバーフローのように)バグとして見つかることもありますが、通常のユースケースでは問題にならない実装になっている場合もあり、コードレビューやテストをすり抜けてしまうことがあります。 脆弱性を発見するためには、どのような攻撃手法が存在するかを知る必要があり、発見にはそれなりの知見が必要となりますが、いくつかのチェックポイントがありますので、ここではそれを紹介してみようと思います。
ただ、あまり多くの脆弱性について言及すると、話題が発散してしまいますので、ここでは、OSコマンドインジェクションを防ぐためにコードレビューでチェックすべき点に絞って紹介します。
- Pocke様より、本記事においてミスリーディングを誘う可能性がある箇所を以下のBlogにてご指摘いただきましたので、チェックポイント(3. 使用しているOSのコマンドを実行する関数がシェルを用いてコマンドを実行する関数なのかどうかをチェック)を1つ追加させていただきました。Pocke様、ご指摘ありがとうございます。 http://pocke.hatenablog.com/entry/2017/05/01/183053
概要
OSコマンドインジェクションとは、あるプログラムからOSのコマンドを実行する際に、ソフトウェアの作成者が意図しないコマンドをプログラムの利用者が実行できてしまう脆弱性のことです。これを利用すると例えば、作成者がls
(Windowsの場合はdir
)コマンドを呼び出してカレントディレクトリのファイルリストを取得することを想定した処理で、ls
ではなく、rm -rf *
のような別のコマンドを実行できてしまいます。
この脆弱性は利用されると、OSで使用できる任意のコマンドをプログラム利用者が実行できてしまいますので、リモートからアクセス可能なプログラムにOSコマンドインジェクションの脆弱性が存在すると、不特定多数の利用者から、任意のコマンドを実行されてしまう状態に陥ることになります。
上記の通り、利用されると非常に危険な脆弱性ですが、幸いにしてコードレビューで注意すべき点はそれほど多くありません。 OSコマンドインジェクションの脆弱性を防ぐためにコードレビューでチェックすべきポイントは以下の通りです。
具体的なチェックポイント
-
OSのコマンドを実行する関数を使用しているかどうかをチェックします。
-
OSのコマンドを実行する関数に与える引数を、プログラム外部からの入力を使って構成しているかどうかをチェックします。
-
使用しているOSのコマンドを実行する関数がシェルを用いてコマンドを実行する関数なのかどうかをチェックします。
-
OSのコマンドを実行する関数に与える引数が適切にエスケープされているかどうかをチェックします。
1.OSのコマンドを実行する関数を使用しているかどうかをチェック
OSのコマンドを実行する関数が使用されていなければ、OSコマンドインジェクションは引き起こせないため、これらの関数を使用しているか どうかが最初のチェックポイントとなります。 一例をあげると、PHPでは「system()」や「exec()」等、Perlでは、「eval()」や「open()」、「system()」等が該当します。 ここで挙げた例以外にも、OSコマンドインジェクションを引き起こす可能性のある関数がありますが、具体例の紹介は、別記事にて取り扱う予定です。
-
使用していない場合 -> OSコマンドインジェクションの危険性はありませんので、以下のチェックは必要ありません。
-
使用している場合 -> チェックポイント2へ進んでください。
2.OSのコマンドを実行する関数に与える引数を、プログラム外部からの入力を使って構成しているかどうかをチェック
OSのコマンドを実行する関数は、実行するコマンドを引数として渡します。この引数を組み立てる際にプログラム外部からの入力を使っている場合は、OSコマンドインジェクションが発生する可能性が高まります。
-
OSのコマンドを実行する関数に与える引数をプログラム外部からの入力を使わないで構成している場合 -> 以下のチェックは必要ありません。ただし、OSのコマンドの中には、
xargs
やfind
等の任意のコマンドを引数に指定出来るコマンドが存在するので、そのようなコマンドをOSのコマンドを実行する関数の引数にしている場合は修正が必要です。 -
OSのコマンドを実行する関数に与える引数がプログラム外部からの入力を使って構成している場合 -> チェックポイント3へ進んでください。
3.使用しているOSのコマンドを実行する関数がシェルを用いてコマンドを実行する関数なのかどうかをチェック
あるプログラムからOSのコマンドを実行する関数には、シェルを用いてOSのコマンドを実行する関数と、シェルを用いないで OSのコマンドを実行する関数があります。 OSコマンドインジェクションは、シェルが解釈可能な特殊文字等を利用し、作成者が意図しないコマンドも実行させるものですので、 問題となるのは、シェルを用いてOSのコマンドを実行する関数となります。
-
使用している関数がシェルを用いないでOSのコマンドを実行している場合 -> OSコマンドインジェクションの危険性はないので、問題ありません。
-
ただし、シェルを用いないでOSのコマンドを実行する関数の中には引数の指定方法によって、シェルを用いる、用いないを切り替える関数がありますので、このチェックポイントでは、各関数のドキュメントをよく読むことをおすすめします。主要な言語での具体例については、改めて別記事にて紹介させていただきます。
-
また、以下のようにOSのコマンドを実行する関数に与える引数に直接外部からの入力を使っている場合は、外部から自由にOSのコマンドが実行できる状態ですので、シェルを用いる関数か用いない関数かにかかわらず外部からの入力を直接引数に渡さないように修正が必要です。
-
import sys
import subprocess
args = sys.argv
subprocess.call(args[1])
-
使用している関数がシェルを用いてOSのコマンドを実行している場合 -> シェルを用いないでOSのコマンドを実行する関数を使用して下さい。
-
どうしてもシェルを用いないでOSのコマンドを実行する関数が使用できない場合、あるいは、使用している言語にそのような関数が用意されていない場合はチェックポイント4へ進んで下さい。
-
ただし、チェクポイント4の方法は確実に安全とは言い切れないため、可能な限りチェックポイント3の段階で対応して下さい。
-
4.OSのコマンドを実行する関数に与える引数が適切にエスケープされているかどうかをチェック
前述の通りOSコマンドインジェクションは、シェルが解釈可能な特殊文字等を利用し、作成者が意図しないコマンドも実行させるものです。 主要な言語では、OSコマンドインジェクションを防ぐためにシェルの特殊文字をエスケープする方法が存在します。 例えば、PHPでは「escapeshellarg()」という関数が文字列のエスケープのために用意されています。具体的なエスケープ方法に ついても、後日、別記事にて扱う予定です。 それらの方法を使用して、外部からの入力をエスケープしてから、OSのコマンドを実行する関数を呼び出しているかチェックします。
-
外部からの入力をエスケープしてから、OSのコマンドを実行する関数を呼び出している
-
可能であればOSのコマンドを実行する関数の使用をやめるか、OSのコマンドを実行する関数に渡す引数を 外部からの入力によって変更させないように修正するか、シェルを用いない関数に変更したほうが安全です。
-
シェルの文字列解釈の仕様は複雑なため、各言語で用意しているエスケープの方法にもバグが存在することがあり、 正しくエスケープされない可能性があります。
-
-
外部からの入力をエスケープせずに、OSのコマンドを実行する関数を呼び出している
- OSコマンドインジェクションが実行可能ですので、ここまで紹介した方法で修正する必要があります。
終わりに
大まかに4つのチェックポイントを紹介しました。あまり難易度の高いチェック方法はなかったのではないかと思います。
本記事では、紹介できませんでしたが、OSコマンドインジェクションを引き起こす可能性のある具体的な関数や各言語で用意されている エスケープの具体的な方法については、また後日、改めて別記事として記載する予定です。