鸿蒙应用安全编码专题系列之UIAbility安全

0 评论 371 浏览 0 收藏 13 分钟

UIAbility组件是鸿蒙应用中包含用户界面(UI)的核心应用组件,主要承担与用户交互的功能,同时也是应用与其他应用进行跨应用交互的核心入口,其配置安全性直接影响应用整体安全边界。

本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看:鸿蒙应用安全编码专题文章汇总 | 华为开发者联盟

一、背景介绍

UIAbility组件是鸿蒙应用中包含用户界面(UI)的核心应用组件,主要承担与用户交互的功能,同时也是应用与其他应用进行跨应用交互的核心入口,其配置安全性直接影响应用整体安全边界。

若UIAbility在注册配置时,将exported属性设置为true,则该组件将处于对外暴露状态,允许其他应用发起交互请求。此时,恶意应用可通过拉起该UIAbility并传入非法参数实施攻击;若该暴露的UIAbility同时配置了deeplink或applink,攻击者还可通过远程链路发起攻击,进一步扩大安全风险。

从应用安全防护视角来看,暴露的UIAbility是鸿蒙应用最核心的安全暴露面,其配置不当或校验缺失,极易成为恶意攻击的突破口。

因此,若UIAbility无需与其他应用进行跨应用交互,应明确将exported属性配置为false

若UIAbility因业务需求必须对外暴露、与其他应用交互,理论上可通过配置permissions权限对其进行访问控制。但由于鸿蒙系统暂不支持应用自定义权限,对于普通应用而言,通常无法通过权限配置实现对暴露UIAbility的有效防护。

基于上述现状,若应用的UIAbility必须对外暴露,且需接收外部应用传入的参数进行业务处理,则必须对传入参数进行严格的合法性校验,防止参数注入等攻击;若应用可明确该暴露UIAbility的合法调用方范围,则需从want对象中获取调用方(拉起方)的身份信息并进行安全校验,确保仅允许合法应用发起调用。

针对调用方身份校验,实际开发中常出现校验方法不规范、校验逻辑不完整的问题,易导致攻击绕过。以下重点分析常见的不安全校验方式,并给出安全合规的实现方案。

二、不安全的校验方式及风险分析

2.1 仅校验调用方包名(校验不完整)

当其他应用拉起本应用的UIAbility时,开发者可从want对象中获取调用方的包名(bundleName),并以此作为唯一校验依据。

参考:docs/zh-cn/application-dev/reference/apis-ability-kit/js-apis-app-ability-want.md-代码预览-docs:基于OpenHarmony生态的开发者文档项目 – AtomGit | GitCode

cke_75968.png

典型不安全校验示例代码如下:

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(DOMAIN, TAG, 'EntryAbility onCreate');
    const callerBundleName1 = want.parameters?.['ohos.aafwk.param.callerBundleName'];
    if (callerBundleName1 === 'com.example.callerapp') {
      // 仅校验包名,无其他校验逻辑
    }
  }

  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    const callerBundleName1 = want.parameters?.['ohos.aafwk.param.callerBundleName'];
    if (callerBundleName1 === 'com.example.callerapp') {
      // 仅校验包名,无其他校验逻辑
    }
  }

该校验方式存在明显安全隐患:尽管在鸿蒙系统中仿冒应用包名的难度较高,但并非完全不可实现。攻击者可通过技术手段仿冒合法应用包名,绕过该校验并发起恶意调用,因此仅校验调用方包名不足以保障安全。

2.2 校验包名+签名,但签名信息从本地读取(分布式场景绕过)

为弥补仅校验包名的不足,部分开发者会结合应用签名信息进行校验,认为包名与签名结合即可实现绝对安全——鸿蒙应用的签名信息包含多个安全字段,具备不可仿冒、不可篡改的特性。

参考:BundleInfo-bundleManager-接口依赖的元素及定义-ArkTS API-Ability Kit(程序框架服务)-应用框架 – 华为HarmonyOS开发者

cke_9411.png

典型不安全校验示例代码如下(从本地读取签名信息):先获取调用方包名,然后调用getBundleInfoSync获取其签名信息,然后进行校验

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(DOMAIN, TAG, 'EntryAbility onCreate');
    const callerBundleName1 = want.parameters?.['ohos.aafwk.param.callerBundleName'];
    if (callerBundleName1 === 'com.example.callerapp') {
      let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_SIGNATURE_INFO;
      let userId = 100;
      try {
        // 从本地获取调用方包名对应的签名信息
        let data = bundleManager.getBundleInfoSync('com.example.callerapp', bundleFlags, userId);
        // 校验签名信息中的appId是否合法
        if (data?.signatureInfo?.appId === '123456789') {
          // 执行后续业务逻辑
        }
      } catch (err) {
        let message = (err as BusinessError).message;
        hilog.error(0x0000, 'testTag', 'getBundleInfoSync failed: %{public}s', message);
      }
    }
  }

核心安全问题

上述代码在本地场景下可实现有效校验,但未考虑鸿蒙系统的分布式特性——鸿蒙分布式服务(Distributed Service Kit)支持多设备登录同一账号并组网后,实现跨设备应用拉起功能(跨端拉起)。

详情参考:Distributed Service Kit简介-Distributed Service Kit(分布式管理服务)-网络-系统 – 华为HarmonyOS开发者

攻击场景示例:假设有设备A和设备B,设备A上部署应用1(采用上述校验方式),攻击者在设备B上仿冒应用2(仅仿冒包名,无法仿冒签名),通过调用getAvailableDeviceListSync接口获取设备A的deviceId,再调用startAbility接口(在want中传入deviceId参数),远程拉起设备A上的应用1。此时,应用1会从本地读取合法应用2的签名信息,而非远程仿冒应用2的签名信息,导致校验绕过,攻击成功。

攻击流程图如下:

cke_745875.png

该攻击可成功的核心原因的是:应用1的校验逻辑未适配分布式场景,仅读取本地设备上目标包名的签名信息,未获取远程拉起场景下调用方的真实签名信息,存在校验遗漏。

三、安全的校验实现方案

解决上述分布式场景校验绕过问题的核心,是获取远程拉起场景下调用方的真实签名信息。鸿蒙系统中,want对象不仅包含调用方的包名信息,还携带调用方的签名相关信息(跨设备拉起时,该签名信息为远程设备上调用方的真实信息,可确保不可篡改)。详见下图。

cke_1565182.png

因此,只需从want对象中同时读取调用方的包名和签名相关信息(如appId),进行联合校验,即可覆盖本地及分布式场景,实现安全校验。

安全校验示例代码如下:

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(DOMAIN, TAG, 'EntryAbility onCreate');
    // 从want中读取调用方包名和签名相关的appId
    const callerBundleName1 = want.parameters?.['ohos.aafwk.param.callerBundleName'];
    const callerAppId = want.parameters?.['ohos.aafwk.param.callerAppId'];

    // 联合校验包名和appId(签名信息)
    if (callerBundleName1 === 'com.example.callerapp' && callerAppId === '123456789') {
      hilog.info(DOMAIN, TAG, 'caller is correct');
      // 执行合法调用后的业务逻辑
    }
  }

四、安全建议

基于上述分析,为保障UIAbility组件的交互安全,结合鸿蒙应用开发场景,提出以下安全编码建议:

  1. 对于无需与其他应用交互的UIAbility,必须显式将exported属性配置为false,杜绝不必要的安全暴露。
  2. 若UIAbility因业务需求必须对外暴露,需对外部传入的所有参数进行充分的合法性校验,防止参数注入、非法数据篡改等攻击。
  3. 若已知UIAbility的合法调用方范围,需对调用方身份进行严格校验,且需满足以下三点要求:
  • 校验逻辑需结合调用方包名与签名信息(如appId),不可仅校验包名;
  • 调用方的包名和签名信息,必须从want对象中直接读取,不可从本地获取(避免分布式场景绕过);
  • 需在onCreateonNewWant两个回调中均实现相同的校验逻辑,确保全场景覆盖。

五、参考文档

 

其他鸿蒙应用安全编码专题文章参考:https://developer.huawei.com/consumer/cn/blog//topic/03207416677214221

更多精彩内容,请关注人人都是产品经理微信公众号或下载App
评论
评论请登录
  1. 目前还没评论,等你发挥!