본문 바로가기

IT/Android

Making Sense of Intent Filters in Android 13

728x90
반응형

Android 13 이전에는 매니페스트에 exported component 로 등록하고 <intent-filter> 를 추가하면 명시적 intent 로 component 를 시작할 수 있었습니다. – 심지어 intent filter 가 일치하지 않는 경우에도. 일부 상황에서는 다른 앱이 내부 전용 기능을 트리거하도록 허용할 수 있습니다.

이 동작은 Android 13에서 업데이트되었습니다. 이제 action 을 지정하고 외부 앱에서 시작하는 intent는 intent가 선언된 <intent-filter> 요소와 일치하는 경우에만 exported component 에 전달됩니다 .

 

반직관적

기존 Android 버전에서는 intent 가 component 의 선언된 <intent-filter> 요소와 일치하지 않는 component(예: <activity>)에 intent 를 전달하는 두 가지 방법이 있습니다.

  1. 명시적 intent : 발신자에게 권한이 있는 한 component 이름이 설정된 intent 는 항상 component 에 전달됩니다.
  2. Intent selectors : matching 되는 intent 를 main intent 의 selector 로 설정할 때, main intent 는 항상 전달됩니다.

개발자는 intent-filter 가 부분적이 아닌 모든 intent 에 영향을 미칠 것으로 예상했습니다. 사실 우리는 인텐트 필터에 대해 많은 혼란을 보았습니다.

 

리뷰

다음 각 질문에 대해 다음이 제공됩니다.

  • startActivity()또는 sendBroadcast()에 전달되는 인텐트 개체의 생성
  • <intent-filter>요소입니다 .

당신의 임무는 다음과 같은 질문에 답하는 것입니다. intent 가 intent filter 와 일치합니까?

첫째, intent:

// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0

// Java
Intent intent = new Intent("foo");
startActivity(intent);

// Kotlin
val intent = Intent(“foo”)
startActivity(intent)

 

그리고 intent filter :

// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0

<activity android:name=".MyActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="foo" />
    </intent-filter>
</activity>

아니요! (intent 가 intent filter 와 일치하지 않습니다)

intent 에 카테고리가 포함되어 있지 않으면 Android는 startActivity() 및 startActivityForResult() 에 전달된 모든 암시적 intent 에 대해 CATEGORY_DEFAULT 로 처리합니다. 이 동작은 해당 intent 가 activity 를 시작하는데 사용되는 경우에만 정의됩니다. 암시적 activity intent( 문서 )를 수신하려면 intent filter 에 CATEGORY_DEFAULT 가 포함되어야 합니다. 이것은 activity 를 시작할 때만 적용되는 점을 참고하세요. service 를 시작하거나 broadcast 를 전송할 때는 적용되지 않습니다.

이 예제와 올바르게 일치하려면 intent filter 를 다음과 같이 구현해야 합니다.

// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0

<activity android:name=".MyActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="foo" />
+       <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

다른 예를 볼까요?

 

Intent :

// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0

-Java-
  
Intent intent = new Intent("foo");
intent.setData(Uri.parse("https://android.com"));
sendBroadcast(intent);

-Kotlin-

val intent = Intent(“foo”)
intent.data = Uri.parse("https://android.com")
startActivity(intent)

Filter :

// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0

<receiver android:name=".MyReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="foo" />
    </intent-filter>
</receiver>

또 아닙니다! (intent 가 intent filter 와 일치하지 않습니다)

인텐트 필터는 데이터가 포함된 intent 를 수락하려면 <data> 요소를 지정해야 합니다 (문서). 일치하려면 다음과 같아야 합니다.

// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0

<receiver android:name=".MyReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="foo" />
+       <data android:scheme="https" />
    </intent-filter>
</receiver>

 

하나의 위험 개선

작년에 우리는 intent filter 가 보다 직관적인 방식으로 작동하도록 하여 우리가 도울 수 있다고 생각하는 한 가지 특정 함정을 발견했습니다.

예를 들어 보겠습니다.

intent :

// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0

-Java-
Intent intent = new Intent("foo");
// Setting an explicit component name
intent.setClassName("com.example",
    "com.example.MyActivity");
startActivity(intent);

-Kotlin-
val intent = Intent(“foo”)
intent.setClassName(“com.example”, "com.example.MyActivity")
startActivity(intent)

 

filter :

// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0

<activity android:name=".MyActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="foo" />
        <category 
            android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

 

기존 Android 버전에서는 intent 와 intent filter가 일치합니다. 명시적 intent 가 선언된 intent filter 와 일치할 필요가 없기 때문입니다. 앱이 매니페스트에서 exported component 를 선언하고 <intent-filter>를 추가하면 component 는 intent filter 와 일치하지 않는 모든 intent 에 의해 시작될 수 있습니다! 이로 인해 많은 앱에서 취약점이 발생할 수 있습니다.

다음은 야생(wild)에서 발견한 예입니다.

취약한 앱 코드 :

// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0

object MyIntentHandler {
  fun handleIntent(context: Context, intent: Intent) {
    // Handles all intents of the application
  }
}

class InternalReceiver : BroadcastReceiver() {
  override fun onReceive(context: Context, intent: Intent) {
    // Dispatch all actions to centralized handler
    MyIntentHandler.handleIntent(context, intent)
  }
}

class ExternalReceiver : BroadcastReceiver() {
  override fun onReceive(context: Context, intent: Intent) {
    // Dispatch all actions to centralized handler without checks
    MyIntentHandler.handleIntent(context, intent)
  }
}

 

피해자의 매니페스트에 선언된 component:

// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0

<receiver
    android:name=".InternalReceiver"
    android:exported="false" >
  <intent-filter>
    <action android:name="com.example.PRIVATE_INTERNAL_ACTION" />
  </intent-filter>
</receiver>

<receiver
    android:name=".ExternalReceiver"
    android:exported="true" >
  <intent-filter>
    <action android:name="com.example.PUBLIC_EXTERNAL_ACTION_1" />
    <action android:name="com.example.PUBLIC_EXTERNAL_ACTION_2" />
  </intent-filter>
</receiver>

 

com.example.PRIVATE_INTERNAL_ACTION action 은 이를 처리하는 receiver(InternalReceiver)가 exported 되지 않았기 때문에 애플리케이션 외부에서 액세스할 수 없습니다. 그러나 ExternalReceiver가 들어오는 action 을 확인하고 보호하지 않기 때문에 악의적인 행위자는 다음을 수행하여 내부 기능을 트리거할 수 있습니다.

// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0

// Set action to the internal action
val intent = Intent("com.example.PRIVATE_INTERNAL_ACTION")
  
// Set an explicit component name. Before Android 13, this intent
// will always be delivered, even if the action does not match the
// intent filter declared in the manifest.
intent.setClassName("com.example", "com.example.ExternalReceiver")

sendBroadcast(intent)

android 13이상을 target 으로 하는 앱의 경우 이제 변경되었습니다. 다음 섹션에서는 이러한 변경 사항에 대해 설명합니다.

 

변경된 사항은 무엇입니까?

Android 13 이상(intent 수신 측)을 target 으로 하는 앱부터 외부 앱에서 시작된 모든 intent 는 intent가 선언된 <intent-filter>요소와 일치하는 경우에만 exported component 로 전달됩니다.

 

한 가지 큰 주의 사항: intent 가 action 을 지정하지 않으면 filter 에 하나 이상의 action 이 포함되어 있는 한 intent 일치 테스트를 통과합니다. 이는 들어오는 intent 에 action 이 없는 경우(intent.getAction() 이 null 을 반환할 때) 항상 처리해야 한다는 것을 의미합니다!

 

일치하지 않는 intent 는 차단됩니다. intent 일치가 강제되지 않는 상황은 다음과 같습니다.

  • intent filter 를 선언하지 않은 components 에 전달된 intent
  • 동일한 앱 내에서 시작된 intent
  • system 과 root 에서 시작된 intent

이 변경사항은 보안상의 이유로 훌륭하지만 – 앱이 명시적 intent 를 통해 다른 앱과 상호 작용하도록 이 동작에 의존하는 경우 Android 13 을 target 으로 업데이트 하지 않더라도 동작 변경을 볼 수 있습니다.

이러한 변경으로 인해 이전 예의 악의적인 행위자는 피해자 애플리케이션이 Android 13을 target 으로 업데이트되었다고 가정할 때 Android 13에서 더 이상 피해자 애플리케이션의 내부 기능을 트리거할 수 없습니다. 그러나 이전 Android 버전에서 실행할 때 애플리케이션을 보호하도록 허용된 작업만 확인하고 수락하도록 모든 exported component 를 업데이트하는 것이 좋습니다. 업데이트된 예제는 다음과 같습니다 .

 

// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0

class ExternalReceiver : BroadcastReceiver() {

  private val allowedActions = listOf(
    "com.example.PUBLIC_EXTERNAL_ACTION_1", 
    "com.example.PUBLIC_EXTERNAL_ACTION_2"
  )

  override fun onReceive(context: Context, intent: Intent) {
    // Skip when action is null
    val action = intent.action ?: return
    
    // Only accept actions that are allowed 
    if (!allowedActions.contains(action)) {
      return
    }

    // Dispatch all actions to centralized handler
    MyIntentHandler.handleIntent(context, intent)
  }
}

 

어떻게 해야 하나요?

먼저 intent 수신 앱이 Android 13 이상을 target 으로 하는 경우에만 시행이 활성화된다는 점에 유의해야 합니다 . 동일한 앱에 내부적으로 전달된 intent 에는 영향을 미치지 않습니다.

intent 전송 측

명시적 intent(명시적으로 component 이름이 설정된 intent)를 다른 앱 으로 보내는 경우 해당 intent 가 component 의 <intent-filter> 요소와 일치하는지 확인하세요 . 일치 logic 은 정확히 암시적 intent 를 해결 하는 것과 정확히 동일합니다 .

일치하지 않는 명시적 의도로 activity 을 시작하려고 하면 즉시 ActivityNotFoundException다음 메시지가 표시됩니다.

Unable to find explicit activity class {component name}; have you declared this activity in your AndroidManifest.xml, or does your intent not match its declared <intent-filter>?

 

broadcast receiver 및 service 의 경우 "PackageManager " 태그에 있는 경고 logcat 메시지 외에는 명백한 실패 신호가 없습니다 .

Intent does not match component’s intent filter: <intent description>

Access blocked: <component name>

 

intent 수신 측

component 가 예상하고 수락하는 가능한 모든 intent filter를 AndroidManifest.xml 에 선언해야 합니다.

그 외에도 component 가 명시적 intent 만 예상하는 경우 필터링이 필요하지 않으면 모든 <intent-filter> 요소를 ​​제거하는 것이 좋습니다. intent filter 가 없는 component 는 명시적 intent 를 허용합니다.

테스트

이 변경 사항은 개발자 프리뷰 1 이후 Android 13 빌드에서 이미 활성화되었습니다.

개발자 옵션의 호환성 프레임워크 UI를 사용하거나 터미널 창에 다음 ADB 명령을 입력하여 이 변경 사항을 토글할 수 있습니다 .

// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0

$ adb shell am compat disable ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS <package>

이는 intent 를 수신 하는 앱에 적용된다는 점을 기억하세요 . 앱이 다른 앱으로 보내는 intent 에 대한 변경을 비활성화할 수 없습니다 .

 

앞으로

이 변경 사항은 android 13 을 target 으로 하기 전에도 개발자에게 영향을 미칠 수 있으므로 외부 앱과 상호 작용하기 위해 intent 를 사용하는 경우 이 변경 사항을 조사하고 곧 업데이트를 조정하는 것이 중요합니다. 자세한 내용 은 intent 및 intent filter 를 설명하는 문서를 확인하세요.

728x90
반응형