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

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

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

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

制限される理由

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

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

フォアグラウンド サービス(進行中のタスク用)

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

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

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

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

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

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

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

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

送信者は PendingIntent をオプトインする必要がある

アプリが Android 14(API レベル 34)以上をターゲットとしている場合、PendingIntent を送信しても、デフォルトで BAL 権限が付与されなくなりました。明示的にオプトインしない場合、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. Creator アプリは PendingIntent をビルドします。
    2. SDK 35 以上をターゲットとする場合、権限を使用するには、作成者は setPendingIntentCreatorBackgroundActivityStartMode() を使用して BAL 権限を明示的に委任する必要があります。デフォルトでは、権限は委任されません。
    3. PendingIntent は別のアプリ(アプリ B)に配信されます。
  2. リリースと貢献(アプリ B - 送信者)
    1. 後で、送信側アプリが PendingIntent.send() を呼び出して起動を開始します。
    2. SDK 34 以上をターゲットとする場合、送信者は、自身の権限を使用したい場合は、setPendingIntentBackgroundActivityStartMode() を使用して自身の権限を明示的に提供する必要があります。デフォルトでは、権限は委任されません。
  3. Android システム セキュリティ検証
    1. Android システムが起動リクエストをインターセプトし、セキュリティ チェックを実行します。
    2. 次の 2 つの条件を評価します。
    3. クリエイターは権限を委任しましたか?また、クリエイター アプリは現在、一般的な BAL 例外のいずれかを満たしていますか?
    4. 送信者は権限を付与しましたか?また、送信者アプリは現在、一般的な BAL の例外のいずれかを満たしていますか?
  4. 結果
    1. ALLOWED: ステップ 3 の 2 つの条件の少なくとも 1 つが満たされている場合、権限チェーンが検証されます。Android システムはターゲット アクティビティを開始し、送信者は成功結果を受け取ります。
    2. BLOCKED: どちらの条件も満たされていない場合、システムはリリースをブロックします。送信側アプリは、失敗を示す直接の戻り値や例外を受け取りません。代わりに、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());
     )
 }