【APK安全】Receiver嗅探:Android广播组件的权限与UID安全防护及测试指南
文章目录
- 前言
- 一、Receiver嗅探的核心风险与防御方案
- 1. 风险1:隐式广播未授权嗅探(权限配置缺陷)
- 风险本质
- 典型攻击案例:验证码短信广播嗅探
- 防御方案:权限强控+显式广播+接收者校验
- 2. 风险2:动态注册Receiver的嗅探(权限缺失与校验遗漏)
- 风险本质
- 典型攻击案例:动态注册广播的登录状态篡改
- 防御方案:动态注册指定权限+发送者强校验
- 3. 风险3:UID共享与签名漏洞导致的嗅探
- 风险本质
- 典型攻击案例:共享UID的系统工具广播嗅探
- 防御方案:严控UID共享+强化签名安全
- 二、Receiver嗅探的安全测试方法
- 1. 静态测试:定位配置与代码漏洞
- (1)静态注册Receiver检测(Manifest配置)
- (2)动态注册Receiver检测(代码逻辑)
- 2. 动态测试:模拟嗅探验证防御有效性
- (1)隐式广播嗅探测试
- (2)动态注册Receiver权限测试
- (3)UID共享漏洞测试(需签名密钥)
- 三、总结:Receiver安全防御的核心原则
前言
Receiver作为Android跨组件通信的核心组件,承担着“事件通知”与“数据传递”的关键角色(如系统启动完成、网络状态变化、应用内部状态更新)。但其“广播式”的通信特性,也使其成为恶意应用窃取敏感数据的目标——Receiver嗅探即恶意应用通过注册与目标应用相同的广播规则(Intent Filter)、绕过权限校验或利用UID(用户标识符)漏洞,非法接收目标应用发送的广播数据(如验证码、订单信息、用户隐私),导致数据泄露或业务逻辑被篡改。
本文聚焦Receiver嗅探的三大核心风险(权限配置缺陷、UID共享漏洞、动态注册防护缺失),拆解攻击路径、提供可落地的防御方案,并详解全流程安全测试方法。
一、Receiver嗅探的核心风险与防御方案
Receiver的安全本质是“可控的广播接收”,所有嗅探风险均源于“接收范围超出预期”。以下从权限管控、UID隔离、动态注册三个维度,分析风险成因与防御手段。
1. 风险1:隐式广播未授权嗅探(权限配置缺陷)
风险本质
隐式广播(未通过setComponent
/setPackage
指定接收者)依赖Intent Filter(Action、Category、Data)匹配Receiver,若目标应用的Receiver未配置android:permission
(接收权限)或sendBroadcast
时未指定发送权限,恶意应用可通过注册相同的Intent Filter,“监听”并接收目标应用的广播数据。常见漏洞包括:
- Receiver无接收权限(
exported=true
却未配置android:permission
); - 发送广播时未指定
permission
参数(允许任何Receiver接收); - 自定义接收权限未声明(游离权限,恶意应用可自定义同名权限)。
典型攻击案例:验证码短信广播嗅探
-
目标应用(如金融APP)通过隐式广播传递短信验证码,用于登录验证,Manifest与代码配置存在漏洞:
<!-- 风险配置:Receiver允许外部接收(exported=true),无接收权限控制 --> <receiverandroid:name=".receiver.SmsCodeReceiver"android:exported="true"> <!-- 允许外部应用接收 --><intent-filter><!-- 隐式匹配规则:Action为自定义验证码事件 --><action android:name="com.target.app.action.SMS_CODE_RECEIVED" /><category android:name="android.intent.category.DEFAULT" /></intent-filter> </receiver>
// 风险代码:发送隐式广播时未指定发送权限,任何Receiver均可接收 String smsCode = "123456"; // 用户登录验证码(敏感数据) Intent smsIntent = new Intent("com.target.app.action.SMS_CODE_RECEIVED"); smsIntent.putExtra("sms_code", smsCode); // 携带敏感验证码 sendBroadcast(smsIntent); // 未指定permission参数,广播“裸发”
-
恶意应用通过以下步骤嗅探验证码:
-
在自身Manifest中注册相同的Intent Filter:
<receiverandroid:name=".receiver.FakeSmsReceiver"android:exported="true"><intent-filter><action android:name="com.target.app.action.SMS_CODE_RECEIVED" /> <!-- 与目标应用相同的Action --><category android:name="android.intent.category.DEFAULT" /></intent-filter> </receiver>
-
在
FakeSmsReceiver
中接收并窃取验证码:public class FakeSmsReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {if ("com.target.app.action.SMS_CODE_RECEIVED".equals(intent.getAction())) {// 窃取目标应用的验证码String smsCode = intent.getStringExtra("sms_code");// 上传验证码到恶意服务器uploadToServer(smsCode);}} }
-
-
当目标应用发送验证码广播时,系统会同时将广播传递给目标应用的
SmsCodeReceiver
和恶意应用的FakeSmsReceiver
,导致验证码泄露。
防御方案:权限强控+显式广播+接收者校验
-
为Receiver配置接收权限,避免游离权限
在Manifest中为Receiver配置android:permission
(接收广播需声明的权限),并确保该权限为自定义权限且已声明(建议保护级别设为signature
),禁止normal
级权限(恶意应用可直接声明):<!-- 声明自定义接收权限(签名级保护,仅同签名应用可接收) --> <permissionandroid:name="com.target.app.permission.RECEIVE_SMS_CODE"android:protectionLevel="signature"android:description="@string/receive_sms_perm_desc" /> <!-- 权限用途说明 --><!-- Receiver关联接收权限 --> <receiverandroid:name=".receiver.SmsCodeReceiver"android:exported="true"android:permission="com.target.app.permission.RECEIVE_SMS_CODE"> <!-- 接收需权限 --><intent-filter><action android:name="com.target.app.action.SMS_CODE_RECEIVED" /><category android:name="android.intent.category.DEFAULT" /></intent-filter> </receiver>
-
发送广播时指定发送权限,双重防护
调用sendBroadcast
时通过第二个参数指定“发送权限”(接收者需声明该权限才能接收),即使Receiver权限配置失效,也能通过发送权限拦截非法接收:String smsCode = "123456"; Intent smsIntent = new Intent("com.target.app.action.SMS_CODE_RECEIVED"); smsIntent.putExtra("sms_code", smsCode); // 关键:指定发送权限,接收者需声明该权限才能接收 sendBroadcast(smsIntent, "com.target.app.permission.RECEIVE_SMS_CODE");
-
优先使用显式广播,禁止非必要隐式广播
对应用内部或指定合作应用的广播,通过setComponent
或setPackage
指定接收者,避免隐式匹配导致嗅探:String smsCode = "123456"; Intent smsIntent = new Intent("com.target.app.action.SMS_CODE_RECEIVED"); // 显式指定接收者(目标应用的SmsCodeReceiver) smsIntent.setComponent(new ComponentName("com.target.app", "com.target.app.receiver.SmsCodeReceiver")); smsIntent.putExtra("sms_code", smsCode); sendBroadcast(smsIntent);
注意:Android 8.0(API 26)后对隐式广播有严格限制(除少数系统广播外,后台应用无法接收隐式广播),但前台应用仍可接收,需持续防护。
-
代码层校验发送者UID/包名,拒绝非法广播
在Receiver的onReceive
方法中,通过getCallingUid
(获取发送者UID)或getCallingPackage
(获取发送者包名)校验身份,仅允许可信应用发送的广播:public class SmsCodeReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {// 校验发送者UID(仅允许自身或可信应用的UID)int callingUid = Binder.getCallingUid();Set<Integer> trustedUids = new HashSet<>();trustedUids.add(context.getApplicationInfo().uid); // 自身UIDtrustedUids.add(10086); // 可信合作应用UID(需实际替换)if (!trustedUids.contains(callingUid)) {abortBroadcast(); // 终止广播传递throw new SecurityException("Unauthorized sender UID: " + callingUid);}// (可选)校验发送者包名,防止UID伪造String callingPackage = context.getPackageManager().getNameForUid(callingUid);Set<String> trustedPackages = new HashSet<>();trustedPackages.add("com.target.app"); // 自身包名trustedPackages.add("com.cooper.app"); // 可信合作应用包名if (callingPackage == null || !trustedPackages.contains(callingPackage)) {abortBroadcast();throw new SecurityException("Unauthorized sender package: " + callingPackage);}// 校验通过,处理验证码String smsCode = intent.getStringExtra("sms_code");handleSmsCode(smsCode);} }
2. 风险2:动态注册Receiver的嗅探(权限缺失与校验遗漏)
风险本质
动态注册的Receiver(通过registerReceiver
在代码中注册,无需Manifest配置)若未指定permission
参数(接收权限),或未在代码中校验发送者身份,恶意应用可通过发送伪造广播或监听敏感事件,实现嗅探或业务干扰。常见漏洞包括:
- 动态注册时未传递
permission
参数(允许任何应用发送广播); - 接收系统广播(如
CONNECTIVITY_CHANGE
)时未过滤敏感数据; - 未校验发送者UID,导致恶意应用发送伪造广播篡改逻辑。
典型攻击案例:动态注册广播的登录状态篡改
-
目标应用动态注册
LoginStatusReceiver
,用于接收登录状态更新广播(如用户登录/登出),但未配置权限与身份校验:// 风险代码:动态注册Receiver时未指定接收权限,无身份校验 private LoginStatusReceiver loginReceiver;@Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);loginReceiver = new LoginStatusReceiver();IntentFilter filter = new IntentFilter("com.target.app.action.LOGIN_STATUS_CHANGED");// 未传递permission参数,任何应用均可发送广播registerReceiver(loginReceiver, filter); }// 接收登录状态广播,未校验发送者 private class LoginStatusReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {boolean isLogin = intent.getBooleanExtra("is_login", false);// 直接更新登录状态(无校验,恶意应用可伪造)updateLoginStatus(isLogin);} }
-
恶意应用发送伪造的“登录成功”广播,篡改目标应用状态:
// 恶意应用发送伪造广播 Intent fakeLoginIntent = new Intent("com.target.app.action.LOGIN_STATUS_CHANGED"); fakeLoginIntent.putExtra("is_login", true); // 伪造登录成功 // 发送广播,目标应用的Receiver会接收并更新状态 sendBroadcast(fakeLoginIntent);
-
目标应用未校验广播发送者,误将伪造广播当作合法事件,导致未登录用户被强制显示“已登录”状态,可能泄露其他用户的缓存数据。
防御方案:动态注册指定权限+发送者强校验
-
动态注册时指定接收权限,过滤非法发送者
调用registerReceiver
时,通过第三个参数传递接收权限(需提前在Manifest中声明),确保仅拥有该权限的应用能发送广播:// Manifest中声明自定义接收权限 <!-- <permission android:name="com.target.app.permission.SEND_LOGIN_STATUS" android:protectionLevel="signature" /> -->// 动态注册时指定接收权限 @Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);loginReceiver = new LoginStatusReceiver();IntentFilter filter = new IntentFilter("com.target.app.action.LOGIN_STATUS_CHANGED");// 关键:传递接收权限,发送者需声明该权限registerReceiver(loginReceiver, filter, "com.target.app.permission.SEND_LOGIN_STATUS", null); }
-
在动态Receiver中校验发送者身份
即使指定了接收权限,仍需在onReceive
中通过getCallingUid
/getCallingPackage
校验发送者,避免权限被绕过(如共享UID漏洞):private class LoginStatusReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {// 校验发送者包名(仅允许自身发送)String callingPackage = context.getPackageManager().getNameForUid(Binder.getCallingUid());if (!"com.target.app".equals(callingPackage)) {abortBroadcast();return;}// 校验通过,处理登录状态boolean isLogin = intent.getBooleanExtra("is_login", false);updateLoginStatus(isLogin);} }
-
动态Receiver注销,避免内存泄漏与持续暴露
在onDestroy
中及时注销动态Receiver,避免应用退出后仍能接收广播,减少被嗅探的窗口:@Override protected void onDestroy() {super.onDestroy();if (loginReceiver != null) {unregisterReceiver(loginReceiver); // 注销Receiver} }
3. 风险3:UID共享与签名漏洞导致的嗅探
风险本质
Android通过UID实现应用隔离,不同应用默认拥有唯一UID;但同签名且声明android:sharedUserId
的应用可共享UID(共享数据与权限)。若目标应用滥用sharedUserId
且签名密钥泄露,恶意应用可通过相同签名与UID伪装成“可信应用”,接收本应仅内部或同签名应用才能接收的广播,实现嗅探。
典型攻击案例:共享UID的系统工具广播嗅探
-
目标应用(如系统清理工具)为方便与同厂商应用共享数据,在Manifest中配置:
<!-- 风险配置:滥用sharedUserId,且签名密钥未妥善保管 --> <manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.target.systemcleaner"android:sharedUserId="android.uid.systemcleaner"> <!-- 共享UID --><receiverandroid:name=".receiver.CleanStatusReceiver"android:exported="false" <!-- 理论上仅内部接收,但UID共享导致漏洞 -->android:permission="com.target.permission.RECEIVE_CLEAN_STATUS"><intent-filter><action android:name="com.target.app.action.CLEAN_FINISHED" /> <!-- 清理完成广播,含敏感日志 --></intent-filter></receiver> </manifest>
-
恶意应用获取目标应用的签名密钥(如密钥泄露),在自身Manifest中配置相同
sharedUserId
与签名:<manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.malicious.fakecleaner"android:sharedUserId="android.uid.systemcleaner"> <!-- 共享同一UID --><!-- 用目标应用的泄露密钥签名 --> </manifest>
-
由于共享UID,恶意应用与目标应用拥有相同的权限,即使
CleanStatusReceiver
设为exported=false
,恶意应用仍能:- 动态注册相同Action的Receiver,接收“清理完成”广播;
- 读取广播中携带的敏感日志(如用户安装的应用列表、清理的文件路径)。
防御方案:严控UID共享+强化签名安全
-
禁止滥用sharedUserId,最小化权限范围
非必要不使用sharedUserId
,若需跨应用协作,优先通过“显式广播+签名校验”实现,而非依赖UID共享;若必须使用,需:- 自定义非系统级UID(避免
android.uid.system
等敏感系统UID); - 在Receiver中额外校验发送者包名(即使共享UID,也仅允许可信包名发送)。
- 自定义非系统级UID(避免
-
妥善保管签名密钥,避免密钥泄露
- 使用2048位以上RSA密钥签名,禁止使用debug签名;
- 采用Android Keystore存储密钥,禁止硬编码到代码或配置文件;
- 定期轮换签名密钥(发现泄露风险时立即更新),并通过应用商店推送签名更新。
-
对共享UID的Receiver强化校验
若Receiver所在应用使用sharedUserId
,需在onReceive
中强制校验发送者包名,拒绝非预期包名的广播:@Override public void onReceive(Context context, Intent intent) {String callingPackage = context.getPackageManager().getNameForUid(Binder.getCallingUid());// 仅允许自身和可信同UID应用发送if (!"com.target.systemcleaner".equals(callingPackage) && !"com.trusted.partner".equals(callingPackage)) {abortBroadcast();throw new SecurityException("Unauthorized package for shared UID");}// 处理广播数据 }
二、Receiver嗅探的安全测试方法
Receiver嗅探的测试需围绕“权限配置合规性”“UID与签名安全性”“广播接收可控性”三大核心,结合静态代码审计(反编译+源码分析)与动态攻击模拟,覆盖显式/隐式、静态/动态注册的全场景。
1. 静态测试:定位配置与代码漏洞
静态测试通过JADX(反编译APK)或Android Studio(源码审计),识别Receiver的权限配置、注册方式、身份校验中的风险点,核心工具为JADX、Android Lint。
(1)静态注册Receiver检测(Manifest配置)
-
步骤1:筛选高风险Receiver
在JADX中打开AndroidManifest.xml
,搜索<receiver>
标签,重点关注以下风险项:android:exported="true"
且未配置android:permission
的Receiver(允许外部接收却无权限控制);- 配置
android:permission
但未在Manifest中声明(游离权限)的Receiver; - 配置
intent-filter
的隐式Receiver(非必要场景,如内部通信使用隐式广播); - 应用声明
android:sharedUserId
且Receiver处理敏感数据的场景。
-
步骤2:验证权限有效性
对Receiver依赖的接收权限,使用ADB命令检查是否为系统预定义权限或已声明的自定义权限:# 检查权限是否存在(非游离权限) adb shell pm list permissions -f | grep "目标权限名称" # 示例:检查com.target.app.permission.RECEIVE_SMS_CODE adb shell pm list permissions -f | grep "com.target.app.permission.RECEIVE_SMS_CODE"
若命令无输出,说明权限未声明,确认为游离权限风险。
(2)动态注册Receiver检测(代码逻辑)
-
步骤1:定位动态注册点
在JADX中搜索registerReceiver
关键字,筛选所有动态注册Receiver的代码,重点关注:- 未传递
permission
参数的registerReceiver
调用(如registerReceiver(receiver, filter)
); - 传递的
permission
为游离权限(未在Manifest中声明); - 接收敏感事件(如登录状态、验证码)且无身份校验的动态Receiver。
- 未传递
-
步骤2:审计
onReceive
中的身份校验
检查Receiver的onReceive
方法,是否存在getCallingUid
、getCallingPackage
或checkCallingPermission
调用,无校验逻辑则为风险。
2. 动态测试:模拟嗅探验证防御有效性
动态测试通过ADB命令、测试应用模拟恶意嗅探,验证Receiver的防御逻辑是否生效,核心工具为ADB、Android Studio(测试应用开发)。
(1)隐式广播嗅探测试
-
构建测试应用
- 在测试应用Manifest中注册与目标Receiver相同的Intent Filter;
- 若目标Receiver配置了接收权限,在测试应用中自定义同名游离权限(
normal
级),尝试绕过。
-
测试步骤
-
启动目标应用,触发其发送敏感广播(如验证码、登录状态);
-
查看测试应用的日志,确认是否接收到目标广播的数据:
# 查看测试应用的广播接收日志 adb logcat -s FakeSmsReceiver:V
-
若测试应用能接收数据,说明存在嗅探风险;若抛出
SecurityException
,说明权限控制有效。
-
(2)动态注册Receiver权限测试
-
ADB命令模拟发送广播
使用ADB发送与目标动态Receiver匹配的广播,验证是否需权限:# 格式:adb shell am broadcast -a <Action> -n <接收者组件> --ei <extra_key> <extra_value> # 示例:尝试发送登录状态广播(无权限) adb shell am broadcast -a com.target.app.action.LOGIN_STATUS_CHANGED --ei is_login true
- 若目标应用的Receiver接收并处理广播,说明动态注册时未配置权限;
- 若返回
Permission Denial
,说明权限控制有效。
-
测试应用模拟伪造广播
开发测试应用,发送与目标动态Receiver匹配的伪造广播,验证onReceive
中的身份校验是否生效:- 若测试应用能篡改目标应用状态(如伪造登录),说明无身份校验;
- 若广播被
abortBroadcast
或抛出异常,说明身份校验有效。
(3)UID共享漏洞测试(需签名密钥)
- 构建测试应用
- 使用与目标应用相同的
sharedUserId
和签名密钥(测试环境可用debug密钥); - 注册与目标Receiver相同Action的Receiver,或动态注册接收内部广播。
- 使用与目标应用相同的
- 测试步骤
- 安装目标应用与测试应用(需卸载同UID的其他应用);
- 触发目标应用发送内部广播;
- 若测试应用能接收广播,说明
sharedUserId
配置存在风险,需强化包名校验。
三、总结:Receiver安全防御的核心原则
Receiver的安全核心是“最小化广播接收范围”,所有防御措施需围绕以下原则展开:
- 权限强控:静态Receiver配置
android:permission
,动态Receiver注册时指定权限,发送广播时补充发送权限,避免游离权限; - 显式优先:非必要不使用隐式广播,内部通信强制用显式广播(
setComponent
/setPackage
); - 身份校验:在
onReceive
中强制校验发送者UID/包名,即使配置权限也需二次确认; - UID谨慎:禁止滥用
sharedUserId
,共享UID场景需额外校验包名,避免签名密钥泄露; - 持续测试:静态审计配置与代码,动态模拟嗅探场景,确保防御逻辑在不同Android版本下生效。