Android风险管理测试并不是在项目结束后补一轮安全扫描,而是要在需求评审、编码和测试执行阶段持续识别并验证风险控制措施是否有效。一个典型的风险闭环包括四个动作:找出应用可能被利用或出错的路径、给这些路径定级、用可重复的测试手段验证、最后通过发布门禁阻止未处理的高风险项上线。下面的内容围绕这四步展开,并给出可以直接用于项目的检查命令和脚本。

一、从组件导出面和权限组合识别风险
风险识别最直接的入口是AndroidManifest.xml。如果Activity、Service或BroadcastReceiver被声明为android:exported="true",又没有自定义权限或调用方校验,外部应用就可以通过显式Intent拉起这些组件。特别是在Android 12及更高版本中,带<intent-filter>的组件必须显式声明android:exported,很多旧项目在升级targetSdk时只是机械补上true,反而扩大了攻击面。测试时可以用adb命令列出所有导出组件,再逐个对照业务需求判断是否真的需要对外暴露。
<activity
android:name=".ui.SettingsActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
</intent-filter>
</activity>
权限组合风险往往被单条权限检查忽略。例如READ_CONTACTS本身可能用于邀请好友,但如果应用同时申请了INTERNET权限,并且没有对通讯录数据做传输加密,就构成一条完整的数据外泄链路。测试时应该把权限按功能分组,建立“权限-数据-出口”三元组。静态分析可以查看权限声明和网络调用点,动态测试则可在授权后抓包确认是否有明文上传通讯录、定位或剪贴板的行为。Lint自带的ProtectedPermissions和CustomPermissionTypo等检查能覆盖一部分,但更关键的是人工确认权限申请文案与实际用途一致,避免隐私合规风险。
对于已经上架的应用,还可以结合Android自带的权限使用记录和隐私仪表盘来倒推风险。测试时使用adb shell dumpsys package com.ipipp.app可以拿到请求的权限列表和签名信息,结合真实用户授权后的行为日志,能发现一些只在特定条件下触发的敏感操作。把这些风险点写入风险登记册后,需要给每个风险分配初始等级,为后续测试优先级提供依据。
adb shell dumpsys package com.ipipp.app | grep -A 20 "requested permissions"
二、用静态审计和动态插桩覆盖高频风险路径
静态审计擅长发现结构性问题,例如WebView开启了addJavascriptInterface却没有限制加载的页面来源,或者SharedPreferences中存储了密码、Token等敏感信息。MobSF这类开源工具能对APK进行签名分析、组件导出检查、危险权限识别和URL提取,适合在测试环境快速生成基线报告。如果不想依赖外部服务,可以使用命令进行本地扫描,也可以把MobSF API接入CI,在每次合并请求时自动产生风险报告。
import requests
def scan_apk(file_path, mobsf_url='http://127.0.0.1:8000'):
with open(file_path, 'rb') as f:
files = {'file': f}
resp = requests.post(mobsf_url + '/api/v1/scan', files=files)
return resp.json()
report = scan_apk('release/app-release.apk')
print(report.get('hash', 'scan failed'))
动态插桩则能覆盖静态分析难以确认的运行时行为。以WebView为例,很多应用会注册JavaScript接口供H5调用,但如果H5页面通过HTTP加载,或允许用户点击外部链接后继续在WebView中打开,就可能被中间人注入恶意脚本。Frida可以hook WebView的loadUrl和addJavascriptInterface方法,在运行期记录实际URL和接口名。下面这段脚本会在目标应用加载file://或http://地址时输出告警,并打印暴露给JavaScript的原生方法名。
Java.perform(function () {
var WebView = Java.use('android.webkit.WebView');
WebView.loadUrl.overload('java.lang.String').implementation = function (url) {
if (url.indexOf('file://') === 0 || url.indexOf('http://') === 0) {
console.log('Risk URL: ' + url);
}
return this.loadUrl(url);
};
WebView.addJavascriptInterface.overload('java.lang.Object', 'java.lang.String').implementation = function (obj, name) {
console.log('Exposed JS interface: ' + name);
return this.addJavascriptInterface(obj, name);
};
});
数据存储风险同样需要动静结合。SharedPreferences默认存放在应用私有目录,但如果应用开启了allowBackup="true",攻击者或恶意软件可能通过adb backup获取备份文件,进而恢复出明文数据。测试时可以先用静态扫描确认allowBackup状态,再在真机上执行备份命令验证。对于需要本地缓存的敏感信息,建议改用Android Keystore配合加密存储,或者至少在风险评估中指出备份文件可能泄露Token的残余风险,并要求关闭备份或使用密钥排除规则。
<application
android:allowBackup="false"
android:fullBackupContent="@xml/backup_rules">
</application>
三、风险矩阵、测试用例与发布门禁
风险识别完成后,如果没有等级划分,测试资源很容易被低风险项消耗殆尽。实践中常用风险矩阵给每个风险打分,维度包括发生概率、影响范围和可检测性。比如一个导出组件没有权限保护,但只需要本地物理接触才能触发,影响范围小,等级可能为中;而一个未校验协议的WebView可以远程加载任意页面,影响用户数据,等级就是高。将风险按分值排序后,测试用例就能优先覆盖高风险项,每个用例都标出对应的风险编号和验证方法。
| 风险编号 | 风险描述 | 可能性 | 影响 | 等级 |
|---|---|---|---|---|
| R-01 | 导出Activity无权限保护 | 中 | 中 | 中 |
| R-02 | WebView加载http链接 | 高 | 高 | 高 |
| R-03 | 备份文件包含Token | 中 | 高 | 高 |
持续集成阶段的发布门禁是风险管理落地的关键一步。可以在GitLab CI、Jenkins或GitHub Actions中增加风险测试任务,执行Lint、MobSF扫描和自定义Frida脚本。只有高风险项全部关闭或降级为中风险并经过审批后,才允许合并到发布分支。下面是一个GitLab CI的示例任务,它先运行Lint静态检查,再调用Python脚本对比扫描结果中的高风险规则,任何失败都会阻止流水线通过。
stages:
- test
- release
risk_scan:
stage: test
script:
- ./gradlew lintDebug
- python3 mobsf_scan.py --threshold HIGH
- python3 frida_webview_check.py
allow_failure: false
release_build:
stage: release
script:
- ./gradlew assembleRelease
only:
- main
门禁不是要消灭所有风险,而是让风险显性化、可追踪。对于残余的低风险项,可以用运行时告警、服务端风控、隐私政策披露等方式缓解。测试团队需要定期回顾风险登记册,把线上事故、用户投诉和系统更新触发的变更追加进去,形成动态闭环。这样Android风险管理测试就从一次性动作变成版本质量体系的一部分,既能避免过度设计,也能在发布速度和安全要求之间找到平衡。
Android风险管理移动安全测试风险控制修改时间:2026-09-25 23:38:33