【APK安全】公共事件的安全风险及防御指南

前言

在Android应用的组件通信中,“公共事件”机制(如基于系统框架或第三方库的publishCommonEvent发布事件、subscribe订阅事件)是实现跨模块、跨应用数据交互的常用方式。其设计初衷是简化组件解耦,但“公共”特性也使其成为数据泄露与恶意注入的高危点——若事件发布未加密、订阅未校验身份或权限控制缺失,攻击者可通过监听敏感事件(如用户操作、业务数据)窃取隐私,或注入伪造事件篡改应用逻辑。

本文聚焦publishCommonEventsubscribe的核心安全风险,拆解攻击路径、提供防御方案,并详解安全测试方法。

一、公共事件的核心安全风险:从发布到订阅的全链路漏洞

公共事件的安全依赖“发布者可信、数据完整、订阅者可控”三个环节,任一环节缺失防护都可能导致风险。以下从事件发布、事件传输、事件订阅三个阶段,分析典型漏洞场景。

1. 风险1:事件发布时敏感数据“裸传”(publishCommonEvent数据泄露)

风险本质

publishCommonEvent用于向“公共事件总线”发布事件,若发布时未对敏感数据(如用户ID、Token、支付信息)进行加密或脱敏,事件会以明文形式在总线中传输,任何拥有订阅权限的组件(甚至跨应用)都可直接获取数据。常见漏洞包括:

  • 事件数据包含完整隐私字段(如手机号、地址);
  • 仅依赖“内部总线”默认隔离,未考虑总线被注入或跨应用访问的风险;
  • 发布者未校验自身权限,恶意模块可盗用发布接口发送伪造事件。
典型攻击案例:支付事件明文泄露
  1. 目标应用的支付模块通过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); // 发布到公共事件总线
    
  2. 应用内恶意模块(如被注入的第三方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); // 上传窃取数据
        }
    });
    
  3. 由于事件数据明文传输且无访问控制,恶意模块无需权限即可获取完整支付信息,导致用户财务数据泄露。

防御方案:事件数据加密+敏感信息脱敏
  1. 敏感字段加密传输
    对事件中的敏感数据(如银行卡号、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存储,避免硬编码在代码中。

  2. 敏感信息脱敏
    对非必要传输的敏感数据进行脱敏(如仅保留前4后4位),即使泄露也无法获取完整信息:

    // 脱敏处理:银行卡号仅保留前4后4位
    String rawCardNo = "622202********1234";
    event.bankCardNo = rawCardNo.replaceAll("(\\d{4})\\d+(\\d{4})", "$1****$2"); // 结果:6222****1234
    
  3. 发布者权限校验
    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用于订阅指定类型的事件,若订阅时未校验事件的发布者身份(如发布模块的包名、签名),恶意模块或应用可伪造事件并发布,导致订阅者执行非预期逻辑(如篡改数据、触发敏感操作)。常见漏洞包括:

  • 订阅者仅通过事件类型判断合法性,未验证发布者;
  • 事件总线未隔离应用内/跨应用事件,外部应用可伪造内部事件;
  • 订阅回调中直接使用事件数据执行核心逻辑(如更新订单状态、跳转支付)。
典型攻击案例:伪造登录事件绕过验证
  1. 目标应用的用户模块订阅“登录成功”事件,用于更新全局登录状态,未校验发布者:

    // 风险代码:订阅登录事件但未校验发布者
    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(); // 跳转用户中心(敏感操作)
        }
    });
    
  2. 恶意模块伪造LoginSuccessEvent并发布:

    // 伪造登录成功事件
    LoginSuccessEvent fakeEvent = new LoginSuccessEvent();
    fakeEvent.userId = "fake_user_123";
    fakeEvent.token = "fake_token_abc";
    // 发布伪造事件到公共总线
    EventBus.publishCommonEvent(fakeEvent);
    
  3. 订阅者接收伪造事件后,未验证发布者是否为合法的登录模块,直接更新登录状态并跳转用户中心,导致未登录用户绕过验证进入隐私页面。

防御方案:事件来源校验+发布者签名验证
  1. 事件中携带发布者身份标识
    在事件对象中添加发布者的包名/模块名,供订阅者校验:

    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);
    
  2. 订阅者严格校验发布者身份
    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();
        }
    });
    
  3. 事件总线层面隔离应用内/跨应用事件
    若使用支持跨应用的事件总线(如系统级事件框架),需通过权限或包名限制事件范围:

    // 订阅时指定仅接收本应用发布的事件
    EventBus.subscribe(LoginSuccessEvent.class, 
        EventScope.APPLICATION, // 限制范围:仅应用内事件
        new EventSubscriber<LoginSuccessEvent>() { ... }
    );
    

3. 风险3:权限控制缺失(跨应用事件的未授权访问)

风险本质

若公共事件总线支持跨应用通信(如系统框架提供的publishCommonEvent),且未配置发布/订阅权限,恶意应用可:

  • 订阅目标应用的公共事件,窃取跨应用传输的敏感数据(如合作应用间的业务数据);
  • 向目标应用发布伪造事件,干扰其正常逻辑(如向支付应用发布“退款成功”事件)。
    常见漏洞包括:
  • 事件总线未关联权限,任何应用可自由发布/订阅;
  • 权限保护级别过低(如normal),恶意应用可直接声明;
  • 跨应用事件未加密,中间人可拦截篡改。
典型攻击案例:跨应用订单事件被未授权订阅
  1. 目标应用(电商APP)与合作应用(物流APP)通过系统公共事件总线传输订单信息,未配置权限:

    // 电商APP发布订单事件(跨应用可见)
    OrderEvent event = new OrderEvent();
    event.orderId = "ORDER123";
    event.receiverAddress = "北京市朝阳区xxx街道"; // 敏感收货地址
    // 未指定发布权限,任何应用可订阅
    SystemEventBus.publishCommonEvent(event);
    
  2. 恶意应用在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); // 上传用户隐私地址
        }
    });
    
  3. 由于事件总线未配置权限控制,恶意应用无需授权即可订阅跨应用订单事件,导致用户收货地址等隐私数据泄露。

防御方案:权限绑定+跨应用事件加密
  1. 为事件总线配置发布/订阅权限
    在系统事件总线或框架中,为敏感事件关联自定义权限(保护级别signaturedangerous),仅授权应用可发布/订阅:

    <!-- 声明跨应用事件的订阅权限(签名级保护) -->
    <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" // 订阅需此权限
    );
    
  2. 跨应用事件全加密传输
    对跨应用传输的事件,使用双方协商的密钥(如通过安全通道交换的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);
            // 处理解密后的事件
        }
    });
    
  3. 限制跨应用事件的可见性
    发布跨应用事件时,通过setPackage指定接收应用包名,避免向非预期应用暴露:

    // 仅允许物流APP(com.logistics.app)接收订单事件
    SystemEventBus.publishCommonEvent(
        event, 
        "com.target.app.permission.SUBSCRIBE_ORDER_EVENT",
        "com.logistics.app" // 目标应用包名
    );
    

二、公共事件的安全测试方法

公共事件的安全测试需覆盖“发布-传输-订阅”全链路,结合静态代码审计与动态攻击模拟,验证数据保护、身份校验、权限控制的有效性。

1. 静态测试:定位事件处理的逻辑漏洞

静态测试通过JADX(反编译APK)或源码审计,识别publishCommonEventsubscribe的使用风险,核心关注数据处理、身份校验、权限配置三个维度。

(1)事件发布逻辑检测
  • 步骤1:定位publishCommonEvent调用点
    在JADX中搜索publishCommonEvent关键字,筛选所有事件发布逻辑,重点关注:

    • 事件中是否包含敏感字段(如usertokencardaddress);
    • 敏感字段是否加密或脱敏(搜索encryptdesensitize等关键字);
    • 发布时是否指定权限或目标应用包名(跨应用场景)。
  • 步骤2:验证发布者权限控制
    检查发布逻辑是否包含权限校验(如checkPermission、自定义权限检查方法),无校验则可能存在恶意发布风险。

(2)事件订阅逻辑检测
  • 步骤1:定位subscribe调用点
    搜索subscribe关键字,筛选所有事件订阅逻辑,重点关注:

    • 订阅的事件类型是否为敏感事件(如登录、支付、订单相关);
    • onEvent回调中是否有发布者身份校验(如校验publisherPackagepublisherSignature);
    • 是否限制事件来源范围(如仅应用内、仅指定包名)。
  • 步骤2:审计事件数据使用逻辑
    检查订阅者是否直接使用事件数据执行敏感操作(如更新登录态、跳转支付),未校验数据完整性(如签名验证)则存在篡改风险。

(3)权限与配置检测
  • 步骤1:检查事件相关权限声明
    在Manifest中搜索与事件相关的权限(如SUBSCRIBE_ORDER_EVENT),验证:

    • 权限是否声明(避免游离权限);
    • 保护级别是否为signaturedangerous(非normal)。
  • 步骤2:分析事件总线配置
    若使用第三方事件总线框架(如自定义EventBus),检查其配置是否:

    • 支持权限绑定;
    • 区分应用内/跨应用事件;
    • 提供加密传输选项。

2. 动态测试:模拟攻击验证防御有效性

动态测试通过开发测试应用或使用工具,模拟事件嗅探、伪造攻击,验证防御逻辑的有效性。

(1)事件嗅探测试(验证数据保护)
  1. 构建测试应用
    • 若为应用内事件:开发同应用模块(或注入测试代码),调用subscribe订阅目标敏感事件;
    • 若为跨应用事件:开发独立测试应用,注册订阅目标事件(若有公开权限则声明)。
  2. 测试步骤
    • 触发目标应用发布敏感事件(如完成一次支付、登录);
    • 查看测试应用是否能接收事件,检查事件数据是否加密/脱敏:
      • 若接收的是明文敏感数据(如完整银行卡号),说明数据保护缺失;
      • 若接收的是加密或脱敏数据,且无法解密/还原,说明数据保护有效。
(2)事件伪造测试(验证身份校验)
  1. 构建测试应用
    • 开发测试模块或应用,构造伪造事件(如LoginSuccessEvent),调用publishCommonEvent发布;
    • 若目标应用的事件有签名验证,伪造事件时使用无效签名。
  2. 测试步骤
    • 确保目标应用已订阅该事件;
    • 发布伪造事件,观察目标应用行为:
      • 若目标应用执行敏感操作(如跳转用户中心),说明未校验发布者身份;
      • 若目标应用拒绝处理(如日志输出“未授权发布者”),说明身份校验有效。
(3)跨应用权限测试(验证权限控制)
  1. 使用ADB或测试应用模拟未授权订阅

    # 模拟跨应用订阅事件(无权限)
    adb shell am broadcast -a com.target.app.event.ORDER_EVENT --es order_id "TEST123"
    
  2. 测试步骤

    • 未声明权限的情况下,尝试订阅跨应用事件;
    • 观察是否能接收事件:
      • 若能接收,说明权限控制缺失;
      • 若返回Permission Denial,说明权限控制有效。

三、总结:公共事件安全的核心原则

公共事件的安全核心是“数据最小暴露+身份严格校验”,所有防御措施需围绕以下原则展开:

  1. 数据传输安全:敏感字段必加密或脱敏,跨应用事件全加密;
  2. 身份双向校验:发布者验证自身权限,订阅者校验发布者身份(包名+签名);
  3. 权限精细控制:为事件绑定signature级权限,跨应用事件限制目标包名;
  4. 范围最小化:优先使用应用内事件总线,非必要不开启跨应用事件;
  5. 持续测试:静态审计事件处理逻辑,动态模拟嗅探与伪造攻击。
源码地址: https://pan.quark.cn/s/d1f41682e390 miyoubiAuto 米游社每日米游币自动化Python脚本(务必使用Python3) 8更新:更换cookie的获取地址 注意:禁止在B站、贴吧、或各大论坛大肆传播! 作者已退游,项目不维护了。 如果有能力的可以pr修复。 小引一波 推荐关注几个非常可爱有趣的女孩! 欢迎B站搜索: @嘉然今天吃什么 @向晚大魔王 @乃琳Queen @贝拉kira 第三方库 食用方法 下载源码 在Global.py中设置米游社Cookie 运行myb.py 本地第一次运行时会自动生产一个文件储存cookie,请勿删除 当前仅支持单个账号! 获取Cookie方法 浏览器无痕模式打开 http://user.mihoyo.com/ ,登录账号 按,打开,找到并点击 按刷新页面,按下图复制 Cookie: How to get mys cookie 当触发时,可尝试按关闭,然后再次刷新页面,最后复制 Cookie。 也可以使用另一种方法: 复制代码 浏览器无痕模式打开 http://user.mihoyo.com/ ,登录账号 按,打开,找到并点击 控制台粘贴代码并运行,获得类似的输出信息 部分即为所需复制的 Cookie,点击确定复制 部署方法--腾讯云函数版(推荐! ) 下载项目源码和压缩包 进入项目文件夹打开命令行执行以下命令 xxxxxxx为通过上面方式或取得米游社cookie 一定要用双引号包裹!! 例如: png 复制返回内容(包括括号) 例如: QQ截图20210505031552.png 登录腾讯云函数官网 选择函数服务-新建-自定义创建 函数名称随意-地区随意-运行环境Python3....
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

介一笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值