システムが必要とする処理を実行するために外部プログラムを呼び出すことはよくある。これは、コードの再利用の一形態であり、コンポーネント指向ソフトウェア開発の原始的な形態と見ることもできるだろう。信頼できない入力を無害化せず、それを使って外部プログラムを実行すると、コマンドインジェクションや引数インジェクションの脆弱性につながる。
すべての Java アプリケーションは Runtime クラスのインスタンスを1つだけ持っており、このインスタンスを通じて実行環境とのやりとりを行うことができる。このインスタンスは、Runtime.getRuntime() メソッドを呼ぶことで取得できる。Runtime.exec のセマンティクスは厳密に規定されていないため、このメソッドの動作に必要以上に依存すべきではない。しかし、Runtime.execは一般に、シェルを介さず直接コマンドを起動する。シェルを起動したい場合、POSIXでは/bin/sh -c、Windowsではcmd.exe を実行する。コマンドライン引数を1つの文字列として受け取るexec() メソッドは、受け取った文字列を StringTokenizer を使ってトークンに分解する。Windowsでは、これらのトークンは1つの文字列に組み立て直されたのちに実行される。
したがって、コマンドインタプリタが明示的に呼び出されないかぎり、コマンドインジェクション攻撃は成功しないだろう。一方、空白文字やダブルクォートなどを含む引数や、オプションを表す "-" や "/" 文字で始まる引数によって、引数インジェクション攻撃を受ける可能性はある。
このルールは「IDS00-J. SQL インジェクションを防ぐ」の個別具体例である。プログラムの信頼境界外から取得した文字列データは、コマンドとして実行される前に無害化しなくてはならない。
違反コード (Windows)
以下の違反コード例は「dir」コマンドを使ってディレクトリ内のファイル一覧を行うものである。Runtime.exec() を使って Windows の dir コマンドを起動している。
class DirList { public static void main(String[] args) throws Exception { String dir = System.getProperty("dir"); Runtime rt = Runtime.getRuntime(); Process proc = rt.exec("cmd.exe /C dir " + dir); int result = proc.waitFor(); if (result != 0) { System.out.println("process error: " + result); } InputStream in = (result == 0) ? proc.getInputStream() : proc.getErrorStream(); int c; while ((c = in.read()) != -1) { System.out.print((char) c); } } }
Runtime.exec() は実行環境から取得され、無害化されていないデータを受け取るため、このコードはコマンドインジェクション攻撃を受ける可能性がある。
攻撃者は以下のようなコマンドを指定して攻撃することができる。
java -Ddir='dummy & echo bad' Java
実際には、2つのコマンドが実行される。
cmd.exe /C dir dummy & echo bad
このコマンドは、最初に存在しないフォルダ「dummy」をリストし、次に文字列「bad」をコンソールに出力する。
違反コード (POSIX)
以下の違反コード例は上記と同じであるが、POSIX の「ls」コマンドを使っている。Runtime.exec() メソッドに渡す引数のみが違反コード(Windows)と異なる。
class DirList { public static void main(String[] args) throws Exception { String dir = System.getProperty("dir"); Runtime rt = Runtime.getRuntime(); Process proc = rt.exec(new String[] {"sh", "-c", "ls " + dir}); int result = proc.waitFor(); if (result != 0) { System.out.println("process error: " + result); } InputStream in = (result == 0) ? proc.getInputStream() : proc.getErrorStream(); int c; while ((c = in.read()) != -1) { System.out.print((char) c); } } }
攻撃者は違反コード(Windows)と同様の手法で攻撃を行うことができる。実際に実行されるコマンドは以下になる。
sh -c 'ls dummy & echo bad'
適合コード (無害化)
以下の適合コード例では、ホワイトリストに登録された少数の文字だけがRuntime.exec()メソッドに引数として渡されるよう制限することで、信頼できないユーザ入力を無害化している。
// ... if (!Pattern.matches("[0-9A-Za-z@.]+", dir)) { // エラー処理 } // ...
この適合コードの無害化方法では、ディレクトリ名を表す正当な入力をエラーにしてしまう場合がある。また、実行されるコマンドインタプリタがシステム依存であるため、このコードがJavaプログラムが実行されるあらゆる環境でコマンドインジェクションを防止できると断言することもできない。
適合コード (制限された選択を提供する)
以下の適合コードでは、信頼する文字列だけを Runtime.exec() に渡すことにより、コマンドインジェクションを防いでいる。ユーザは、Runtime.exec() の引数文字列をあらかじめ決められた選択肢の中から選ぶことはできるが、直接渡すことはできない。
// ... String dir = null; int number = Integer.parseInt(System.getProperty("dir")); // 整数のみを許す switch (number) { case 1: dir = "data1"; break; // 選択肢 1 case 2: dir = "data2"; break; // 選択肢 2 default: // 無効な値 break; } if (dir == null) { // エラー処理 }
この適合コードでは選択されるディレクトリをハードコードしている。
選択肢に含めたいディレクトリの数が多くなれば、この方法が破綻するのは明らかである。そのような場合にも対応できるようにするには、選択肢となるディレクトリのデータをプロパティファイルから java.util.Properties オブジェクトに読み込むというやり方がある。
適合コード (Runtime.exec() を使わない)
システムコマンドの実行により行われるタスクの処理を別の手段で実現できるのであれば、ほとんどの場合、別の手段を採用するほうがよい。以下の適合コードでは File.list() メソッドを使ってディレクトリのリスティングを行い、コマンドインジェクション攻撃や引数インジェクション攻撃を防いでいる。
import java.io.File; class DirList { public static void main(String[] args) throws Exception { File dir = new File(System.getProperty("dir")); if (!dir.isDirectory()) { System.out.println("Not a directory"); } else { for (String file : dir.list()) { System.out.println(file); } } } }
リスク評価
信頼できない、無害化されていない入力を Runtime.exec() メソッドに渡すと、コマンドインジェクション攻撃や引数インジェクション攻撃を受ける可能性がある。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
IDS07-J | 高 | 中 | 中 | P12 | L1 |
関連する脆弱性
[CVE-2010-0886] | Sun Java Web Start Plugin Command Line Argument Injection |
[CVE-2010-1826] | Command Injection in updateSharingD's handing of Mach RPC messages |
[T-472] | Mac OS X Java Command Injection Flaw in updateSharingD lets local users gain elevated Privileges |
関連ガイドライン
The CERT C Secure Coding Standard | ENV03-C. Sanitize the environment when invoking external programs |
ENV04-C. Do not call system() if you do not need a command processor | |
The CERT C++ Secure Coding Standard | ENV03-CPP. Sanitize the environment when invoking external programs |
ENV04-CPP. Do not call system() if you do not need a command processor | |
ISO/IEC TR 24772:2010 | "Injection [RST]" |
MITRE CWE | CWE-78. Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') |
参考文献
[Chess 2007] | Chapter 5: Handling Input, "Command Injection" |
[OWASP 2005] | |
[Permissions 2008] |
翻訳元
これは以下のページを翻訳したものです。
IDS07-J. Do not pass untrusted, unsanitized data to the Runtime.exec() method (revision 121)