활동 보안

Android는 악성 앱으로부터 사용자를 보호하고 신뢰할 수 있는 UI 환경을 제공합니다. 활동 보안 프레임워크는 규칙과 플랫폼 제한을 포함합니다. 이러한 규칙과 제한사항은 원치 않는 UI 중단, 작업 하이재킹, 기타 보안 위협을 방지합니다. 이러한 위협은 앱 구성요소가 화면에 표시되는 시점과 방식과 관련이 있습니다. 이 프레임워크의 주요 구성요소는 백그라운드에서 활동 시작을 제한합니다.

백그라운드 활동 시작 제한

백그라운드 활동 실행 (BAL)은 포그라운드에 있지 않고 표시되는 활동이 없거나 다른 앱에서 수신된 PendingIntent가 있는 앱이 새 활동을 시작하려고 할 때 발생합니다. 이는 백그라운드 활동 실행 (BAL)입니다. 알람 시계 앱이 시작되는 경우와 같은 합법적인 사용 사례가 있지만 무제한 BAL은 사용자 환경을 저하시키고 보안 취약점을 만듭니다.

왜 제한되나요?

Android 10 (API 수준 29)부터 플랫폼에서는 앱이 백그라운드에서 활동을 시작할 수 있는 경우를 제한합니다. 이러한 보호 조치는 다음과 같은 일반적인 악용을 완화하여 악성 앱 동작을 방지하고 사용자 환경을 개선하는 데 도움이 됩니다.

  • UI 하이재킹 및 팝업 광고: 백그라운드 앱이 사용자가 현재 상호작용하고 있는 앱 위에 활동 (광고인 경우가 많음)을 예기치 않게 실행하여 세션을 하이재킹합니다.
  • 피싱 및 사칭: 백그라운드 앱이 다른 앱을 사칭하는 활동 (예: 합법적인 앱의 가짜 로그인 화면)을 실행하여 사용자 인증 정보를 도용합니다. 이는 악성 활동이 합법적인 앱의 작업 스택에 삽입되는 '활동 샌드위치' 공격을 통해 이루어지는 경우가 많습니다.
  • 탭재킹: 백그라운드 앱이 다른 앱 위에 투명하거나 가려진 활동을 표시하여 사용자의 탭을 가로채고 의도하지 않은 작업을 하도록 속입니다.
  • 앱 깨우기: 한 앱의 백그라운드 구성요소가 다른 앱의 포그라운드 구성요소를 깨워 일일 활성 사용자 측정항목을 부당하게 늘립니다.

포그라운드 서비스 (진행 중인 작업용)

앱이 음악 재생이나 운동 추적과 같이 사용자가 알아야 하는 장기 실행 작업을 백그라운드에서 실행해야 하는 경우 포그라운드 서비스를 사용해야 합니다. 포그라운드 서비스는 사용자가 닫을 수 없는 지속적인 알림을 표시해야 합니다. 이 알림은 대화형 컨트롤 (예: 음악 앱의 재생/일시중지 버튼)을 제공할 수 있습니다. 이렇게 하면 사용자에게 정보를 제공하고 제어할 수 있도록 하지만 전체 화면 활동으로 사용자를 방해하지는 않습니다.

표준 알림으로 시작하여 필요한 경우에만 더 방해가 되는 옵션으로 에스컬레이션하는 이 계층 구조를 따르면 사용자에게 더 나은 예측 가능한 환경을 제공할 수 있습니다.

백그라운드 활동 시작이 허용되는 경우 (예외)

다음 조건 중 하나를 충족하는 경우 앱은 백그라운드에서 활동을 시작할 수 있습니다.

  • 앱에 포그라운드의 활동과 같은 보이는 창이 있습니다.
  • 앱이 현재 입력 방식 편집기 (IME)입니다.
  • 활동이 시스템에서 전송된 PendingIntent에서 시작됩니다(예: 알림 탭에서).
  • 사용자가 앱에 SYSTEM_ALERT_WINDOW 권한을 부여했습니다.
  • 앱에 START_ACTIVITIES_FROM_BACKGROUND 권한이 부여되었습니다.
  • 앱이 백그라운드 활동을 시작할 권한이 부여된 서비스에 바인드되어 있습니다.
  • 실행은 사용자가 앱 아이콘을 탭하거나 위젯과 상호작용하는 등 기기의 런처 앱에 의해 시작됩니다.
  • 항상 실행되어야 하는 운영체제의 핵심 부분에서 실행됩니다(예: 전화 통신 서비스가 수신 전화 화면을 시작함).

새로운 강화 및 필수 선택

보안을 더욱 강화하기 위해 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())
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를 사용하여 활동을 안전하게 실행하는 프로세스를 보여줍니다. 성공적인 실행은 참여 앱 중 하나 이상이 권한을 부여하고 백그라운드에서 활동을 실행할 수 있는 유효한 권한 체인에 따라 달라집니다.

  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. 다음 두 가지 조건을 평가합니다.
    3. 크리에이터가 권한을 위임했으며 크리에이터 앱이 현재 일반 BAL 예외 중 하나를 충족하나요?
    4. 발신자가 권한을 제공했으며 발신자 앱이 현재 일반 BAL 예외 중 하나를 충족하나요?
  4. 결과
    1. 허용됨: 3단계의 두 조건 중 하나 이상이 충족되면 권한 체인이 검증됩니다. Android 시스템이 타겟 활동을 시작하고 발신자가 성공 결과를 수신합니다.
    2. 차단됨: 조건이 모두 충족되지 않으면 시스템에서 실행을 차단합니다. 실패를 나타내는 직접 반환 값이나 예외가 전송자 앱에 수신되지 않습니다. 대신 Android 시스템은 내부적으로 Logcat에 '백그라운드 활동 시작이 차단됨' 메시지를 로깅하며 개발자는 디버깅을 위해 이를 확인해야 합니다.

작업 도용 방지

작업 내 하이재킹 공격('활동 샌드위치' 등)을 방지하기 위해 Android 15에서는 API 수준 37 이상을 타겟팅하는 앱에 관한 새로운 규칙을 도입합니다.

  • 규칙 1: 단일 작업 내에서 활동은 현재 작업에서 가장 위에 있는 활동과 동일한 애플리케이션에 속하는 (즉, UID가 동일한) 다른 활동에 의해서만 실행될 수 있습니다.
  • 규칙 2: 최상위 활동의 UID와 일치하는 포그라운드 작업 내 활동만 새 작업을 만들거나 다른 기존 작업을 포그라운드로 가져올 수 있습니다.

개발자의 작업 내 보호 사용 설정

이 동작은 타겟 SDK 37부터 사용 설정할 수 있으며, 사용 설정하려면 명시적으로 선택해야 합니다. 이는 악성 앱이 앱의 작업에 활동을 실행하여 앱을 가장하고 사용자 데이터를 도용할 수 있는 작업 내 가로채기 (또는 활동 샌드위치)를 방지하도록 설계되었습니다.

보호 사용 설정

애플리케이션에 ASM을 선택하고 사용 설정하려면 AndroidManifest.xml 파일에서 android:allowCrossUidActivitySwitchFromBelow 속성을 false로 설정합니다. 이는 기본적으로 앱의 모든 활동을 보호하는 애플리케이션 수준 설정입니다.

특정 활동에 대한 예외 만들기

앱에 대해 사용 설정했지만 신뢰할 수 있는 특정 활동이 다른 앱에 의해 실행되도록 허용해야 하는 경우 타겟 예외를 만들면 됩니다. 이 보호에서 단일 활동을 제외하려면 해당 활동의 onCreate() 메서드 내에서 setAllowCrossUidActivitySwitchFromBelow(true)을 호출하세요. 이렇게 하면 앱의 나머지 부분이 보호된 상태로 유지되는 동안 하나의 활동을 실행할 수 있습니다.

문제 해결

정규 표현식을 사용하여 관련 메시지를 찾도록 Logcat을 필터링합니다. ActivityTaskManager 태그가 자주 사용되며 ActivityTaskManager로 필터링하면 로그를 격리하는 데 도움이 됩니다.

주요 로그 메시지 이해

차단된 실행 (오류): 이 메시지는 활동 시작이 차단되었음을 나타냅니다.

로그를 분석할 때는 다음 필드를 확인하세요.

  • realCallingPackage: PendingIntent를 보낸 앱입니다. 발신자입니다.
  • callingPackage: PendingIntent를 생성한 앱입니다. 이는 크리에이터입니다.

엄격 모드

Android 16부터 앱 개발자는 엄격 모드를 사용 설정하여 활동 실행이 차단될 때 (또는 앱의 타겟 SDK가 올라갈 때 차단될 위험이 있을 때) 알림을 받을 수 있습니다.

애플리케이션, 활동 또는 기타 애플리케이션 구성요소의 Application.onCreate() 메서드에서 사용 설정하는 예시 코드:

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