JPCERT コーディネーションセンター

IDS07-J. Runtime.exec()メソッドに渡す信頼できないデータは無害化する

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

S2076

OS commands should not be vulnerable to injection attacks
関連する脆弱性

CVE-2010-0886

Sun Java Web Start Plugin Command Line Argument Injection

CVE-2010-1826

Command injection in updateSharingD's handling of Mach RPC messages

T-472

Mac OS X Java Command Injection Flaw in updateSharingD lets local users gain elevated privileges

関連ガイドライン

CERT C コーディングスタンダード

ENV03-C. Sanitize the environment when invoking external programs
ENV33-C. Do not call system()

SEI CERT C++ Coding Standard

ENV03-CPP. Sanitize the environment when invoking external programs
VOID ENV02-CPP. Do not call system() if you do not need a command processor

SEI CERT Perl Coding Standard IDS34-PL. Do not pass untrusted, unsanitized data to a command interpreter

ISO/IEC TR 24772:2013

Injection [RST]

MITRE CWE

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)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter