본문 바로가기
■Development■/《Android》

[Android] android : exported 속성 완벽 가이드

by 은스타 2022. 10. 4.
반응형

안드로이드 exported 속성 완벽 가이드: 보안 취약점을 막는 필수 설정

안녕하세요.
이번 포스팅은 Android 개발시 Menifest.xml에서 application설정 항목에 중요한 요소인 'android:exported' 속성에 대해 알아보고 이 속성을 어떻게 적용해야 하는지 천천히 알아보도록 하겠습니다.


목차

  1. exported 속성이란?
  2. Android 12(API 31) 이상에서의 변화
  3. exported 속성 사용 방법
  4. 언제 true로 설정해야 할까?
  5. 언제 false로 설정해야 할까?
  6. 보안 취약점 사례 분석
  7. 컴포넌트별 권장 설정
  8. exported 관련 개발자 도구
  9. 자주 묻는 질문(FAQ)

 

#1. exported 속성이란?

안드로이드의 exported 속성은 앱의 컴포넌트(Activity, Service, BroadcastReceiver, ContentProvider)가 다른 앱에서 접근 가능한지 여부를 결정하는 중요한 보안 설정입니다. AndroidManifest.xml 파일에서 각 컴포넌트에 대해 이 속성을 정의할 수 있으며, 앱의 보안에 직접적인 영향을 미칩니다.

<activity
    android:name=".MainActivity"
    android:exported="true">
    <!-- ... -->
</activity>

exported 속성 값에 따른 의미:

  • true: 다른 앱에서 이 컴포넌트에 접근할 수 있음
  • false: 오직 같은 앱 또는 같은 사용자 ID를 가진 앱만 이 컴포넌트에 접근할 수 있음

이 속성은 특히 앱 간 통신(Inter-Process Communication, IPC)이 필요한 상황에서 중요하며, 부적절하게 설정될 경우 앱의 보안 취약점이 될 수 있습니다.

 

#2. Android 12(API 31) 이상에서의 변화

Android 12(API 레벨 31)부터는 exported 속성이 필수가 되었습니다. 이는 안드로이드 플랫폼의 보안을 강화하기 위한 중요한 변화입니다.

주요 변경사항

  • Android 12를 타겟팅하는 앱은 인텐트 필터(Intent Filter)가 있는 모든 컴포넌트에 대해 반드시 exported 속성을 명시적으로 선언해야 합니다.
  • 이전 버전에서는 인텐트 필터가 있으면 자동으로 exported=true로 간주되었지만, 이제는 명시적 선언이 필요합니다.
  • 이 요구사항을 지키지 않으면 앱 설치 또는 업데이트가 실패합니다.

에러 메시지 예시

Android 12 이상을 타겟팅하는 앱에서 exported 속성을 명시하지 않으면 다음과 같은 오류가 발생합니다:

Manifest merger failed : Apps targeting Android 12 and higher are required to specify an explicit value for android:exported when the corresponding component has an intent filter defined.

 

#3. exported 속성 사용 방법

AndroidManifest.xml 파일에서 각 안드로이드 컴포넌트에 대해 exported 속성을 설정할 수 있습니다.

Activity에서 사용 예시

<!-- 외부 앱에서 실행 가능한 Activity -->
<activity
    android:name=".PublicActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<!-- 앱 내부에서만 접근 가능한 Activity -->
<activity
    android:name=".PrivateActivity"
    android:exported="false" />

Service에서 사용 예시

<!-- 외부 앱에서 시작할 수 있는 Service -->
<service
    android:name=".PublicService"
    android:exported="true" />

<!-- 앱 내부에서만 사용하는 Service -->
<service
    android:name=".PrivateService"
    android:exported="false" />

BroadcastReceiver에서 사용 예시

<!-- 시스템 이벤트를 수신하는 Receiver -->
<receiver
    android:name=".SystemEventReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

<!-- 앱 내부 이벤트만 처리하는 Receiver -->
<receiver
    android:name=".InternalEventReceiver"
    android:exported="false" />

ContentProvider에서 사용 예시

<!-- 다른 앱과 데이터를 공유하는 ContentProvider -->
<provider
    android:name=".PublicDataProvider"
    android:authorities="com.example.app.provider"
    android:exported="true" />

<!-- 앱 내부에서만 사용하는 ContentProvider -->
<provider
    android:name=".PrivateDataProvider"
    android:authorities="com.example.app.private.provider"
    android:exported="false" />

 

#4. 언제 true로 설정해야 할까?

exported 속성을 true로 설정해야 하는 대표적인 상황은 다음과 같습니다:

1. 앱의 런처 Activity

앱을 시작하는 메인 화면은 사용자가 시스템 런처에서 접근할 수 있어야 하므로 exported="true"로 설정해야 합니다.

<activity
    android:name=".MainActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

2. 딥 링크를 처리하는 Activity

웹 링크나 외부 앱에서 특정 화면으로 직접 이동하는 딥 링크를 구현할 때 해당 Activity는 exported="true"로 설정해야 합니다.

<activity
    android:name=".DeepLinkActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https" android:host="example.com" />
    </intent-filter>
</activity>

3. 시스템 이벤트를 수신하는 BroadcastReceiver

시스템 이벤트(예: 부팅 완료, 네트워크 변경 등)를 수신해야 하는 BroadcastReceiver는 exported="true"로 설정해야 합니다.

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

4. 다른 앱에 제공하는 Service

다른 앱이 시작하거나 바인딩해야 하는 Service는 exported="true"로 설정해야 합니다.

<service
    android:name=".SharedService"
    android:exported="true" />

5. 데이터를 공유하는 ContentProvider

다른 앱에서 접근해야 하는 데이터를 제공하는 ContentProvider는 exported="true"로 설정해야 합니다.

<provider
    android:name=".PublicDataProvider"
    android:authorities="com.example.app.provider"
    android:exported="true" />

 

#6. 언제 false로 설정해야 할까?

다음과 같은 상황에서는 exported 속성을 false로 설정하는 것이 적절합니다:

1. 앱 내부에서만 사용하는 Activity

다른 앱에서 접근할 필요가 없는 내부 화면들은 모두 exported="false"로 설정하여 보안을 강화해야 합니다.

<activity
    android:name=".InternalSettingsActivity"
    android:exported="false" />

2. 앱 내부 로직을 처리하는 Service

백그라운드 작업이나 앱 내부 로직을 처리하는 Service는 외부 접근을 차단해야 합니다.

<service
    android:name=".DataSyncService"
    android:exported="false" />

3. 앱 내부 이벤트만 처리하는 BroadcastReceiver

앱 내부에서 발생하는 이벤트만 처리하는 BroadcastReceiver는 exported="false"로 설정해야 합니다.

<receiver
    android:name=".AppEventReceiver"
    android:exported="false" />

4. 앱 내부 데이터만 관리하는 ContentProvider

앱 내부 데이터만 관리하는 ContentProvider는 외부 접근을 차단해야 합니다.

<provider
    android:name=".InternalDataProvider"
    android:authorities="com.example.app.internal.provider"
    android:exported="false" />

 

#7. 보안 취약점 사례 분석

부적절한 exported 설정으로 인해 발생할 수 있는 실제 보안 취약점 사례를 살펴보겠습니다.

사례 1: 권한 없는 Activity 노출

<!-- 취약한 설정 -->
<activity
    android:name=".UserDataActivity"
    android:exported="true">
    <!-- 인텐트 필터가 없지만 exported="true"로 설정됨 -->
</activity>

취약점: 다른 앱에서 다음과 같은 코드로 사용자 데이터에 접근할 수 있습니다.

Intent intent = new Intent();
intent.setComponent(new ComponentName("com.vulnerable.app", "com.vulnerable.app.UserDataActivity"));
startActivity(intent);

해결방법: 앱 내부 기능만 제공하는 Activity는 반드시 exported="false"로 설정해야 합니다.

사례 2: 보호되지 않은 Service 악용

<!-- 취약한 설정 -->
<service
    android:name=".PaymentService"
    android:exported="true" />

취약점: 악의적인 앱이 결제 서비스를 직접 호출할 수 있습니다.

Intent intent = new Intent();
intent.setComponent(new ComponentName("com.vulnerable.app", "com.vulnerable.app.PaymentService"));
intent.putExtra("amount", 100000);
startService(intent);

해결방법: 중요한 기능을 수행하는 Service는 exported="false"로 설정하고, 필요한 경우 커스텀 퍼미션을 통해 접근을 제한해야 합니다.

 

#8. 컴포넌트별 권장 설정

안드로이드 앱의 각 컴포넌트 유형별 권장 exported 설정은 다음과 같습니다:

Activity

No 사용 목적 권장 설정 추가 보안 조치
1 앱 런처 화면 true -
2 딥 링크 대상 화면 true Intent 데이터 검증
3 앱 내부 화면 false -
4 설정 화면 false -
5 결제 관련 화면 false -

Service

No 사용 목적 권장 설정 추가 보안 조치
1 앱 내부 백그라운드 작업 false -
2 외부 앱 제공 기능 true 커스텀 퍼미션
3 데이터 동기화 false -
4 푸시 메시지 처리 false -

BroadcastReceiver

No 사용 목적 권장 설정 추가 보안 조치
1 시스템 이벤트 수신
true -
2 앱 내부 이벤트 처리 false -
3 FCM 메시지 수신 true -
4 앱 업데이트 수신 true -

ContentProvider

No 사용 목적 권장 설정 추가 보안 조치
1 앱 내부 데이터 관리 false -
2 다른 앱과 데이터 공유 true URI 퍼미션 또는 읽기/쓰기 퍼미션
3 파일 공유 true FileProvider 사용
4 앱 설정 관리 false -

 

#9. exported 관련 개발자 도구

안드로이드 앱의 exported 설정을 관리하는 데 도움이 되는 개발자 도구를 소개합니다.

1. Android Lint 체크

Android Studio의 Lint 도구는 안전하지 않은 exported 설정을 자동으로 검사합니다.

./gradlew lint

2. 매니페스트 병합 보고서 확인

매니페스트 병합 보고서를 통해 최종 AndroidManifest.xml의 exported 설정을 확인할 수 있습니다.

./gradlew :app:mergeDebugManifest --info

3. APK 분석기 활용

배포 전 APK를 분석하여 모든 컴포넌트의 exported 설정을 확인할 수 있습니다.

./gradlew :app:assembleDebug
apkanalyzer manifest permissions app-debug.apk

4. 보안 취약점 스캐너

OWASP ZAP, MobSF와 같은 보안 취약점 스캐너를 활용하여 잠재적인 exported 관련 취약점을 탐지할 수 있습니다.

 

#10. 자주 묻는 질문(FAQ)

Q: exported="true"와 인텐트 필터의 관계는 무엇인가요?

A: Android 11(API 30) 이하에서는 인텐트 필터가 있는 컴포넌트는 자동으로 exported="true"로 간주됩니다. 그러나 Android 12(API 31) 이상에서는 인텐트 필터의 유무와 관계없이 명시적으로 exported 속성을 설정해야 합니다.

Q: exported="false"로 설정된 컴포넌트는 어떻게 접근할 수 있나요?

A: exported="false"로 설정된 컴포넌트는 동일한 앱 내부 또는 동일한 서명과 사용자 ID를 가진 앱에서만 접근할 수 있습니다. 다른 앱에서는 접근이 불가능합니다.

Q: 앱에 필요한 최소한의 exported="true" 컴포넌트는 무엇인가요?

A: 일반적으로 다음 컴포넌트만 exported="true"로 설정하면 됩니다:

  • 앱 런처 Activity
  • 딥 링크를 처리하는 Activity
  • 시스템 이벤트를 수신하는 BroadcastReceiver
  • 다른 앱에 기능을 제공하는 Service 또는 ContentProvider

Q: exported="true"로 설정해야 하는 컴포넌트에 대한 추가 보안 조치는 무엇인가요?

A: 다음과 같은 추가 보안 조치를 고려할 수 있습니다:

  • 커스텀 퍼미션 정의 및 적용
  • 인텐트 데이터의 유효성 검증
  • 발신자 패키지 또는 서명 확인
  • ContentProvider의 경우 URI 퍼미션 사용

Q: ContentProvider의 기본 exported 값은 무엇인가요?

A: ContentProvider의 기본 exported 값은 true입니다. 즉, 명시적으로 exported="false"로 설정하지 않으면 다른 앱에서 접근할 수 있습니다. 이는 다른 컴포넌트의 기본값과 다르므로 특히 주의해야 합니다.


결론

안드로이드 앱 개발에서 exported 속성은 앱 보안의 핵심 요소입니다. 특히 Android 12 이상에서는 모든 인텐트 필터가 있는 컴포넌트에 대해 exported 속성을 명시적으로 선언해야 하므로, 개발자는 이에 대한 정확한 이해가 필요합니다.

기본 원칙은 간단합니다: 필요한 경우에만 exported="true"로 설정하고, 나머지는 모두 false로 설정하세요. 이러한 최소 권한 원칙을 따르면 앱의 공격 표면을 최소화하고 보안 취약점을 크게 줄일 수 있습니다.

보안은 개발 과정의 마지막 단계가 아니라 처음부터 고려해야 하는 중요한 요소임을 기억하세요. exported 속성을 올바르게 설정하는 것은 안전한 안드로이드 앱을 개발하기 위한 첫 번째 단계입니다.

긴 글 읽어주셔서 합니다.

끝.

반응형