アクティビティのセキュリティ

Android は、悪意のあるアプリからユーザーを保護し、信頼できる UI エクスペリエンスを提供します。Activity Security フレームワークには、ルールとプラットフォームの制限が含まれています。これらのルールと制限により、不要な UI の中断、タスクのハイジャック、その他のセキュリティ上の脅威を防ぐことができます。これらの脅威は、アプリのコンポーネントが画面に表示されるタイミングと方法に関連しています。このフレームワークの重要なコンポーネントは、バックグラウンドからのアクティビティの起動を制限します。

バックグラウンド アクティビティの起動に関する制限

バックグラウンド アクティビティの起動(BAL)は、フォアグラウンドにないアプリ、可視アクティビティがないアプリ、または別のアプリから受信した PendingIntent が新しいアクティビティを開始しようとしたときに発生します。これがバックグラウンド アクティビティの起動(BAL)です。 アラーム時計アプリの起動など、正当なユースケースはありますが、BAL を無制限に許可すると、ユーザー エクスペリエンスが低下し、セキュリティの脆弱性が生じます。

制限される理由

Android 10(API レベル 29)以降、プラットフォームでは、アプリがバックグラウンドからアクティビティを開始できるタイミングに制限が設けられています。これらの保護機能は、次のような一般的な不正使用を軽減することで、悪意のあるアプリの動作を防ぎ、ユーザー エクスペリエンスを向上させるのに役立ちます。

  • UI のハイジャックとポップアップ広告: バックグラウンド アプリが、ユーザーが現在操作しているアプリの上にアクティビティ(多くの場合、広告)を予期せず起動し、セッションをハイジャックします。
  • フィッシングとなりすまし: バックグラウンド アプリが、別のアプリになりすますアクティビティ(たとえば、正規のアプリの偽のログイン画面)を起動して、ユーザーの認証情報を盗みます。これは多くの場合、「アクティビティ サンドイッチ」攻撃によって行われます。この攻撃では、悪意のあるアクティビティが正規のアプリのタスクスタックに挿入されます。
  • タップジャック: バックグラウンド アプリが、別のアプリの上に透明または不明瞭なアクティビティを表示して、ユーザーのタップを傍受し、意図しない操作を行うようにだまします。
  • アプリの起動: あるアプリのバックグラウンド コンポーネントが、別のアプリのフォアグラウンド コンポーネントを起動して、不正に 1 日あたりのアクティブ ユーザー数の指標を増やします。

フォアグラウンド サービス(継続的なタスクの場合)

音楽の再生やワークアウトのトラッキングなど、ユーザーが認識する必要がある長時間実行タスクをアプリがバックグラウンドで実行する必要がある場合は、フォアグラウンド サービスを使用する必要があります。フォアグラウンド サービスでは、ユーザーが閉じることができない永続的な通知を表示する必要があります。この通知には、インタラクティブなコントロール(音楽アプリの再生/一時停止ボタンなど)を含めることができます。これにより、ユーザーは常に情報を把握して操作できますが、全画面表示のアクティビティで中断されることはありません。

この階層構造に従って、標準通知から開始し、必要に応じてより侵入的なオプションにエスカレートすることで、ユーザーにとってより優れた予測可能なエクスペリエンスを実現できます。

バックグラウンド アクティビティの起動が許可される場合(例外)

次のいずれかの条件を満たす場合、アプリはバックグラウンドからアクティビティを開始できます。

  • フォアグラウンドにあるアクティビティなど、アプリが可視ウィンドウを持っている場合。
  • アプリが現在のインプット メソッド エディタ(IME)である場合。
  • アクティビティが、システムから送信された PendingIntent から開始される場合(通知のタップなど)。
  • アプリに、ユーザーによって付与された SYSTEM_ALERT_WINDOW 権限がある場合。
  • アプリに START_ACTIVITIES_FROM_BACKGROUND 権限が付与されている場合。
  • アプリが、バックグラウンド アクティビティの開始権限が付与されたサービスにバインドされている場合。
  • 起動がデバイスのランチャー アプリによって開始される場合(ユーザーがアプリアイコンをタップしたり、ウィジェットを操作したりする場合など)。
  • 起動が、常に実行する必要があるオペレーティング システムのコア部分から行われる場合(着信画面を開始する電話サービスなど)。

新しい強化と必須のオプトイン

セキュリティをさらに強化するため、Android では、PendingIntentIntentSender を使用してアクティビティを起動するアプリに対して、明示的なオプトインを必要とするより厳格なルールが導入されました。起動が許可されるのは、PendingIntent を作成したアプリまたは送信するアプリが、バックグラウンド起動権限を付与するためにオプトインした場合のみです。

ほとんどの場合、PendingIntent送信 するアプリがオプトインする必要があります。これは通常、ユーザーが直接操作しているアプリ(ボタンをタップするなど)であるためです。

送信者は PendingIntent のオプトインが必要

アプリが Android 14(API レベル 34)以降をターゲットとしている場合、PendingIntent を送信しても、デフォルトで BAL 権限は付与されなくなります。明示的にオプトインしない場合、`PendingIntent` の作成者が独自の権限をすでに付与している場合を除き、アクティビティの起動がブロックされる可能性がありますPendingIntent

起動を確実に成功させるには、送信者が ActivityOptions.setPendingIntentBackgroundActivityStartMode() を呼び出して権限を付与するようにオプトインする必要があります。 推奨されるモードActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE( SDK 36 で追加)です。

これはより厳格で安全なモードです。PendingIntent が送信されたときに送信アプリが画面に表示されている場合にのみ、権限が付与されます。これにより、アクティビティの起動がユーザーによるアプリの操作の直接的な結果であることがより確実に保証されます。

ペンディング インテントの表
図 1: バックグラウンド アクティビティの起動の決定フロー。

ActivityOptions.setPendingIntentBackgroundActivityStartMode() を使用して 権限を付与します。

// Sender Side
ActivityOptions options = ActivityOptions.makeBasic()
    .setPendingIntentBackgroundActivityStartMode(
        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE);

try {
    myPendingIntent.send(options.toBundle());
} catch (PendingIntent.CanceledException e) {
    Log.e(TAG, "The PendingIntent was canceled", e);
}
// Sender Side
val options = ActivityOptions.makeBasic().apply {
    pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE
}

try {
    myPendingIntent.send(options.toBundle())
} catch (e: PendingIntent.CanceledException) {
    Log.e(TAG, "The PendingIntent was canceled", e)
}

作成者は PendingIntent のオプトインが必要

アプリが Android 15(API レベル 35)以降をターゲットとしている場合、PendingIntent作成 するアプリは、デフォルトでバックグラウンド起動権限を付与しなくなります。送信者がアプリの BAL 権限を使用できるようにするには、明示的にオプトインする必要があります。

ActivityOptions.setPendingIntentCreatorBackgroundActivityStartMode() を使用して 権限を付与します。

// Creator Side
Intent intent = new Intent(context, MyActivity.class);
ActivityOptions options = ActivityOptions.makeBasic().setPendingIntentCreatorBackgroundActivityStartMode(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);

PendingIntent pendingIntent = PendingIntent.getActivity(context, REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE, options.toBundle());
// Creator Side
val intent = Intent(context, MyActivity::class.java)
val options = ActivityOptions.makeBasic().apply {
    pendingIntentCreatorBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}

val pendingIntent = PendingIntent.getActivity(context, REQUEST_CODE, intent,
        PendingIntent.FLAG_IMMUTABLE, options.toBundle())

IntentSender を使用して起動する

IntentSender を使用してアクティビティを起動する場合にも、同じ BAL 制限が適用されます。IntentSenderPendingIntent.getIntentSender を介して取得されるため、同様のオプトイン 要件が適用されます。

  • Android 14(API 34)以降では、Context.startIntentSender() を使用するには送信側でのオプトインが必要です。ここでも ActivityOptions バンドルを指定する必要があります。
ActivityOptions options = ActivityOptions.makeBasic()
        .setPendingIntentBackgroundActivityStartMode(
            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);

context.startIntentSender(myIntentSender, fillInIntent, flagsMask,
        flagsValues, extraFlags, options.toBundle());
val options = ActivityOptions.makeBasic().apply {
    pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}

context.startIntentSender(myIntentSender, fillInIntent, flagsMask,
        flagsValues, extraFlags, options.toBundle())
  • Android 17(API 37 以降)以降では、IntentSender.sendIntent() を使用するには送信側でのオプトインが必要です。
ActivityOptions options = ActivityOptions.makeBasic()
        .setPendingIntentBackgroundActivityStartMode(
            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);

myIntentSender.sendIntent(context, code, intent, onFinished, handler,
        requiredPermission, options.toBundle());
val options = ActivityOptions.makeBasic().apply {
    pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}

myIntentSender.sendIntent(context, code, intent, onFinished, handler,
        requiredPermission, options.toBundle())

シーケンス図: BAL 制限

ペンディング インテントの表
図 2: PendingIntent を使用してアクティビティを安全に起動するプロセス

この図は、PendingIntent を使用して Activity を安全に起動するプロセスを示しています。起動を成功させるには、有効な権限チェーン が必要です。このチェーンでは、参加しているアプリの少なくとも 1 つが権限を付与し、バックグラウンドからアクティビティを起動できる必要があります。

  1. 作成と委任(アプリ A - 作成者)
    1. 作成者アプリが PendingIntent をビルドします。
    2. SDK 35 以降をターゲットとしている場合、作成者は、権限を使用する場合は、BAL 権限をsetPendingIntentCreatorBackgroundActivityStartMode() を使用して明示的に委任する必要があります。デフォルトでは、権限は委任されません。
    3. PendingIntent は別のアプリ(アプリ B)に配信されます。
  2. 起動と貢献(アプリ B - 送信者)
    1. 後で、送信者アプリが PendingIntent.send() を呼び出して起動を開始します。
    2. SDK 34 以降をターゲットとしている場合、送信者は、権限を使用する場合は、独自の 権限をsetPendingIntentBackgroundActivityStartMode()を使用して明示的に提供する必要があります。デフォルトでは、権限は委任されません。
  3. Android システムのセキュリティ検証
    1. Android システムが起動リクエストを傍受し、セキュリティ チェックを実行します。
    2. 次の 2 つの条件を評価します。
    3. 作成者が権限を委任したか、また、作成者アプリが 現在、一般的な BAL 例外のいずれかを満たしているか。
    4. 送信者が権限を提供したか、また、送信者アプリが現在、一般的な BAL 例外のいずれかを満たしているか。
  4. 結果
    1. 許可: ステップ 3 の 2 つの条件のうち少なくとも 1 つ が満たされている場合、 権限チェーンが検証されます。Android システムがターゲット アクティビティを開始し、送信者は成功の結果を受け取ります。
    2. ブロック: どちらの 条件も満たされていない場合、システムは起動をブロックします。 送信者アプリは、失敗を示す直接的な戻り値や例外を受け取りません。代わりに、Android システムは内部的に "Background activity launch blocked!"というメッセージを Logcat に記録します。 デベロッパーはデバッグのためにこのメッセージを確認する必要があります。

タスクのハイジャック防止

タスク内ハイジャック攻撃(「アクティビティ サンドイッチ」など)を防ぐため、Android 15 では、API レベル 37 以降をターゲットとするアプリに新しいルールが導入されています。

  • ルール 1: 単一のタスク内でアクティビティを起動できるのは、タスク内の現在の最上位アクティビティと同じアプリケーション(つまり、同じ UID)に属する別の アクティビティのみです。
  • ルール 2: 最上位アクティビティの UID と一致するフォアグラウンド タスク内のアクティビティのみが、新しいタスクを作成したり、別の既存のタスクをフォアグラウンドに移動したりできます。

タスク内保護のデベロッパー オプトイン

この動作はターゲット SDK 37 以降で有効にできます。有効にするには明示的にオプトインする必要があります。これは、悪意のあるアプリがアプリのタスクにアクティビティを起動してなりすまし、ユーザーデータを盗む可能性があるタスク内ハイジャック (またはアクティビティ サンドイッチ )を防ぐように設計されています。

保護を有効にする

オプトインしてアプリの ASM を有効にするには、AndroidManifest.xml ファイルで android:allowCrossUidActivitySwitchFromBelow 属性を false に設定します。これはアプリレベルの設定で、デフォルトでアプリ内のすべてのアクティビティを保護します。

特定のアクティビティの例外を作成する

アプリで有効にしたが、特定の信頼できるアクティビティを他のアプリから起動できるようにする必要がある場合は、ターゲット例外を作成できます。単一のアクティビティをこの保護から除外するには、そのアクティビティの onCreate() メソッド内で setAllowCrossUidActivitySwitchFromBelow(true) を呼び出します。これにより、アプリの残りの部分が保護されたまま、その 1 つのアクティビティを起動できます。

トラブルシューティング

正規表現を使用して Logcat をフィルタし、関連するメッセージを見つけます。タグ ActivityTaskManager がよく使用されます。ActivityTaskManager でフィルタすると、ログを絞り込むことができます。

主なログメッセージについて

Blocked Launch(エラー):このメッセージは、アクティビティの起動が ブロックされたことを示します。

ログを分析するときは、次のフィールドを確認してください。

  • realCallingPackage: PendingIntent を送信したアプリ。これが送信者 です。
  • callingPackage: PendingIntent を作成したアプリ。これが作成者 です。

厳格モード

Android 16 以降では、アプリ デベロッパーは厳格モードを有効にして、アクティビティの起動がブロックされた場合(またはアプリのターゲット SDK が引き上げられたときにブロックされるリスクがある場合)に通知を受け取ることができます。

Application、Activity、その他のアプリケーション コンポーネントの Application.onCreate() メソッドで を有効にするコード例:

override fun onCreate(savedInstanceState: Bundle?) {
     super.onCreate(savedInstanceState)
     StrictMode.setVmPolicy(
         StrictMode.VmPolicy.Builder()
         .detectBlockedBackgroundActivityLaunch()
         .penaltyLog()
         .build());
     )
 }