Android 移动安全教学文档:组件导出安全
前言
Android 应用由四大组件构成:Activity、Service、BroadcastReceiver 和 ContentProvider。每个组件均可选择“导出”,允许其他应用访问。此机制是 Android 进程间通信的基础,但一旦导出,组件将暴露给设备上所有已安装应用。Android 客户端安全问题多源于组件不当导出或导出后缺少访问控制。本文系统讲解组件导出的机制、潜在攻击面和审计方法。
第一章 组件导出机制详解
1.1 显式导出 vs 隐式导出
组件是否导出由 AndroidManifest.xml 中的 android:exported 属性控制。
- 显式导出:明确声明
android:exported="true"。
<activity android:name=".PaymentActivity" android:exported="true" />
- 隐式导出:在 Android 12 (API 31) 之前,若组件声明了
<intent-filter>但未显式设置exported属性,系统默认将其视为exported="true"。
<activity android:name=".ShareActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
此默认规则是历史上许多组件被意外导出的主要原因。
1.2 android:exported 默认值规则
| 条件 | Android 12 之前 (targetSdk < 31) | Android 12+ (targetSdk >= 31) |
|---|---|---|
| 有 intent-filter 且未声明 exported | 默认 true (自动导出) | 编译报错,必须显式声明 |
| 无 intent-filter 且未声明 exported | 默认 false | 默认 false |
| 显式声明 exported="true" | 导出 | 导出 |
| 显式声明 exported="false" | 不导出 | 不导出 |
Android 12 的改动强制开发者明确意图,但系统预装应用或厂商定制组件(targetSdk 可能较低)仍可能存在隐式导出。
1.3 ContentProvider 的特殊规则
ContentProvider 的默认导出规则与其他三大组件不同:
| targetSdk 版本 | 默认 exported 值 |
|---|---|
| < 17 (Android 4.2 之前) | true |
| >= 17 | false |
| 这意味着极老应用(targetSdk < 17)的 ContentProvider 默认导出。审计遗留系统时需留意。 |
第二章 权限保护机制
组件导出不等于完全暴露,Android 提供多层权限保护。
2.1 Manifest 层权限控制
在组件声明中通过 android:permission 属性直接指定访问所需权限。
<permission android:name="com.example.permission.ACCESS_SENSITIVE" android:protectionLevel="signature"/>
<service android:name=".SensitiveService" android:exported="true" android:permission="com.example.permission.ACCESS_SENSITIVE"/>
调用方需在其 Manifest 中声明并获取该权限,否则调用将被拒绝(SecurityException)。
ContentProvider 可细化读写权限:android:readPermission 和 android:writePermission。
2.2 权限保护级别 (protectionLevel)
自定义权限的有效性取决于其 protectionLevel:
| protectionLevel | 授予条件 | 安全性评估 |
|---|---|---|
| normal | 安装时自动授予,无需用户确认 | 低。任何应用声明即可获得。 |
| dangerous | 需要用户运行时授权 | 中。依赖于用户判断。 |
| signature | 必须与声明权限的应用使用相同证书签名 | 高。第三方应用无法获得。 |
| signatureOrSystem | 相同签名或为系统预装应用 | 高。 |
关键:仅用 normal 级别权限保护导出组件,保护力度极弱。
2.3 自定义权限的“抢注”风险
Android 自定义权限的 protectionLevel 由先安装的应用定义。
假设目标应用定义了一个 signature 级别权限:
<permission android:name="com.victim.permission.SENSITIVE" android:protectionLevel="signature"/>
若恶意应用先安装,并抢先定义同名权限为 normal 级别:
<permission android:name="com.victim.permission.SENSITIVE" android:protectionLevel="normal"/>
<uses-permission android:name="com.victim.permission.SENSITIVE"/>
则系统采用先安装者的定义(normal),恶意应用自动获得该权限。Android 12+ 对此有所缓解(同名权限冲突导致安装失败),但低版本仍存在风险。
2.4 代码层权限校验
Manifest 权限是“门卫”,代码层校验是内部检查。利用 Android Binder 机制记录的调用方信息(UID, PID)进行校验:
// 1. 检查调用者是否持有特定权限
if (checkCallingPermission("android.permission.DUMP") != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Permission denied");
}
// 2. 检查调用者 UID(系统进程UID通常<10000)
if (Binder.getCallingUid() >= 10000) {
throw new SecurityException("Only system apps allowed");
}
// 3. 检查调用者包名
String[] packages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
if (!Arrays.asList(packages).contains("com.trusted.app")) {
throw new SecurityException("Unauthorized caller");
}
审计要点:组件导出 + Manifest 无保护 + 代码无校验 = 常见漏洞成因。
2.5 权限保护效果演示
一个有权限保护的 SecureActivity(声明了系统签名权限 android.permission.MANAGE_USERS)与无保护组件形成对比。尝试外部启动会直接被系统拒绝:
adb shell am start -n com.demo.exportedcomponents/.SecureActivity
# 输出: java.lang.SecurityException: Permission Denial: ... requires android.permission.MANAGE_USERS
第三章 四大组件攻击面分析
3.1 Activity
攻击入口:startActivity(), startActivityForResult()
可控参数:Intent 中的 extras(键值对数据)
典型攻击模式:
| 模式 | 原理 | 危害 |
|---|---|---|
| UI 内容注入 | Activity 从 extras 读取字符串直接显示 | 伪造弹窗,钓鱼欺骗 |
| Intent 重定向 | Activity 从 extras 取出嵌套 Intent 后直接启动 | 以受害应用身份启动任意(包括未导出的)组件 |
| WebView URL 注入 | Activity 从 extras 读取 URL 并传给 WebView 加载 | 在受害应用上下文加载恶意网页 |
| 返回值劫持 | 恶意应用通过 startActivityForResult 获取敏感返回数据 |
敏感数据泄露 |
演示1:UI 内容注入
// VulnDisplayActivity.java
String title = getIntent().getStringExtra("title"); // 攻击者可控
String message = getIntent().getStringExtra("message"); // 攻击者可控
new AlertDialog.Builder(this).setTitle(title).setMessage(message).show();
攻击命令:
adb shell am start -n com.demo.exportedcomponents/.VulnDisplayActivity --es title "安全警告" --es message "您的账户存在异常登录..."
攻击者可完全控制弹窗内容,进行钓鱼。
演示2:Intent 重定向
// VulnRedirectActivity.java
Intent nested = getIntent().getParcelableExtra("next_intent"); // 攻击者可控
if (nested != null) {
startActivity(nested); // 漏洞:直接启动攻击者提供的 Intent
}
攻击命令(启动一个未导出的内部Activity):
adb shell am start -n com.demo.exportedcomponents/.VulnRedirectActivity --es target_package "com.demo.exportedcomponents" --es target_class "com.demo.exportedcomponents.InternalSecretActivity"
成功绕过 exported=false 的限制。
3.2 Service
攻击入口:
- Started Service:
startService() - Bound Service:
bindService()
可控参数: - Started Service: Intent extras
- Bound Service: AIDL 接口的所有方法参数
典型攻击模式:模式 原理 危害 未授权功能触发 通过 startService()触发敏感后台操作权限提升、数据篡改 AIDL 方法滥用 绑定服务后,调用未授权校验的 AIDL 接口方法 数据泄露、功能滥用 系统服务未授权访问 通过 ServiceManager获取系统 Binder 服务代理绕过系统权限模型
Bound Service 攻击面更大:建立连接后可持续调用方法。
演示:Started Service 未授权写文件
// VulnService.java
String filename = intent.getStringExtra("filename"); // 攻击者可控
String content = intent.getStringExtra("content"); // 攻击者可控
File file = new File(getFilesDir(), filename);
FileWriter writer = new FileWriter(file);
writer.write(content); // 漏洞:无校验,直接写入
writer.close();
攻击命令:
adb shell am startservice -n com.demo.exportedcomponents/.VulnService --es filename "config.txt" --es content "恶意数据"
adb shell run-as com.demo.exportedcomponents cat /data/data/com.demo.exportedcomponents/files/config.txt
# 输出: 恶意数据
3.3 BroadcastReceiver
攻击入口:sendBroadcast(), sendOrderedBroadcast()
可控参数:Intent 中的 action, extras
典型攻击模式:
| 模式 | 方向 | 原理 | 危害 |
|---|---|---|---|
| 广播注入 | 攻击者 → 受害者 | 向导出 Receiver 发送伪造广播 | 触发敏感逻辑、命令执行 |
| 广播劫持 | 受害者 → 攻击者 | 注册高优先级 Receiver 拦截有序广播 | 窃取广播中的敏感数据 |
| 隐式广播窃听 | 受害者 → 攻击者 | 监听未设置包名的隐式广播 | 信息泄露 |
演示:广播注入
// VulnReceiver.java
public void onReceive(Context context, Intent intent) {
String data = intent.getStringExtra("log_data"); // 攻击者可控
Log.i(TAG, "Received data: " + data); // 漏洞:直接信任并处理
}
攻击命令:
adb shell am broadcast -n com.demo.exportedcomponents/.VulnReceiver -a com.demo.exportedcomponents.ACTION_LOG --es log_data "injected_by_attacker"
应用会处理攻击者注入的数据。
3.4 ContentProvider
攻击入口:ContentResolver.query(), insert(), update(), delete(), openFile(), call()
可控参数:URI, selection, selectionArgs, projection, 文件路径等。
典型攻击模式:
| 模式 | 原理 | 危害 |
|---|---|---|
| 数据泄露 | query() 无权限保护,直接返回数据 |
读取用户敏感数据 |
| SQL 注入 | selection/projection 参数未过滤直接拼接 SQL | 读取任意表数据 |
| 路径遍历 | openFile() 未过滤 ../ 等路径字符 |
读取应用沙箱外任意文件 |
| call() 方法滥用 | call() 方法执行敏感操作且无校验 |
功能滥用 |
演示:数据泄露
Provider 无任何权限保护:
<provider android:name=".VulnProvider" android:authorities="com.demo.exportedcomponents.provider" android:exported="true"/>
攻击命令:
adb shell content query --uri content://com.demo.exportedcomponents.provider/users
直接返回所有用户数据(用户名、邮箱、手机号)。
路径遍历示例:
public ParcelFileDescriptor openFile(Uri uri, String mode) {
String path = uri.getLastPathSegment(); // 攻击者可控: 例如 "../../shared_prefs/secret.xml"
File file = new File(getContext().getFilesDir(), path); // 漏洞:可能跳出沙箱
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
第四章 Android 版本演进的影响
4.1 Android 12 (API 31): 强制声明 exported
核心改动:targetSdkVersion >= 31 的应用,所有包含 <intent-filter> 的组件必须显式声明 android:exported 属性,否则安装失败。
意义:从根本上消除“隐式导出”。
注意:
- 系统预装应用可能不受此限制。
- 已安装的旧版本应用不受影响。
- 开发者可能为图省事全部设为
exported="true"。
4.2 Android 13+:更严格的 Intent 过滤
- 动态注册的 BroadcastReceiver 默认不接收外部应用广播,除非注册时显式指定
Context.RECEIVER_EXPORTED标志。// 接收外部广播 registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED); // 不接收外部广播 registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED); - 对 PendingIntent 的 mutability 要求更严格。
4.3 厂商定制 ROM 的额外攻击面
厂商定制 ROM(如 MIUI, EMUI, ColorOS)常添加大量自定义系统服务。这些服务通过 ServiceManager.addService() 注册,不受 Manifest 的 exported 属性约束。
- 攻击入口:任何应用可通过
ServiceManager.getService("service_name")获取 Binder 代理并调用其方法。 - 安全性:完全依赖于服务内部代码层的权限校验(如
checkCallingPermission,checkCallingUid)。 - 危害:此类服务通常运行在
system_server进程(UID 1000),一旦存在未授权访问,危害极大。
第五章 总结与审计要点
组件导出是 Android 安全的“门户”。审计核心在于检查“门是否该开”以及“开门后是否有守卫”。
审计 checklist:
- 识别导出组件:检查
AndroidManifest.xml中所有组件的android:exported属性。特别注意 Android 12 前的“隐式导出”和低版本 ContentProvider 的默认导出。 - 评估权限保护:
- 组件是否受
android:permission保护? - 保护权限的
protectionLevel是什么?normal级别保护几乎无效。 - 是否存在自定义权限“抢注”风险(针对低版本)?
- 组件是否受
- 检查代码层校验:对于导出的组件,检查其代码中是否对调用者身份(权限、UID、包名)进行了校验。
- 分析攻击面:
- Activity: 检查 Intent 参数是否被安全使用,是否存在 UI 注入、Intent 重定向。
- Service: 检查
onStartCommand逻辑及 AIDL 接口方法。 - BroadcastReceiver: 检查广播数据是否被无条件信任。
- ContentProvider: 检查查询、文件操作、
call()方法是否存在数据泄露、SQL 注入、路径遍历。
- 关注系统服务:在厂商定制 ROM 中,注意审计通过
ServiceManager注册的自定义系统服务。 - 考虑 Android 版本:注意应用
targetSdkVersion,评估 Android 12/13 新规带来的安全影响。
核心安全原则:最小权限原则。非必要不导出;若必须导出,则必须实施有效的权限保护(signature 级别或以上)和严格的代码层调用者校验。