脆弱性対策方法

開発者の不注意や確認不足が、脆弱性につながることが理解できたと思います。
次は、サンプルコードを基に対策方法を学んでいきましょう。

脆弱性の原因

本シナリオでの脆弱性の原因は以下となります。
  • ログの出力
    不必要なログは出力しない事が対策となります。
  • Intent#setData()でのデータの受け渡し
    ActivityManagerのログ出力を開発者が抑制することはできないため、 ログに出力されると問題になるような重要なデータをIntentに設定する場合は、setData()でのデータの受け渡しをしない事が対策となります。

対策方法の概要

実際に脆弱性のあるソースコードを確認し、修正してみましょう。
まず、「ソースコードを開く」ボタンをクリックし、「MainActivity.java」を開いてください。

ソースコードを開く

修正前:
プロジェクト OutputLog
ソースファイル MainActivity.java
	public void onClickLogin(View v) {
		// 入力された契約番号とパスワードの取得
		String id = ((EditText) findViewById(R.id.edittextId)).getText().toString();
		String pass = ((EditText) findViewById(R.id.edittextPass)).getText().toString();

		// 未入力項目がある場合はメッセージ表示
		if ("".equals(id) || "".equals(pass)) {
			Toast.makeText(this, "入力内容に不備があります", Toast.LENGTH_LONG).show();
			return;
		}

		// 入力内容をログに出力する
		String log = "ID:" + id + " PASS:" + pass;

		// ▼▼▼脆弱性のあるソースコード▼▼▼
		// ログ出力
		Log.v(getPackageName(), log);
		// ▲▲▲脆弱性のあるソースコード▲▲▲

		Intent intent = new Intent(this, MenuActivity.class);
		
		// URLにトークン情報を設定
		String param = String.format("%s?token=%s", URL_ONLINEBANK, getToken(id ,pass));
				
		// ▼▼▼脆弱性のあるソースコード▼▼▼
		// 情報を明示的Intentに設定
		intent.setData(Uri.parse(param));
		// ▲▲▲脆弱性のあるソースコード▲▲▲

		startActivity(intent);
	}

このアプリには脆弱性対策を行うべき箇所が2箇所あります。
  • 46行目
    46行目のログ出力処理を削除します。
    Log.v()に設定した値は、LogCatに出力されます。LogCatに出力されたログは、Android4.1.2以前の環境では、READ_LOGSのPermissionを持ったアプリにログを読み取られる可能性があります。
    Log.v()によるログ出力処理を削除することで、LogCatから値を読み取られなくなります。
  • 56行目
    Intentの値の受け渡しの方法を変更します。
    Intentの内容は、ActivityManagerがログ出力するため、重要な情報を意図せずにログ出力しないように配慮します。setData()で値を渡していたところを、putExtra()に変更します(赤文字部分)。
    intent.putExtra("url", param);
    setData()で設定したデータは、LogCatにそのまま出力されてしまいます。
    putExtra()で設定したデータは、LogCatには(hasExtras)とだけ表示されるため、格納されたデータがそのまま出力されることはありません。
修正例は以下の通りです。

修正後:
プロジェクト OutputLog
ソースファイル MainActivity.java
	public void onClickLogin(View v) {
		// 入力された契約番号とパスワードの取得
		String id = ((EditText) findViewById(R.id.edittextId)).getText().toString();
		String pass = ((EditText) findViewById(R.id.edittextPass)).getText().toString();

		// 未入力項目がある場合はメッセージ表示
		if ("".equals(id) || "".equals(pass)) {
			Toast.makeText(this, "入力内容に不備があります", Toast.LENGTH_LONG).show();
			return;
		}

		// 入力内容をログに出力する
		String log = "ID:" + id + " PASS:" + pass;

	    // ▼▼▼脆弱性を修正したソースコード▼▼▼
		// ログ出力はしない

	    // ▲▲▲脆弱性を修正したソースコード▲▲▲

		Intent intent = new Intent(this, MenuActivity.class);
		
		// URLにトークン情報を設定
		String param = String.format("%s?token=%s", URL_ONLINEBANK, getToken(id ,pass));
		
	    // ▼▼▼脆弱性を修正したソースコード▼▼▼
		// 情報を暗黙的Intentに設定
	    intent.putExtra("url", param);
		// ▲▲▲脆弱性を修正したソースコード▲▲▲

		startActivity(intent);
	}

対策のまとめ1(ログの出力)

「ログの出力」による脆弱性について、対策は以下のとおりです。
  • 対策
    不要なログ出力処理を削除します。
  • 修正方法
    ソースコードを確認し、不要なログ出力処理を削除します。
    なお、処理を削除する際には注意が必要です。次項「不要なログ出力を削除する際の注意」を確認してください。
  • 不要なログ出力を削除する際の注意
    不要なログ出力処理を削除する際は、以下について注意が必要です。
    • 手作業でログ出力処理を削除するのは避ける
      プログラムの規模が大きくなった場合、手作業によるログ出力処理の削除は現実的ではありません。ProGuardなどのツールを利用し、機械的に削除できるような仕組みを利用しましょう。
      ProGuradの使い方についてはProGuardの使い方を参照してください。
    • ログ出力メソッドを統一する
      ログ出力メソッドを持つクラスには、android.util.Log、System.out、System.errがあります。これらのクラスを使用しても、アプリのログの出力先はLogCatとなるため、メソッドを使い分ける意味がありません。
      リリース時にログを削除する事を考えると、ログの出力方法を統一した方が自動化しやすく、削除漏れが発生する可能性が低くなります。
    • ログ出力処理の削除が本来のアプリの処理に影響を与えない事を確認する
      ログ出力メソッドの呼び出し時の引数に、メソッドの戻り値などを使用している場合、ログ出力メソッドが実行されなくなることによって、引数におかれていたメソッドの呼び出しも行われなくなることに注意する必要があります。
      ログ出力を削除することによってアプリの動作が変わる事がないよう、ログ出力メソッドの引数には値を直接渡すようにしましょう。
  • ProGuardの使い方
    ProGuardを使用することで、ログ出力処理を削除する作業を自動化できます。ProGuardの設定ファイルはアプリのプロジェクトを作成したADTのバージョンによって異なるので注意してください。
    ADT17以降の開発環境で作成したプロジェクトの場合、アプリのプロジェクトディレクトリ直下にある「proguard-project.txt」を編集してください。
    ADT16以前で作成したプロジェクトの場合は、アプリのプロジェクトディレクトリ直下にある「proguard.cfg」を編集してください。
    もし、両方のファイルが存在している場合は、proguard.cfgを編集します。
    設定する内容は以下のとおりです。
    • Log出力メソッドの削除指定
      ここではLog.d(), Log.v()を削除する例を挙げます。
      -assumenosideeffects指定の中で削除したいメソッドを指定します。
      -assumenosideeffects class android.util.Log {
          public static int d(...);
          public static int v(...);
      }
    • ProGuardを有効にする
      プロジェクトの初期状態では、ProGuradは動作しないようになっています。動作させるためには、アプリのプロジェクトディレクトリ直下にある「project.properties」を編集します。
      以下は、「project.properties」の全体です。
      ハイライトで示した部分がProGuardを有効にする設定が書かれている部分です。この部分のコメントアウトを解除する事でProGuradが動作するようになります。
      # This file is automatically generated by Android Tools.
      # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
      #
      # This file must be checked in Version Control Systems.
      #
      # To customize properties used by the Ant build system edit
      # "ant.properties", and override values to adapt the script to your
      # project structure.
      #
      # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
      #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
      
      # Project target.
      target=android-8
    • ProGuardが動作するタイミング
      ProGuardはアプリがリリースビルドされるタイミングで自動的に実行されます。
      アプリをリリースビルドしてエクスポートする
    • 注意
      ProGuardの設定はプロジェクト単位です。ソースコードを他のプロジェクトに移植した場合には、予期せぬログ出力を防ぐために移植先プロジェクトのProGuard設定を確認してください。
    さらに詳細な設定を行いたい場合は、Android Developersを参照してください。

    参考URL:
    Android Developers-ProGuardの説明: http://developer.android.com/tools/help/proguard.html

対策のまとめ2(Intent#setData()でのデータの受け渡し)

「Intent#setData()でのデータの受け渡し」による脆弱性について、対策は以下のとおりです。
  • 対策
    ActivityManagerは、Intentの内容をログ出力します。開発者側でActivityManagerの振舞いを変更することはできないので、 開発者が取れる対策は「ログ出力される項目に重要な情報を格納しない」ということになります。
  • 修正方法
    今回の例では、ActivityManagerが受け取ったIntentのdata要素の内容をログに出力することが原因ですので、data要素には、重要な情報を格納しないように修正することが基本となります。 重要なデータを受け渡す必要がある場合には、extra要素に格納することで情報の内容がログに出力されることを抑止することができます。

    しかし、Intent自体を傍受された場合はextra要素に格納した項目も漏えいしてしまうので、注意が必要です。
    また、Android OSは様々なアプリ間でIntentをやりとりして連携します。同様の問題は他のアプリにIntentを送った場合にも起きる事が考えられます。 この問題はIntentを受け取った側によってログ出力が行われることが原因ですので、開発者側で取れる対策には限界があります。
    例えば、Gmailアプリにメール送信用のIntentを送った場合は、以下のようにメールアドレス等がLogCatに出力され、開発者側ではこの出力を止めることはできません。
    ActivityManager(10871):Starting: Intent { act=android.intent.action.SENDTO dat=mailto:info@EXAMPLE.CO.JP flg=0x3800000 cmp=com.google.android.gm/.ComposeActivityGmail (has extras) }
    
    開発者は「Intentに重要な情報を格納しない」ということを基本としてアプリを設計した方が良いといえます。


なお、Android4.2以降の環境では、ログを読み込む権限を持つアプリであっても、他のアプリが出力したログは読み込めないようになっています。
端末のバージョン 他アプリが出力したログの読み込み
Android4.1 (APIレベル16)以前 可能
Android4.2 (APIレベル17)以降 不可能