在这里插入图片描述
在这里插入图片描述

一、前置思考

1.1 为什么需要沙箱

“为什么我的应用读不到别的应用的文件?”“为什么访问不了系统目录?”——这是鸿蒙开发者最常见的疑问。答案就是应用沙箱

传统移动系统里,应用之间可以互相读数据、互相拉起、共享存储。这种"自由"恰恰是恶意软件滋生的土壤:流氓应用读取通讯录、窃取其他应用缓存、后台偷传数据。

鸿蒙把**“默认隔离”**作为基本原则:每个应用从诞生起就运行在独立沙箱中,对系统资源和其它应用的数据,默认一律拒绝访问。要访问?必须显式授权。这种设计让"隔离"成为默认状态而不是可选项。

1.2 沙箱与"容器"的区别

很多人把沙箱和 Docker 容器类比,但移动沙箱比容器更严格:

维度 沙箱(应用隔离) 容器(服务隔离)
隔离目标 应用进程 + 数据 + 网络 服务运行环境
文件系统 独立根路径 + SELinux 上下文 只读镜像 + 挂载卷
网络 默认断网,需权限 显式端口映射
内核访问 禁止(系统级) 共享宿主内核
逃脱后果 提权攻击 容器逃逸

关键差异:移动沙箱背后是操作系统级强制访问控制(MAC),不是"约定俗成"的隔离,而是策略强制执行——即使开发者在代码里故意尝试越权,系统层面也会拦截。

1.3 本文结构

本文从沙箱的四层隔离模型展开:进程隔离、文件系统隔离、网络访问管控、跨沙箱通信(IPC)控制,最后给出实战 Demo 与避坑清单。

二、核心原理

2.1 沙箱四层隔离模型

┌─────────────────────────────────────────────┐
│            进程隔离                           │
│  独立进程 · 独立内存空间 · 独立句柄表           │
├─────────────────────────────────────────────┤
│            文件系统隔离                       │
│  应用私有目录 · 沙箱根路径 · SELinux 上下文     │
├─────────────────────────────────────────────┤
│            网络访问管控                       │
│  默认断网 · INTERNET 权限 · 网络策略过滤        │
├─────────────────────────────────────────────┤
│            跨沙箱通信控制 (IPC)               │
│  IPC 鉴权 · URI 临时授权 · 权限校验            │
└─────────────────────────────────────────────┘

2.2 进程隔离的底层实现

进程隔离不只是"每个应用一个进程"这么简单,它包含三个层次:

(1)UID 隔离

每个应用分配独立的用户标识(UID)。Linux 权限模型天然保证了:不同 UID 的进程无法互访对方的文件(除非显式授权)、无法 kill 对方、无法读取对方的私有数据。这是最底层的隔离。

(2)地址空间隔离

每个应用进程拥有独立的虚拟地址空间。现代 CPU 的 MMU 硬件保证了进程 A 无法访问进程 B 的物理内存——即使代码存在缺陷,也只能产生本进程内的崩溃,不会直接读取其他应用内存。

(3)能力隔离(Capabilities)

进程只携带完成任务所需的最小能力集。普通应用默认不具备 ptrace 权限、不具备加载内核模块权限、不具备直接访问裸设备权限。这限制了被攻破进程的横向移动能力。

2.3 文件系统隔离与沙箱目录

/data/storage/el2/base/haps/entry/
  ├── files/       # 应用私有文件(仅本应用可读写)
  ├── cache/       # 缓存目录(系统可清理)
  ├── database/    # 数据库目录
  ├── preferences/ # 首选项
  └── temp/        # 临时文件

关键规则:

  • 每个应用只能访问自己的沙箱目录
  • 其他应用的沙箱路径即使"知道路径"也访问不了(SELinux 拒绝)
  • 系统目录(/system、/proc 等)对普通应用完全不可见

SELinux 上下文是文件隔离的真正实现者。每个应用进程被分配独立的 SELinux 安全上下文(如 u:r:app_xxx:s0),目录的标签决定了谁能访问。即使攻击者知道了其他应用的路径并尝试 open(),MAC 策略也会返回 EACCES——路径知道也没用。

2.4 网络访问管控

// 沙箱网络策略示意
class SandboxNetworkPolicy {
  // 应用默认无网络权限
  static requiresInternetPermission(host: string): boolean {
    // 域名白名单 + 权限校验双重控制
    const whitelist: Array<string> = [
      'api.example.com',
      'static.example.com'
    ];
    for (let i: number = 0; i < whitelist.length; i++) {
      if (host.indexOf(whitelist[i]) !== -1) {
        return true;
      }
    }
    return false;
  }
}

网络管控的要点:

  • 默认断网:应用默认没有 INTERNET 权限,申请后才可联网
  • 域名白名单:企业级管控下可限定应用只能访问白名单域名
  • 后台网络限制:后台状态下网络访问受限,防止偷传数据
  • 明文 HTTP 检测:系统可检测并提示明文 HTTP 流量(生产环境应全 HTTPS)

2.5 跨沙箱通信(IPC)

应用之间需要协作时(分享文件、拉起相机),走受控的 IPC:

应用A                               应用B
  │                                   │
  │ 1. 发起 URI 临时授权               │
  │ 2. 系统校验权限/生成授权Token       │
  │ 3. 携带 URI 调用 B 的能力           │
  │ ─────────────────────────────────→ │ 4. 校验授权Token
  │                                    │ 5. 校验通过 → 提供能力
  │                                    │    校验失败 → 拒绝
  │ ←───────────────────────────────── │ 6. 返回结果
  ▼                                   ▼

URI 临时授权是跨沙箱通信的关键机制:授权不是永久的,而是带时效的(如 10 分钟),用后即失效。

2.6 沙箱与能力开放

沙箱不是"铁桶",而是"带门的墙":

能力 开放方式 时效
拉起其他应用 startAbility + 显式导出 单次
分享文件 URI 临时授权 10-30 分钟
访问相册 PhotoViewPicker 单次选择
访问传感器 user_grant 权限 用户可撤销
访问分布式数据 同账号 + 权限校验 持续

所有能力开放都必须走系统通道,不存在"绕过沙箱直连"的合法路径。

三、典型攻击场景还原

3.1 场景一:跨应用数据窃取

攻击者操作:
1. 恶意应用尝试直接读取银行 App 的数据库文件
2. 尝试用已知路径遍历其他应用目录

被拦截的环节:
① 不同 UID 进程无法访问对方私有目录(内核级拒绝)
② SELinux 上下文不匹配,open() 返回 EACCES
③ 即使 root 提权,TEE 内的密钥依然不可访问

3.2 场景二:后台偷传数据

攻击者操作:
1. 应用进入后台后持续上传用户数据
2. 高频访问网络连接

被拦截的环节:
① 后台网络访问受限,非合规任务被系统限制
② 未申请 INTERNET 权限的应用根本不能联网
③ 权限使用被系统审计,用户可见可查

3.3 场景三:伪装系统应用

攻击者操作:
1. 恶意应用伪装成系统设置界面
2. 诱导用户授权敏感权限

被拦截的环节:
① 系统应用有独立的签名与 UID,普通应用无法冒充
② 权限弹窗由系统绘制,恶意应用无法伪造
③ 组件导出控制:未导出的系统能力无法被调用

四、实战落地

对应 Demo 页面:entry/src/main/ets/pages/SandboxIsolationDemo.ets

Demo 包含三个 Tab:

  1. 沙箱目录:展示沙箱根路径,支持一键"探测路径访问权限"——直观演示哪些路径可访问、哪些被拒绝(其他应用目录、系统目录、/proc 一律拒绝)
  2. 资源隔离:列出文件/进程/网络/IPC 的隔离策略与管控状态,可切换演示
  3. 跨沙箱IPC:展示跨应用通信记录,支持模拟"shareFile 分享"并演示 URI 临时授权流程(先校验后授权)
// Demo 核心:沙箱路径探测
private probePaths(): void {
  const paths: string[] = [
    '/data/storage/el2/base/haps/entry/files',   // 可访问
    '/data/storage/el2/base/haps/other_app',      // 其他应用 → 拒绝
    '/system/app/',                               // 系统目录 → 拒绝
    '/proc/kmsg',                                 // 内核信息 → 拒绝
    '/data/storage/el2/distributedfiles/'         // 分布式目录
  ];
  let result: string = '沙箱路径探测结果:\n';
  for (let i: number = 0; i < paths.length; i++) {
    const accessible: boolean = !(paths[i].indexOf('other_app') !== -1 ||
      paths[i].indexOf('/system/') !== -1 || paths[i].indexOf('/proc/kmsg') !== -1);
    result += (accessible ? '✅ 可访问: ' : '❌ 拒绝: ') + paths[i] + '\n';
  }
  this.pathAccessResult = result;
}

4.1 沙箱内的文件读写最佳实践

import { common } from '@kit.AbilityKit';
import { fileIo as fs } from '@kit.CoreFileKit';

// 正确做法:通过 context 获取沙箱路径
export class SandboxFileHelper {
  static getPrivateDir(context: common.UIAbilityContext): string {
    // 获取应用私有目录(files)
    const filesDir: string = context.filesDir;
    // 注意:不要写死路径,不同版本/设备根路径可能变化
    return filesDir;
  }

  static writePrivateFile(context: common.UIAbilityContext, name: string, content: string): void {
    const filePath: string = this.getPrivateDir(context) + '/' + name;
    const file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
    fs.writeSync(file.fd, content);
    fs.closeSync(file);
  }
}

核心原则:永远通过 context.filesDircontext.cacheDircontext.databaseDir 等系统接口获取目录,绝不硬编码路径。

五、避坑速查

现象 原因 解决
路径硬编码 换设备后目录找不到 沙箱根路径随环境变化 用 context 接口获取目录,不写死
无法访问公共相册 MediaLibrary 打开失败 需要 URI 授权 + 用户确认 用 PhotoViewPicker/文件选择器
跨应用分享失败 startAbility 无反应 目标应用未声明可导出 目标应用能力需显式导出 + 权限校验
以为 cache 会永久保存 数据突然没了 cache 目录可被系统清理 重要数据放 files/,不用 cache/
后台想访问网络 请求失败 未申请 INTERNET 或后台限制 申请权限 + 使用后台任务合规接口
直接读写系统路径 权限拒绝崩溃 普通应用无系统目录权限 一切走沙箱目录
URI 授权超时 分享到一半失败 授权 TTL 过短 大文件分享适当延长授权时效
组件未导出却跨应用调用 调用失败 目标能力未开放 需求方走系统能力或申请开放

六、总结

沙箱隔离的设计哲学:

  1. 默认拒绝:零信任起步,一切资源访问都要显式授权
  2. 层次分明:进程/文件/网络/IPC 四层独立管控
  3. 授权有时效:URI 临时授权用后即失效,不留后门

落地建议:

  • 所有文件读写统一走沙箱目录,不碰系统路径
  • 跨应用协作务必走 URI 临时授权,不要尝试直连路径
  • 理解"隔离是特性不是限制",把合规访问做成标准流程

沙箱是鸿蒙安全模型的基石,理解它的边界,才能既保障安全又不牺牲业务能力。开发者应该把沙箱当作"受保护的开发环境"来利用,而不是绕过的对象。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐