4. 权限滥用风险分析:权限申请原则、最小权限检查

权限滥用,说白了一个 App 要了你根本不需要的东西。比如一个手电筒应用,非要读你的通讯录——这不扯吗?我在做 SDK 安全评估的时候,这种案例见得太多了。今天我们就聊聊权限申请的底线在哪,以及怎么用最小权限原则把风险降下来。

核心观点:权限不是越多越好。每多一个权限,攻击面就大一圈。这是我花了几次事故换来的教训。

4.1 权限申请的三条铁律

先问个问题:你开发一个天气 App,需要访问位置权限吗?可能大部分人说“需要,因为要定位城市”。但你再想想——用户手动选城市不就行了?其实很多权限,是我们可以绕过去的。

我给自己定了一个原则,写在下面这个表里:

原则 含义 我踩过的坑
必要性 该权限是核心功能不可替代的? 一个相册扫图功能,我当初用了存储权限,其实用 SAF 框架完全不需要。
最小化 能不能只申请必要子集? 位置权限有精准和粗略之分,我曾直接要了精准,后来发现粗略完全够用。
即用即申请 不要启动就弹窗,要用时才弹。 记得有个游戏启动就要麦克风权限,用户直接给差评。

小提示:每次添加权限前,先问自己“没有这个权限会怎样?”如果功能还能跑,那就别申请。

4.2 最小权限检查——怎么落地?

说完了原则,咱得动真格检查。手动看 Manifest 太累了,我一般用工具辅助。但你得理解背后的逻辑,不然工具扫出来你也不知道该不该修。

第一步:理清权限依赖图

每个 SDK 组件到底用了哪些权限?这不是只看 AndroidManifest 就行的,有时候 Native 代码里偷偷调用了系统 API 获取设备信息。我曾经接手一个第三方登录 SDK,声明只用 INTERNET,结果它自己偷偷读 IMEI——这个只能靠运行时 hook 抓出来。

第二步:对比功能边界

把权限列表拆开:必须的 vs 可选的 vs 冗余的。我习惯画一张权限覆盖图:

避坑指南:我曾经因为一个冗余的“读取短信”权限,整款 App 被应用商店拒审。后来排查发现是某个老旧模块残留的权限声明,合并代码时忘了删。

第三步:自动化扫描

工具层面,我推荐三个层次:

  • 静态扫描:检查 AndroidManifest 声明 + 代码中 API 调用。可以用自定义 lint 规则。
  • 动态监控:运行时抓取真实权限调用。AppTonic 或 Frida 脚本都可以。
  • 隐私沙箱对比:对照厂商隐私政策,看权限是否都写了原因。

下面这段 Python 代码,是我平时快速扫描一个 APK 权限声明的小工具片段:

import zipfile
import xml.etree.ElementTree as ET

def extract_permissions(apk_path):
    with zipfile.ZipFile(apk_path, 'r') as z:
        if 'AndroidManifest.xml' not in z.namelist():
            return []
        # 解压后的 manifest 需先用 AXMLPrinter2 转成可读 XML
        raw = z.read('AndroidManifest.xml')
        root = ET.fromstring(raw)
        ns = {'android': 'http://schemas.android.com/apk/res/android'}
        perms = root.findall('.//uses-permission')
        return [p.get('{http://schemas.android.com/apk/res/android}name') for p in perms]

permissions = extract_permissions('your_app.apk')
print("声明的权限有:", permissions)

嗯,注意这只是一个开头。真正分析时,你还需要把每个权限对应的 API 调用链找出来。

4.3 权限矩阵——一张图管住全局

我项目里习惯维护一张权限矩阵表。每次版本迭代都更新它。下面是一个简化版示例:

权限名称 功能模块 是否必要 是否已申请原因 备注
CAMERA 扫码登录 是 是 仅在扫码页弹窗
ACCESS_FINE_LOCATION 地图导航 可降级为粗略 否 待优化
READ_CONTACTS 邀请好友 否(可选功能) 否 考虑移除

有了这个矩阵,你跟产品经理吵架的时候也有底气。我上次就靠这表说服对方,把两个非必要权限删掉了。

4.4 核心流程图——最小权限决策路径

下面是我自己画的最小权限检查流程图。每次接入新 SDK 或者改权限时,我都会按这个路径走一遍:

最小权限决策与检查流程 功能需求分析 列出候选权限 逐条必要性审查 冗余?移除 必要,保留 子集最小化 运行时时机审查 形成权限矩阵 回退

你看这个图,从需求分析一直走到矩阵形成。红色分支表示冗余权限直接移除,绿色分支保留后还要做子集最小化。我每次改权限,都能在这张图上找到对应环节,不落下。

4.5 实战:一个位置权限的优化案例

以前接手一个天气 App,它申请了 ACCESS_FINE_LOCATION。我一看代码,发现定位只用了一次——用户首次打开时获取城市。之后全在本地缓存里读。那为啥不直接用 ACCESS_COARSE_LOCATION?甚至用 IP 定位就够。我跟产品反馈后,他们同意改成 COARSE,并且只在首次启动时弹窗。用户体验和安全性都上去了。

我的习惯:每次接入新 SDK,我都会先跑一遍静态扫描,然后动态抓一次真实权限调用。对比两张表,差异就是风险点。

记住,权限滥用风险往往不是恶意代码导致的,而是开发者懒、历史包袱、或者沟通不清。你肯多花十分钟画一张权限矩阵,后面就能省下好几天应付审核的时间。我自己的经验告诉我——最小权限不是束缚,是保护你自己的铠甲。