FlawOpen

flawopen.com/Command Injection/Java Runtime.exec()

Java Runtime.exec()はCommand Injectionを防げる?

CWE-78: OS Command Injectionリファレンスページ
要点

Runtime.exec(String)はshellを暗黙に起動しないため、shell演算子が自動的にshell構文として解釈されることはありません。ただし文字列はプログラムと引数に分割され、信頼できない文字列で対象プログラムに渡す引数を変えられる可能性があります。ProcessBuilderは引数境界を明示しますが、各値の検証と実行ファイル・許可オプションの制限は必要です。

危険
// ユーザー入力をコマンド文字列に連結
Runtime.getRuntime().exec(
  "approved-tool --input " + filename
);
より安全
// 実行ファイルを固定し引数を分離;先にfilenameを検証
String safeName = validateFilename(filename);
new ProcessBuilder(
  "/opt/app/bin/approved-tool",
  "--input", safeName
).start();

shell injectionとargument injectionは別の問題

Java 18 以降、Oracle はエラーを招きやすいとして Runtime.exec(String) を非推奨にし、exec(String[]) または ProcessBuilder を推奨しています。文字列オーバーロードは空白でトークン分割するため、空白を含むパスなどの引数が分割されることがあります。 Runtime.exec(String)はshなどのshellを暗黙に起動しません。Oracleの説明では、単一文字列のオーバーロードはコマンド文字列を引数配列に変換します。そのため一般的なshellメタ文字はshell構文としては処理されませんが、分割によって追加引数が生じたり、対象プログラムが引数をオプションとして解釈したりする危険は残ります。ProcessBuilderは実行ファイルと引数を文字列リストで受け取り、一般的な環境では各要素が引数境界になります。実行ファイルを固定し、対象の文法に合わせて値を検証し、オプションに見える値を拒否または安全に処理してください。--は対象の文書で保証されている場合だけ使います。信頼できない値とともにsh -cなどでshellを明示起動しないでください。

よくある質問

Runtime.exec(String)がshellを起動しないなら安全?

いいえ。このオーバーロードではshell injectionのリスクは下がりますが、argument injectionは可能です。対象プログラムが攻撃者入力をオプションやファイル名として解釈することがあります。固定した実行ファイルと分離した引数をProcessBuilderで渡し、各値を対象プログラムに合わせて検証します。

ProcessBuilderが引数を検証してくれる?

いいえ。プログラムと引数のリストを構築してプロセスを開始するだけで、安全性や妥当性は確認しません。アプリケーション側で許可リストと検証を適用し、対象プログラムの引数仕様に従ってください。

参考資料

関連項目 OS コマンドインジェクション(CWE-78)の仕組みと各言語での防ぎ方 — flawopen.com Node.js child_process.exec()のコマンドインジェクションリスク Node.js exec() と execFile():違いと安全な使い方