Bezpieczeństwo aktywności

Android chroni użytkowników przed złośliwymi aplikacjami i zapewnia im godny zaufania interfejs. Ramy zabezpieczeń aktywności obejmują reguły i ograniczenia platformy. Te reguły i ograniczenia zapobiegają niechcianym przerwom w interfejsie, przejmowaniu zadań i innym zagrożeniom dla bezpieczeństwa. Zagrożenia te dotyczą tego, kiedy i jak komponenty aplikacji pojawiają się na ekranie. Kluczowy komponent tego frameworka ogranicza rozpoczynanie Activity w tle.

Ograniczenia dotyczące uruchamiania aktywności w tle

Uruchomienie aktywności w tle (BAL) następuje, gdy aplikacja, która nie jest na pierwszym planie, nie ma widocznych aktywności lub otrzymała pakiet PendingIntent z innej aplikacji, próbuje rozpocząć nową aktywność. Jest to uruchomienie aktywności w tle (BAL). Chociaż istnieją uzasadnione przypadki użycia, np. gdy włącza się aplikacja budzika, nieograniczone BAL-e pogarszają komfort użytkowania i stwarzają luki w zabezpieczeniach.

Dlaczego są one ograniczone?

Od Androida 10 (poziom interfejsu API 29) platforma wprowadziła ograniczenia dotyczące tego, kiedy aplikacje mogą rozpoczynać działania w tle. Te zabezpieczenia pomagają zapobiegać złośliwemu działaniu aplikacji i zwiększają wygodę użytkowników, ograniczając typowe nadużycia, takie jak:

  • Przejęcie interfejsu i reklamy wyskakujące: aplikacja działająca w tle niespodziewanie uruchamia aktywność (często reklamę) nad aplikacją, z której użytkownik aktualnie korzysta, przejmując jego sesję.
  • Phishing i podszywanie się: aplikacja działająca w tle uruchamia aktywność, która podszywa się pod inną aplikację (np. fałszywy ekran logowania do legalnej aplikacji), aby wykraść dane logowania użytkownika. Często odbywa się to za pomocą ataku „Activity Sandwich”, w którym złośliwa aktywność jest wstawiana do stosu zadań legalnej aplikacji.
  • Przechwytywanie kliknięć: aplikacja działająca w tle wyświetla przezroczystą lub zasłoniętą aktywność nad inną aplikacją, aby przechwytywać kliknięcia użytkownika i nakłaniać go do wykonywania niezamierzonych działań.
  • Wzbudzanie aplikacji: komponent działający w tle w jednej aplikacji wzbudza komponenty działające na pierwszym planie w innej aplikacji, aby nielegalnie zwiększyć dane o dziennej liczbie aktywnych użytkowników.

Usługi działające na pierwszym planie (w przypadku bieżących zadań)

Jeśli aplikacja musi wykonywać w tle długotrwałe zadanie, o którym użytkownik musi wiedzieć, np. odtwarzać muzykę lub śledzić trening, powinna korzystać z usługi na pierwszym planie. Usługa na pierwszym planie musi wyświetlać trwałe powiadomienie, którego użytkownik nie może zamknąć. To powiadomienie może zawierać interaktywne elementy sterujące (np. przyciski odtwarzania/wstrzymywania w aplikacji do odtwarzania muzyki). Dzięki temu użytkownik jest informowany i ma kontrolę, ale nie jest przerywany przez aktywność na pełnym ekranie.

Stosując tę hierarchię – zaczynając od standardowych powiadomień i tylko w razie potrzeby przechodząc do bardziej natrętnych opcji – zapewnisz użytkownikom lepsze i bardziej przewidywalne wrażenia.

Kiedy zezwala się na uruchamianie aktywności w tle (wyjątki)

Aplikacja może rozpocząć aktywność w tle, jeśli zostanie spełniony jeden z tych warunków:

  • Aplikacja ma widoczne okno, np. aktywność na pierwszym planie.
  • Aplikacja jest obecnie edytorem metody wprowadzania (IME).
  • Aktywność jest uruchamiana z PendingIntent wysłanego przez system (np. po kliknięciu powiadomienia).
  • Aplikacja ma uprawnienie SYSTEM_ALERT_WINDOW przyznane przez użytkownika.
  • Aplikacja otrzymała uprawnienie START_ACTIVITIES_FROM_BACKGROUND.
  • Aplikacja jest powiązana z usługą, która ma uprawnienia do uruchamiania aktywności w tle.
  • Uruchomienie jest inicjowane przez aplikację uruchamiającą na urządzeniu, np. gdy użytkownik kliknie ikonę aplikacji lub wejdzie w interakcję z widżetem.
  • Uruchomienie następuje z podstawowej części systemu operacyjnego, która musi działać przez cały czas, np. usługa telefoniczna uruchamia ekran połączenia przychodzącego.

Nowe wzmacnianie zabezpieczeń i wymagane akceptacje

Aby jeszcze bardziej zwiększyć bezpieczeństwo, Android wprowadził bardziej rygorystyczne reguły wymagające wyraźnej zgody użytkownika w przypadku aplikacji, które używają PendingIntentIntentSender do uruchamiania działań. Uruchomienie jest dozwolone tylko wtedy, gdy aplikacja, która utworzyła PendingIntent lub aplikacja, która wysyła PendingIntent, wyrazi zgodę na przyznanie uprawnień do uruchamiania w tle.

W większości przypadków aplikacja wysyłająca PendingIntent powinna wyrazić zgodę, ponieważ zwykle jest to aplikacja, z którą użytkownik wchodzi w bezpośrednią interakcję (np. klikając przycisk).

Nadawcy muszą wyrazić zgodę na PendingIntent

Gdy aplikacja jest kierowana na Androida 14 (API na poziomie 34) lub nowszego, nie przyznaje już domyślnie uprawnień BAL podczas wysyłania PendingIntent. Jeśli nie wyrazisz na to wyraźnej zgody, uruchomienie aktywności może zostać zablokowane, chyba że twórca PendingIntent przyznał już własne uprawnienia.

Aby uruchomienie się powiodło, nadawca powinien wyrazić zgodę na przyznanie uprawnień, wywołując funkcję ActivityOptions.setPendingIntentBackgroundActivityStartMode(). Zalecany tryb to ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE (dodany w SDK 36).

Jest to bardziej rygorystyczny i bezpieczniejszy tryb. Uprawnienia są przyznawane tylko wtedy, gdy aplikacja wysyłająca jest widoczna na ekranie w momencie wysłania PendingIntent. Dzięki temu masz większą pewność, że uruchomienie aktywności jest bezpośrednim wynikiem interakcji użytkownika z Twoją aplikacją.

Tabela intencji oczekujących
Ilustracja 1. Schemat decyzyjny uruchamiania aktywności w tle.

Aby przyznać uprawnienia, użyj metody 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)
}

Twórcy muszą wyrazić zgodę na używanie PendingIntent

Gdy aplikacja jest kierowana na Androida 15 (API na poziomie 35) lub nowszego, aplikacja, która tworzy PendingIntent, nie przyznaje już domyślnie uprawnień do uruchamiania w tle. Aby zezwolić nadawcy na korzystanie z uprawnień BAL aplikacji, musisz wyraźnie wyrazić na to zgodę.

Aby przyznać uprawnienia, użyj metody 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())

Uruchamianie za pomocą IntentSender

Te same ograniczenia dotyczące BAL obowiązują też w przypadku uruchamiania działań za pomocą elementu IntentSender. IntentSender jest uzyskiwany za pomocą metody PendingIntent.getIntentSender, dlatego podlega podobnym wymaganiom dotyczącym zgody użytkownika.

  • Od Androida 14 (API 34) korzystanie z metody Context.startIntentSender() wymaga zgody po stronie nadawcy. Musisz też podać tutaj 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())
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())

Schemat sekwencji: ograniczenia dotyczące BAL

Tabela intencji oczekujących
Ilustracja 2. Proces bezpiecznego uruchamiania aktywności za pomocą intencji PendingIntent

Ten diagram ilustruje proces bezpiecznego uruchamiania aktywności za pomocą elementu PendingIntent. Udane uruchomienie zależy od prawidłowego łańcucha uprawnień, w którym co najmniej 1 z uczestniczących w nim aplikacji przyznaje uprawnienia i ma możliwość uruchomienia aktywności w tle.

  1. Tworzenie i przekazywanie uprawnień (aplikacja A – twórca)
    1. Aplikacja Creator tworzy PendingIntent
    2. Jeśli aplikacja kieruje reklamy na pakiet SDK w wersji 35 lub nowszej, twórca musi wyraźnie przekazać uprawnienia BAL za pomocą funkcji setPendingIntentCreatorBackgroundActivityStartMode(), jeśli chce, aby te uprawnienia były używane. Domyślnie nie są delegowane żadne uprawnienia.
    3. PendingIntent jest następnie dostarczany do innej aplikacji (aplikacji B).
  2. Uruchomienie i udział (aplikacja B – nadawca)
    1. Później aplikacja nadawcy inicjuje uruchomienie, wywołując funkcję PendingIntent.send().
    2. Jeśli aplikacja kieruje reklamy na pakiet SDK w wersji 34 lub nowszej, musi jawnie przekazywać własne uprawnienia za pomocą metody setPendingIntentBackgroundActivityStartMode(), jeśli chce, aby były one używane. Domyślnie nie są delegowane żadne uprawnienia.
  3. Weryfikacja bezpieczeństwa systemu Android
    1. System Android przechwytuje żądanie uruchomienia i przeprowadza kontrolę bezpieczeństwa.
    2. Ocenia 2 warunki:
    3. Czy twórca przekazał swoje uprawnienia, ORAZ czy aplikacja twórcy spełnia obecnie jeden z ogólnych wyjątków dotyczących zasad BAL?
    4. Czy nadawca przekazał swoje uprawnienia ORAZ czy aplikacja nadawcy spełnia obecnie jeden z ogólnych wyjątków dotyczących zasad BAL?
  4. Wynik
    1. DOZWOLONE: jeśli przynajmniej jeden z 2 warunków w kroku 3 jest spełniony, łańcuch uprawnień jest weryfikowany. System Android uruchamia docelową aktywność, a nadawca otrzymuje wynik wskazujący na powodzenie.
    2. BLOCKED: jeśli żaden z warunków nie jest spełniony, system blokuje uruchomienie. Aplikacja wysyłająca nie otrzymuje bezpośredniej wartości zwracanej ani wyjątku wskazującego na błąd. Zamiast tego system Android wewnętrznie rejestruje w Logcat komunikat „Background activity launch blocked!” (Uruchomienie aktywności w tle zostało zablokowane!), który deweloperzy muszą sprawdzać w celu debugowania.

Zapobieganie przejęciu zadania

Aby zapobiegać atakom polegającym na przejmowaniu zadań (np. „Activity Sandwich”), Android 15 wprowadza nowe reguły dla aplikacji kierowanych na API na poziomie 37 lub wyższym.

  • Reguła 1: w ramach jednego zadania aktywność może być uruchamiana tylko przez inną aktywność, która należy do tej samej aplikacji (czyli ma ten sam identyfikator UID) co bieżąca aktywność na samej górze zadania.
  • Reguła 2: tylko aktywność w ramach zadania na pierwszym planie, która pasuje do identyfikatora UID aktywności na samej górze, może utworzyć nowe zadanie lub przenieść na pierwszy plan inne, istniejące zadanie.

Zgoda dewelopera na ochronę w trakcie wykonywania zadania

To zachowanie można włączyć od wersji docelowego pakietu SDK 37. Aby je włączyć, musisz wyraźnie wyrazić na to zgodę. Ma to zapobiegać przejęciu zadania (lub Activity Sandwiching), w którym złośliwa aplikacja może uruchomić aktywność w zadaniu Twojej aplikacji, aby się pod nią podszyć i ukraść dane użytkownika.

Włączanie ochrony

Aby włączyć ASM w aplikacji, ustaw atrybut android:allowCrossUidActivitySwitchFromBelow na wartość false w pliku AndroidManifest.xml. Jest to ustawienie na poziomie aplikacji, które domyślnie chroni wszystkie działania w aplikacji.

Tworzenie wyjątków dla określonych działań

Jeśli ta funkcja jest włączona w Twojej aplikacji, ale musisz zezwolić na uruchamianie określonego, zaufanego działania przez inne aplikacje, możesz utworzyć wyjątek. Aby wyłączyć ochronę w przypadku pojedynczego działania, wywołaj funkcję setAllowCrossUidActivitySwitchFromBelow(true) w metodzie onCreate() tego działania. Dzięki temu można uruchomić jedną czynność, a pozostała część aplikacji pozostanie chroniona.

Rozwiązywanie problemów

Filtruj Logcat, aby znaleźć odpowiednie wiadomości za pomocą wyrażenia regularnego. Często używany jest tag ActivityTaskManager, a filtrowanie według ActivityTaskManager może pomóc w wyodrębnieniu dzienników.

Interpretacja kluczowych komunikatów w logu

Zablokowane uruchomienie (błąd): ten komunikat oznacza, że rozpoczęcie działania zostało zablokowane.

Podczas analizy logów sprawdź te pola:

  • realCallingPackage: aplikacja, która wysłała PendingIntent. Jest to nadawca.
  • callingPackage: aplikacja, która utworzyła intencję PendingIntent. To twórca.

Tryb ścisły

Od Androida 16 deweloper aplikacji może włączyć tryb ścisły, aby otrzymywać powiadomienia, gdy uruchomienie aktywności jest zablokowane (lub istnieje ryzyko zablokowania, gdy docelowy pakiet SDK aplikacji zostanie zaktualizowany).

Przykładowy kod, który należy włączyć na wczesnym etapie działania aplikacji, aktywności lub innego komponentu aplikacji w metodzie Application.onCreate():

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