Sécurité de l'activité

Android protège les utilisateurs contre les applications malveillantes et offre une expérience d'interface utilisateur fiable. Le framework de sécurité des activités englobe les règles et les restrictions de plate-forme. Ces règles et restrictions empêchent les interruptions indésirables de l'UI, le détournement de tâches et d'autres menaces de sécurité. Ces menaces concernent le moment et la manière dont les composants de l'application s'affichent à l'écran. Un composant clé de ce framework limite le démarrage d'activités en arrière-plan.

Restrictions de lancement d'activités en arrière-plan

Un lancement d'activité en arrière-plan (BAL, Background Activity Launch) se produit lorsqu'une application qui n'est pas au premier plan, sans activité visible, ou un PendingIntent reçu d'une autre application tente de démarrer une nouvelle activité. Il s'agit d'un lancement d'activité en arrière-plan (BAL, Background Activity Launch). Bien qu'il existe des cas d'utilisation légitimes, par exemple lorsqu'une application d'alarme démarre, les BAL non restreintes entraînent une mauvaise expérience utilisateur et créent des failles de sécurité.

Pourquoi sont-elles limitées ?

Depuis Android 10 (niveau d'API 29), la plate-forme impose des restrictions sur le moment où les applications peuvent démarrer des activités en arrière-plan. Ces protections permettent d'empêcher les comportements malveillants des applications et d'améliorer l'expérience utilisateur en atténuant les utilisations abusives courantes, y compris :

  • Détournement de l'interface utilisateur et annonces pop-up : une application en arrière-plan lance de manière inattendue une activité (souvent une annonce) au-dessus de l'application avec laquelle l'utilisateur interagit actuellement, détournant ainsi sa session.
  • Hameçonnage et usurpation d'identité : une application en arrière-plan lance une activité qui usurpe l'identité d'une autre application (par exemple, un faux écran de connexion pour une application légitime) afin de voler les identifiants de l'utilisateur. Cela se fait souvent par le biais d'une attaque "Activity Sandwich", où une activité malveillante est insérée dans la pile de tâches d'une application légitime.
  • Tapjacking : une application en arrière-plan affiche une activité transparente ou masquée au-dessus d'une autre application pour intercepter les actions de l'utilisateur et l'inciter à effectuer des actions non souhaitées.
  • Réveil d'application : un composant en arrière-plan d'une application réveille les composants de premier plan d'une autre application pour gonfler de manière illégitime les métriques des utilisateurs actifs quotidiens.

Services de premier plan (pour les tâches en cours)

Si votre application doit effectuer une tâche de longue durée en arrière-plan dont l'utilisateur doit être informé, comme la lecture de musique ou le suivi d'un entraînement, vous devez utiliser un service de premier plan. Un service de premier plan doit afficher une notification persistante que l'utilisateur ne peut pas fermer. Cette notification peut fournir des commandes interactives (par exemple, des boutons de lecture/pause pour une application musicale). L'utilisateur reste informé et garde le contrôle, mais n'est pas interrompu par une activité en plein écran.

En suivant cette hiérarchie (en commençant par les notifications standards et en ne passant à des options plus intrusives qu'en cas de nécessité), vous créez une expérience plus agréable et plus prévisible pour vos utilisateurs.

Quand le démarrage des activités en arrière-plan est autorisé (exceptions)

Une application peut démarrer une activité en arrière-plan si l'une des conditions suivantes est remplie :

  • L'application dispose d'une fenêtre visible, telle qu'une activité au premier plan.
  • L'application est l'éditeur de mode de saisie (IME) actuel.
  • L'activité est lancée à partir d'un PendingIntent envoyé par le système (par exemple, à partir d'un appui sur une notification).
  • L'utilisateur a accordé l'autorisation SYSTEM_ALERT_WINDOW à l'application.
  • L'autorisation START_ACTIVITIES_FROM_BACKGROUND a été accordée à l'application.
  • L'application est liée à un service qui a été autorisé à démarrer des activités en arrière-plan.
  • Le lancement est initié par l'application de lanceur d'applications de l'appareil, par exemple lorsqu'un utilisateur appuie sur l'icône d'une application ou interagit avec un widget.
  • Le lancement se fait à partir d'une partie essentielle du système d'exploitation qui doit s'exécuter en permanence, comme le service de téléphonie qui lance l'écran d'appel entrant.

Nouveaux renforcement et activation obligatoire

Pour renforcer la sécurité, Android a introduit des règles plus strictes exigeant un consentement explicite pour les applications qui utilisent PendingIntent et IntentSender pour lancer des activités. Un lancement n'est autorisé que si l'application qui a créé le PendingIntent ou celle qui l'envoie choisit d'accorder ses privilèges de lancement en arrière-plan.

Dans la plupart des cas, l'application qui envoie le PendingIntent doit être celle qui active le consentement, car il s'agit généralement de l'application avec laquelle l'utilisateur interagit directement (par exemple, en appuyant sur un bouton).

Les expéditeurs doivent activer PendingIntent

Lorsque votre application cible Android 14 (niveau d'API 34) ou version ultérieure, elle n'accorde plus ses privilèges BAL par défaut lors de l'envoi d'un PendingIntent. Si vous n'activez pas explicitement cette option, le lancement de l'activité peut être bloqué, sauf si le créateur de PendingIntent a déjà accordé ses propres droits.

Pour s'assurer qu'un lancement réussit, l'expéditeur doit activer l'octroi de ses privilèges en appelant ActivityOptions.setPendingIntentBackgroundActivityStartMode(). Le mode recommandé est ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE (ajouté dans le SDK 36).

Il s'agit d'un mode plus strict et plus sûr. Elle n'accorde l'autorisation que si l'application émettrice est visible à l'écran au moment où PendingIntent est envoyé. Cela garantit plus fortement que le lancement de l'activité est le résultat direct de l'interaction d'un utilisateur avec votre application.

Tableau des intents en attente
Figure 1 : Organigramme des décisions pour les lancements d'activités en arrière-plan.

Utilisez ActivityOptions.setPendingIntentBackgroundActivityStartMode() pour accorder des privilèges.

// 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)
}

Les créateurs doivent activer PendingIntent

Lorsque votre application cible Android 15 (niveau d'API 35) ou version ultérieure, une application qui crée un PendingIntent n'accorde plus ses privilèges de lancement en arrière-plan par défaut. Pour autoriser l'expéditeur à utiliser les privilèges BAL de votre application, vous devez l'activer explicitement.

Utilisez ActivityOptions.setPendingIntentCreatorBackgroundActivityStartMode() pour accorder des privilèges.

// 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())

Lancement avec IntentSender

Les mêmes restrictions BAL s'appliquent également lors du lancement d'activités à l'aide d'un IntentSender. Étant donné qu'un IntentSender est obtenu via PendingIntent.getIntentSender, il est soumis à des exigences d'activation similaires.

  • À partir d'Android 14 (API 34), l'utilisation de Context.startIntentSender() nécessite un consentement de l'expéditeur. Vous devez également fournir le bundle ActivityOptions ici.
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())
  • À partir d'Android 17 (API 37 et versions ultérieures), l'utilisation de IntentSender.sendIntent() nécessite une activation côté émetteur.
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())

Diagramme de séquence : restrictions BAL

Tableau des intents en attente
Figure 2 : Processus de lancement sécurisé d'une activité à l'aide d'une PendingIntent

Ce diagramme illustre le processus de lancement sécurisé d'une activité à l'aide d'une PendingIntent. Pour qu'un lancement soit réussi, il faut une chaîne de privilèges valide dans laquelle au moins l'une des applications participantes accorde ses privilèges et peut lancer une activité en arrière-plan.

  1. Création et délégation (Application A : le créateur)
    1. L'application Creator crée le PendingIntent.
    2. Si le créateur cible le SDK 35 ou une version ultérieure, il doit déléguer explicitement ses droits BAL à l'aide de setPendingIntentCreatorBackgroundActivityStartMode() s'il souhaite que ses droits soient utilisés. Par défaut, aucun droit n'est délégué.
    3. Le PendingIntent est ensuite transmis à une autre application (application B).
  2. Lancement et contribution (Annexe B – L'expéditeur)
    1. Plus tard, l'application émettrice lance l'application en appelant PendingIntent.send().
    2. Si le SDK cible la version 34 ou ultérieure, l'expéditeur doit explicitement contribuer à ses propres privilèges à l'aide de setPendingIntentBackgroundActivityStartMode() s'il souhaite que ses privilèges soient utilisés. Par défaut, aucun droit n'est délégué.
  3. Validation de la sécurité du système Android
    1. Le système Android intercepte la requête de lancement et effectue un contrôle de sécurité.
    2. Elle évalue deux conditions :
    3. Le créateur a-t-il délégué ses droits d'accès ? L'application Creator répond-elle actuellement à l'une des exceptions générales concernant les droits d'accès et les limites d'utilisation ?
    4. L'expéditeur a-t-il contribué à ses droits d'accès, ET l'application de l'expéditeur remplit-elle actuellement l'une des exceptions générales concernant la liste d'accès aux ressources de base ?
  4. Résultat
    1. AUTORISÉ : si au moins une des deux conditions de l'étape 3 est remplie, la chaîne de privilèges est validée. Le système Android démarre l'activité cible et l'expéditeur reçoit un résultat positif.
    2. BLOCKED : si aucune des conditions n'est remplie, le système bloque le lancement. L'application émettrice ne reçoit pas de valeur de retour ni d'exception directes indiquant l'échec. Au lieu de cela, le système Android enregistre en interne un message Background activity launch blocked! dans Logcat, que les développeurs doivent vérifier pour le débogage.

Prévention du piratage de tâches

Pour éviter les attaques de détournement de tâches (comme le "sandwich d'activités"), Android 15 introduit de nouvelles règles pour les applications ciblant le niveau d'API 37 ou supérieur.

  • Règle 1 : Dans une même tâche, une activité ne peut être lancée que par une autre activité appartenant à la même application (c'est-à-dire ayant le même UID) que l'activité la plus récente de la tâche.
  • Règle 2 : Seule une activité dans une tâche de premier plan qui correspond à l'UID de l'activité la plus en haut est autorisée à créer une tâche ou à mettre au premier plan une autre tâche existante.

Activation par les développeurs des protections dans les tâches

Ce comportement peut être activé à partir du SDK cible 37. Vous devez l'activer explicitement. Il est conçu pour empêcher le piratage de tâches (ou sandwich d'activités), où une application malveillante pourrait lancer une activité dans la tâche de votre application pour l'usurper et voler les données utilisateur.

Activer les protections

Pour activer ASM pour votre application, définissez l'attribut android:allowCrossUidActivitySwitchFromBelow sur "false" dans votre fichier AndroidManifest.xml. Il s'agit d'un paramètre au niveau de l'application qui protège toutes les activités de votre application par défaut.

Créer des exceptions pour des activités spécifiques

Si vous l'avez activé pour votre application, mais que vous devez autoriser le lancement d'une activité spécifique et fiable par d'autres applications, vous pouvez créer une exception ciblée. Pour exempter une seule activité de cette protection, appelez setAllowCrossUidActivitySwitchFromBelow(true) dans la méthode onCreate() de cette activité. Cela permet de lancer une activité spécifique, tandis que le reste de votre application reste protégé.

Dépannage

Filtrez Logcat pour trouver les messages pertinents à l'aide d'une expression régulière. La balise ActivityTaskManager est souvent utilisée. Le filtrage par ActivityTaskManager peut aider à isoler les journaux.

Comprendre les messages de journaux clés

Lancement bloqué (erreur) : ce message indique qu'un démarrage d'activité a été bloqué.

Lorsque vous analysez les journaux, vérifiez les champs suivants :

  • realCallingPackage : application ayant envoyé l'Intent en attente. Il s'agit de l'expéditeur.
  • callingPackage : application ayant créé l'Intent en attente. Il s'agit du créateur.

Mode strict

À partir d'Android 16, le développeur d'applications peut activer le mode Strict pour être averti lorsqu'un lancement d'activité est bloqué (ou risque de l'être lorsque le SDK cible de l'application est augmenté).

Exemple de code à activer dès le début de la méthode Application.onCreate() de votre application, activité ou autre composant d'application :

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