前言
在Android应用的组件通信中,“公共事件”机制(如基于系统框架或第三方库的publishCommonEvent发布事件、subscribe订阅事件)是实现跨模块、跨应用数据交互的常用方式。其设计初衷是简化组件解耦,但“公共”特性也使其成为数据泄露与恶意注入的高危点——若事件发布未加密、订阅未校验身份或权限控制缺失,攻击者可通过监听敏感事件(如用户操作、业务数据)窃取隐私,或注入伪造事件篡改应用逻辑。
本文聚焦publishCommonEvent与subscribe的核心安全风险,拆解攻击路径、提供防御方案,并详解安全测试方法。
一、公共事件的核心安全风险:从发布到订阅的全链路漏洞
公共事件的安全依赖“发布者可信、数据完整、订阅者可控”三个环节,任一环节缺失防护都可能导致风险。以下从事件发布、事件传输、事件订阅三个阶段,分析典型漏洞场景。
1. 风险1:事件发布时敏感数据“裸传”(publishCommonEvent数据泄露)
风险本质
publishCommonEvent用于向“公共事件总线”发布事件,若发布时未对敏感数据(如用户ID、Token、支付信息)进行加密或脱敏,事件会以明文形式在总线中传输,任何拥有订阅权限的组件(甚至跨应用)都可直接获取数据。常见漏洞包括:
- 事件数据包含完整隐私字段(如手机号、地址);
- 仅依赖“内部总线”默认隔离,未考虑总线被注入或跨应用访问的风险;
- 发布者未校验自身权限,恶意模块可盗用发布接口发送伪造事件。
典型攻击案例:支付事件明文泄露
-
目标应用的支付模块通过
publishCommonEvent发布支付结果事件,数据未加密:// 风险代码:支付结果事件明文发布,含敏感信息 public class PayResultEvent { public String orderId; // 订单号 public String amount; // 支付金额 public String bankCardNo; // 银行卡号(完整明文) public boolean isSuccess; } // 发布事件(未加密,直接传递完整对象) PayResultEvent event = new PayResultEvent(); event.orderId = "ORDER123456"; event.amount = "1000元"; event.bankCardNo = "622202********1234"; // 敏感数据裸传 EventBus.publishCommonEvent(event); // 发布到公共事件总线 -
应用内恶意模块(如被注入的第三方SDK)通过
subscribe订阅该事件:// 恶意模块订阅支付事件,窃取敏感数据 EventBus.subscribe(PayResultEvent.class, new EventSubscriber<PayResultEvent>() { @Override public void onEvent(PayResultEvent event) { // 获取完整银行卡号和支付信息 String cardNo = event.bankCardNo; String amount = event.amount; uploadToMaliciousServer(cardNo, amount); // 上传窃取数据 } }); -
由于事件数据明文传输且无访问控制,恶意模块无需权限即可获取完整支付信息,导致用户财务数据泄露。
防御方案:事件数据加密+敏感信息脱敏
-
敏感字段加密传输
对事件中的敏感数据(如银行卡号、Token)使用非对称加密(如RSA)或对称加密(如AES),仅订阅方持有解密密钥:public class PayResultEvent { public String orderId; public String amount; // 可明文(非敏感) public String encryptedBankCardNo; // 加密后的银行卡号 public boolean isSuccess; } // 发布前加密敏感字段 PayResultEvent event = new PayResultEvent(); event.orderId = "ORDER123456"; event.amount = "1000元"; // 使用应用内预共享的AES密钥加密银行卡号 event.encryptedBankCardNo = AesEncrypt.encrypt("622202********1234", APP_AES_KEY); EventBus.publishCommonEvent(event);注意:加密密钥需通过Android Keystore存储,避免硬编码在代码中。
-
敏感信息脱敏
对非必要传输的敏感数据进行脱敏(如仅保留前4后4位),即使泄露也无法获取完整信息:// 脱敏处理:银行卡号仅保留前4后4位 String rawCardNo = "622202********1234"; event.bankCardNo = rawCardNo.replaceAll("(\\d{4})\\d+(\\d{4})", "$1****$2"); // 结果:6222****1234 -
发布者权限校验
在publishCommonEvent方法中添加权限校验,仅允许授权模块发布敏感事件:// 自定义发布事件工具类,添加权限校验 public class SecureEventPublisher { // 发布敏感事件前校验调用者权限 public static void publishSensitiveEvent(BaseEvent event) { // 检查调用者是否拥有发布权限(如应用内签名校验) if (!PermissionChecker.hasPublishPermission(event.getClass())) { throw new SecurityException("No permission to publish sensitive event"); } EventBus.publishCommonEvent(event); } } // 调用时:使用安全发布方法 SecureEventPublisher.publishSensitiveEvent(event);
2. 风险2:订阅者未校验事件来源(subscribe身份伪造)
风险本质
subscribe用于订阅指定类型的事件,若订阅时未校验事件的发布者身份(如发布模块的包名、签名),恶意模块或应用可伪造事件并发布,导致订阅者执行非预期逻辑(如篡改数据、触发敏感操作)。常见漏洞包括:
- 订阅者仅通过事件类型判断合法性,未验证发布者;
- 事件总线未隔离应用内/跨应用事件,外部应用可伪造内部事件;
- 订阅回调中直接使用事件数据执行核心逻辑(如更新订单状态、跳转支付)。
典型攻击案例:伪造登录事件绕过验证
-
目标应用的用户模块订阅“登录成功”事件,用于更新全局登录状态,未校验发布者:
// 风险代码:订阅登录事件但未校验发布者 EventBus.subscribe(LoginSuccessEvent.class, new EventSubscriber<LoginSuccessEvent>() { @Override public void onEvent(LoginSuccessEvent event) { // 直接使用事件数据更新登录状态,无来源校验 String userId = event.userId; String token = event.token; GlobalState.setLoginState(true, userId, token); // 更新全局状态 jumpToUserCenter(); // 跳转用户中心(敏感操作) } }); -
恶意模块伪造
LoginSuccessEvent并发布:// 伪造登录成功事件 LoginSuccessEvent fakeEvent = new LoginSuccessEvent(); fakeEvent.userId = "fake_user_123"; fakeEvent.token = "fake_token_abc"; // 发布伪造事件到公共总线 EventBus.publishCommonEvent(fakeEvent); -
订阅者接收伪造事件后,未验证发布者是否为合法的登录模块,直接更新登录状态并跳转用户中心,导致未登录用户绕过验证进入隐私页面。
防御方案:事件来源校验+发布者签名验证
-
事件中携带发布者身份标识
在事件对象中添加发布者的包名/模块名,供订阅者校验:public class LoginSuccessEvent { public String userId; public String token; public String publisherPackage; // 发布者包名 public String publisherSignature; // 发布者签名哈希(可选) } // 合法发布者(登录模块)发布事件时,添加自身标识 LoginSuccessEvent event = new LoginSuccessEvent(); event.userId = realUserId; event.token = realToken; event.publisherPackage = "com.target.app.login"; // 登录模块包名 EventBus.publishCommonEvent(event); -
订阅者严格校验发布者身份
在onEvent回调中,验证事件的publisherPackage是否为预期的合法发布者:EventBus.subscribe(LoginSuccessEvent.class, new EventSubscriber<LoginSuccessEvent>() { @Override public void onEvent(LoginSuccessEvent event) { // 校验发布者包名(仅允许登录模块发布) if (!"com.target.app.login".equals(event.publisherPackage)) { Log.e("Security", "Unauthorized publisher: " + event.publisherPackage); return; // 拒绝处理伪造事件 } // 校验发布者签名(防止合法包名被篡改) if (!verifyPublisherSignature(event.publisherPackage, event.publisherSignature)) { Log.e("Security", "Invalid publisher signature"); return; } // 校验通过,处理事件 GlobalState.setLoginState(true, event.userId, event.token); jumpToUserCenter(); } }); -
事件总线层面隔离应用内/跨应用事件
若使用支持跨应用的事件总线(如系统级事件框架),需通过权限或包名限制事件范围:// 订阅时指定仅接收本应用发布的事件 EventBus.subscribe(LoginSuccessEvent.class, EventScope.APPLICATION, // 限制范围:仅应用内事件 new EventSubscriber<LoginSuccessEvent>() { ... } );
3. 风险3:权限控制缺失(跨应用事件的未授权访问)
风险本质
若公共事件总线支持跨应用通信(如系统框架提供的publishCommonEvent),且未配置发布/订阅权限,恶意应用可:
- 订阅目标应用的公共事件,窃取跨应用传输的敏感数据(如合作应用间的业务数据);
- 向目标应用发布伪造事件,干扰其正常逻辑(如向支付应用发布“退款成功”事件)。
常见漏洞包括: - 事件总线未关联权限,任何应用可自由发布/订阅;
- 权限保护级别过低(如
normal),恶意应用可直接声明; - 跨应用事件未加密,中间人可拦截篡改。
典型攻击案例:跨应用订单事件被未授权订阅
-
目标应用(电商APP)与合作应用(物流APP)通过系统公共事件总线传输订单信息,未配置权限:
// 电商APP发布订单事件(跨应用可见) OrderEvent event = new OrderEvent(); event.orderId = "ORDER123"; event.receiverAddress = "北京市朝阳区xxx街道"; // 敏感收货地址 // 未指定发布权限,任何应用可订阅 SystemEventBus.publishCommonEvent(event); -
恶意应用在Manifest中声明订阅该事件,无需权限即可接收:
<!-- 恶意应用声明订阅系统事件 --> <meta-data android:name="system.event.subscribe" android:value="com.target.app.event.OrderEvent" />// 恶意应用订阅订单事件,窃取收货地址 SystemEventBus.subscribe(OrderEvent.class, new EventSubscriber<OrderEvent>() { @Override public void onEvent(OrderEvent event) { String address = event.receiverAddress; uploadAddress(address); // 上传用户隐私地址 } }); -
由于事件总线未配置权限控制,恶意应用无需授权即可订阅跨应用订单事件,导致用户收货地址等隐私数据泄露。
防御方案:权限绑定+跨应用事件加密
-
为事件总线配置发布/订阅权限
在系统事件总线或框架中,为敏感事件关联自定义权限(保护级别signature或dangerous),仅授权应用可发布/订阅:<!-- 声明跨应用事件的订阅权限(签名级保护) --> <permission android:name="com.target.app.permission.SUBSCRIBE_ORDER_EVENT" android:protectionLevel="signature" /> <!-- 仅同签名应用可订阅 --> <!-- 目标应用声明使用该权限 --> <uses-permission android:name="com.target.app.permission.SUBSCRIBE_ORDER_EVENT" />// 发布跨应用事件时指定权限(仅拥有权限的应用可订阅) SystemEventBus.publishCommonEvent( event, "com.target.app.permission.SUBSCRIBE_ORDER_EVENT" // 订阅需此权限 ); -
跨应用事件全加密传输
对跨应用传输的事件,使用双方协商的密钥(如通过安全通道交换的AES密钥)加密完整事件内容,防止中间人窃取:// 加密事件内容 byte[] eventBytes = JsonUtils.toBytes(orderEvent); byte[] encryptedBytes = AesEncrypt.encrypt(eventBytes, SHARED_AES_KEY); // 共享密钥加密 // 发布加密后的事件 EncryptedEvent encryptedEvent = new EncryptedEvent(encryptedBytes); SystemEventBus.publishCommonEvent(encryptedEvent);接收方解密后再处理:
SystemEventBus.subscribe(EncryptedEvent.class, new EventSubscriber<EncryptedEvent>() { @Override public void onEvent(EncryptedEvent encryptedEvent) { byte[] decryptedBytes = AesEncrypt.decrypt(encryptedEvent.data, SHARED_AES_KEY); OrderEvent orderEvent = JsonUtils.fromBytes(decryptedBytes, OrderEvent.class); // 处理解密后的事件 } }); -
限制跨应用事件的可见性
发布跨应用事件时,通过setPackage指定接收应用包名,避免向非预期应用暴露:// 仅允许物流APP(com.logistics.app)接收订单事件 SystemEventBus.publishCommonEvent( event, "com.target.app.permission.SUBSCRIBE_ORDER_EVENT", "com.logistics.app" // 目标应用包名 );
二、公共事件的安全测试方法
公共事件的安全测试需覆盖“发布-传输-订阅”全链路,结合静态代码审计与动态攻击模拟,验证数据保护、身份校验、权限控制的有效性。
1. 静态测试:定位事件处理的逻辑漏洞
静态测试通过JADX(反编译APK)或源码审计,识别publishCommonEvent与subscribe的使用风险,核心关注数据处理、身份校验、权限配置三个维度。
(1)事件发布逻辑检测
-
步骤1:定位publishCommonEvent调用点
在JADX中搜索publishCommonEvent关键字,筛选所有事件发布逻辑,重点关注:- 事件中是否包含敏感字段(如
user、token、card、address); - 敏感字段是否加密或脱敏(搜索
encrypt、desensitize等关键字); - 发布时是否指定权限或目标应用包名(跨应用场景)。
- 事件中是否包含敏感字段(如
-
步骤2:验证发布者权限控制
检查发布逻辑是否包含权限校验(如checkPermission、自定义权限检查方法),无校验则可能存在恶意发布风险。
(2)事件订阅逻辑检测
-
步骤1:定位subscribe调用点
搜索subscribe关键字,筛选所有事件订阅逻辑,重点关注:- 订阅的事件类型是否为敏感事件(如登录、支付、订单相关);
onEvent回调中是否有发布者身份校验(如校验publisherPackage、publisherSignature);- 是否限制事件来源范围(如仅应用内、仅指定包名)。
-
步骤2:审计事件数据使用逻辑
检查订阅者是否直接使用事件数据执行敏感操作(如更新登录态、跳转支付),未校验数据完整性(如签名验证)则存在篡改风险。
(3)权限与配置检测
-
步骤1:检查事件相关权限声明
在Manifest中搜索与事件相关的权限(如SUBSCRIBE_ORDER_EVENT),验证:- 权限是否声明(避免游离权限);
- 保护级别是否为
signature或dangerous(非normal)。
-
步骤2:分析事件总线配置
若使用第三方事件总线框架(如自定义EventBus),检查其配置是否:- 支持权限绑定;
- 区分应用内/跨应用事件;
- 提供加密传输选项。
2. 动态测试:模拟攻击验证防御有效性
动态测试通过开发测试应用或使用工具,模拟事件嗅探、伪造攻击,验证防御逻辑的有效性。
(1)事件嗅探测试(验证数据保护)
- 构建测试应用
- 若为应用内事件:开发同应用模块(或注入测试代码),调用
subscribe订阅目标敏感事件; - 若为跨应用事件:开发独立测试应用,注册订阅目标事件(若有公开权限则声明)。
- 若为应用内事件:开发同应用模块(或注入测试代码),调用
- 测试步骤
- 触发目标应用发布敏感事件(如完成一次支付、登录);
- 查看测试应用是否能接收事件,检查事件数据是否加密/脱敏:
- 若接收的是明文敏感数据(如完整银行卡号),说明数据保护缺失;
- 若接收的是加密或脱敏数据,且无法解密/还原,说明数据保护有效。
(2)事件伪造测试(验证身份校验)
- 构建测试应用
- 开发测试模块或应用,构造伪造事件(如
LoginSuccessEvent),调用publishCommonEvent发布; - 若目标应用的事件有签名验证,伪造事件时使用无效签名。
- 开发测试模块或应用,构造伪造事件(如
- 测试步骤
- 确保目标应用已订阅该事件;
- 发布伪造事件,观察目标应用行为:
- 若目标应用执行敏感操作(如跳转用户中心),说明未校验发布者身份;
- 若目标应用拒绝处理(如日志输出“未授权发布者”),说明身份校验有效。
(3)跨应用权限测试(验证权限控制)
-
使用ADB或测试应用模拟未授权订阅
# 模拟跨应用订阅事件(无权限) adb shell am broadcast -a com.target.app.event.ORDER_EVENT --es order_id "TEST123" -
测试步骤
- 未声明权限的情况下,尝试订阅跨应用事件;
- 观察是否能接收事件:
- 若能接收,说明权限控制缺失;
- 若返回
Permission Denial,说明权限控制有效。
三、总结:公共事件安全的核心原则
公共事件的安全核心是“数据最小暴露+身份严格校验”,所有防御措施需围绕以下原则展开:
- 数据传输安全:敏感字段必加密或脱敏,跨应用事件全加密;
- 身份双向校验:发布者验证自身权限,订阅者校验发布者身份(包名+签名);
- 权限精细控制:为事件绑定
signature级权限,跨应用事件限制目标包名; - 范围最小化:优先使用应用内事件总线,非必要不开启跨应用事件;
- 持续测试:静态审计事件处理逻辑,动态模拟嗅探与伪造攻击。
310

被折叠的 条评论
为什么被折叠?



