IDS07-J. Runtime.exec()メソッドに渡す信頼できないデータは無害化する
システムが必要とする処理を実行するために外部プログラムを呼び出すことはよくある。これは、コードの再利用の一形態であり、コンポーネント指向ソフトウェア開発の原始的な形態と見ることもできるだろう。信頼できない入力を無害化せず、それを使って外部プログラムを実行すると、コマンドインジェクションや引数インジェクションの脆弱性につながる。
すべてのJavaアプリケーションはRuntime
クラスのインスタンスを1つだけ持っており、このインスタンスを通じて実行環境とのやりとりを行うことができる。このインスタンスは、Runtime.getRuntime()
メソッドを呼ぶことで取得できる。Runtime.exec
のセマンティクスは厳密に規定されていないため、このメソッドの動作に必要以上に依存すべきではない。しかし、Runtime.exec
は一般に、シェルを介さず直接コマンドを起動する。シェルを起動したい場合、POSIXでは/bin/sh -c
、Windowsではcmd.exe
を実行する。コマンドライン引数を1つの文字列として受け取るexec()
メソッドは、受け取った文字列をStringTokenizer
を使ってトークンに分解する。Windowsでは、これらのトークンは1つの文字列に組み立て直されたのちに実行される。
したがって、コマンドインタプリタが明示的に呼び出されないかぎり、コマンドインジェクション攻撃は成功しないだろう。一方、空白文字やダブルクォートなどを含む引数や、オプションを表す"-
"や"/
"文字で始まる引数によって、引数インジェクション攻撃を受ける可能性はある。
プログラムの信頼境界外から取得した文字列データは、コマンドとして実行される前に無害化しなくてはならない。
違反コード (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)) {
// Handle error
}
// ...
この適合コードの無害化方法では、ディレクトリ名を表す正当な入力をエラーにしてしまう場合がある。また、実行されるコマンドインタプリタがシステム依存であるため、このコードが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 |
自動検出
ツール | バージョン | チェッカー | 説明 |
---|---|---|---|
The Checker Framework |
2.1.3 |
Tainting Checker | Trust and security errors (see Chapter 8) |
CodeSonar |
8.1p0
|
JAVA.IO.INJ.COMMAND |
Command Injection (Java) |
Coverity | 7.5 | OS_CMD_INJECTION | Implemented |
Parasoft Jtest |
2024.1
|
CERT.IDS07.EXEC | Do not use 'Runtime.exec()' |
SonarQube |
9.9
|
OS commands should not be vulnerable to injection attacks |
関連する脆弱性
CVE-2010-0886 |
|
CVE-2010-1826 |
Command injection in |
T-472 |
Mac OS X Java Command Injection Flaw in |
関連ガイドライン
ENV03-C. Sanitize the environment when invoking external programs |
|
SEI CERT C++ Coding Standard |
ENV03-CPP. Sanitize the environment when invoking external programs |
SEI CERT Perl Coding Standard | IDS34-PL. Do not pass untrusted, unsanitized data to a command interpreter |
ISO/IEC TR 24772:2013 |
Injection [RST] |
CWE-78, Improper Neutralization of Special Elements Used in an OS Command ("OS Command Injection") |
実装の詳細 (Android)
Runtime.exec()
は、OSのコマンドを実行するためにAndroidアプリからも呼び出せる。
参考文献
[Chess 2007] |
Chapter 5, "Handling Input," section "Command Injection" |
[OWASP 2005] | A Guide to Building Secure Web Applications and Web Services |
[Permissions 2008] | Permissions in the Java™ SE 6 Development Kit (JDK) |
[Seacord 2015] |
翻訳元
これは以下のページを翻訳したものです。
IDS07-J. Sanitize untrusted data passed to the Runtime.exec() method (revision 166)