안드로이드 exported 속성 완벽 가이드: 보안 취약점을 막는 필수 설정
안녕하세요.
이번 포스팅은 Android 개발시 Menifest.xml에서 application설정 항목에 중요한 요소인 'android:exported' 속성에 대해 알아보고 이 속성을 어떻게 적용해야 하는지 천천히 알아보도록 하겠습니다.
목차
- exported 속성이란?
- Android 12(API 31) 이상에서의 변화
- exported 속성 사용 방법
- 언제 true로 설정해야 할까?
- 언제 false로 설정해야 할까?
- 보안 취약점 사례 분석
- 컴포넌트별 권장 설정
- exported 관련 개발자 도구
- 자주 묻는 질문(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 속성을 올바르게 설정하는 것은 안전한 안드로이드 앱을 개발하기 위한 첫 번째 단계입니다.
긴 글 읽어주셔서 합니다.
끝.
'■Development■ > 《Android》' 카테고리의 다른 글
[Android] aapt2 process unexpectedly exit. (0) | 2024.07.09 |
---|---|
[Android] Android Studio 자주 쓰는 단축키 모음 (1) | 2022.10.07 |
[Android] 텍스트 크기에 dp 대신 sp를 사용해야 하는 이유 (0) | 2022.09.30 |
[Android] Play Asset Delivery / Play Feature Delivery 완벽 가이드 (0) | 2022.09.29 |
[Android] 키보드가 화면 레이아웃에 영향 주지 않게 하는 방법 (완벽 가이드) (0) | 2020.04.08 |