Android 应用程序安全清单
阶段 0:治理、环境和 CI/CD
实施 CI 门控 (CI gates)
- 在每次 PR 上运行静态检查:ktlint / Android Lint、单元/UI 测试、依赖项审计(Gradle、SBOM)。
- 在出现高/关键 SAST/SCA/DAST 漏洞时阻止发布;在发布分支上保证零 lint 错误。
机密处理
- 源代码、BuildConfig、AndroidManifest.xml、资源或日志中没有机密。
- 使用环境/机密管理器(例如,Gradle 机密、云 KMS);在分析 (analytics) 中隐去机密。
- 定期以及在怀疑泄漏时轮换密钥/令牌。
证据和可追溯性
- 归档每个制品的扫描报告、SBOM、构建日志和发布说明。
扫描自动化
- 在 CI/CD 中集成移动 SAST/DAST/SCA 平台,在每个 PR 和预发布中运行,并在发现高/关键问题时自动使门控失败。
阶段 1:威胁建模 (Threat modeling)、数据分类和清单
- 对数据(PII、凭据、令牌、支付)进行分类,并映射收集、处理、存储(SharedPreferences、文件、数据库、Keystore)和传输。
- 识别信任边界(设备、应用程序沙箱、Keystore、IPC/Binder、WebView、本地库、后端 API)。
- 清点第三方 SDK;首选维护良好、已签名、权限最小的 SDK;尽量减少数量。
阶段 2:构建和配置强化 (Gradle + Manifest)
Gradle/发布设置 (Release settings)
- 使用带有代码缩减/混淆的发布版本(
minifyEnabled true、shrinkResources true)。 - 从已发布 APK/AAB 中剥离调试符号。
- 将映射文件保留在服务器端。
- 从发布版本中排除测试/调试库。
- 从生产构建中删除开发端点和功能标志 (feature flags)。
签名和权限
- 保护签名密钥(硬件支持的密钥库、受限访问)。
- 使用专用的发布签名配置。
- 仅在
AndroidManifest.xml中声明必要的权限。 - 避免过度危险的权限 (dangerous permissions)。
网络安全配置
- 使用
networkSecurityConfig实施 TLS。 - 不允许明文流量(
android:usesCleartextTraffic="false")。 - 仅在必要时并在限定时间内使用域配置覆盖。
Manifest 卫生
- 除非严格要求,否则不要设置组件
exported="true";导出时使用权限进行限制。 - 删除调试标志(发布版本中
android:debuggable="false",除非需要并带有适当的加密,否则不使用allowBackup="true")。 - 为所有运行时权限 (runtime permissions) 提供准确的权限理由说明文本(在应用程序内)。
阶段 3:本地存储和 Keystore 安全
Keystore 使用
- 将长效密钥存储在 Android Keystore 中,并提供最强可行的保护(如果有的话采用硬件支持,如果 UX 允许则采用用户身份验证)。
- 对高度敏感的密钥使用
setUserAuthenticationRequired(true);在适当的情况下利用生物识别/锁屏来控制操作。 - 在注销或更换设备时删除被 Keystore 包装的机密。
- 考虑对高风险令牌进行设备绑定 (device-binding)。
文件、SharedPreferences 和数据库
- 避免将机密或敏感的 PII 以明文形式存储在文件或
SharedPreferences中。 - 加密敏感数据库/文件(例如 SQLCipher 或等效的文件加密)。
- 使用应用程序私有存储(
MODE_PRIVATE)。 - 在受支持的地方应用文件保护标志。
- 确保备份不包含未加密的机密。
缓存和派生数据
- 避免将 PII 持久化在缓存/临时文件中。
- 在注销时以及作为后台处理的一部分(如果合适)清除缓存/临时文件。
- 即使是暂时的,也不要将机密写入日志或
SharedPreferences。
阶段 4:隐私保障(UI、剪贴板、屏幕截图)
- 在应用程序切换器屏幕截图中隐去敏感的 UI(例如,在敏感的 Activities/Views 上使用
FLAG_SECURE)。 - 对于高度敏感的流程,请考虑额外的屏蔽或单独的“安全模式” Activities。
- 避免在应用程序启动时读取剪贴板;绝不将密码/长期令牌放在剪贴板上。
阶段 5:网络安全和 TLS/固定 (pinning)
HTTPS/TLS 实施
- 强制对受信任主机的 HTTPS;拒绝无效的证书/主机名;禁用 HTTP 回退 (fallbacks)。
- 配置严格的超时、带抖动 (jitter) 的指数退避,并在 TLS 错误时采取故障关闭 (fail closed)。
证书固定 (Certificate pinning)
- 优先使用 SPKI SHA-256 固定;为每个主机包含至少一个备用固定。
- 通过 OkHttp / 自定义
TrustManager或networkSecurityConfig实施固定。 - 根据 MITM 场景和过期/轮换的叶子证书进行验证。
- 计划安全的固定轮换;在固定失败时包含遥测。
Cookies 和令牌
- 对于 Web 流程,使用安全、HttpOnly、SameSite cookies。
- 避免设备上的长期不记名令牌 (bearer tokens)。
- 在敏感会话后清除 cookies 和 WebView 数据。
阶段 6:身份验证、授权和会话
身份验证流程
- 为公共客户端使用带有 PKCE 的 OAuth2/OIDC;绝不能在应用程序中嵌入客户端机密。
- 首选基于系统/浏览器的身份验证(Chrome Custom Tabs / 默认浏览器)进行登录,而不是应用程序内的 WebView。
令牌
- 使用短期访问令牌 (access tokens)。
- 仅将刷新令牌 (refresh tokens) 存储在绑定到 Keystore 密钥的加密存储中。
- 在注销、更换设备和出现可疑活动时轮换并使令牌失效。
- 在适当的情况下将会话绑定到设备风险 (device risk)。
授权
- 在服务器端实施授权;将客户端检查仅视为建议性参考。
- 跨应用程序使用的所有后端 API 测试并预防 IDOR/BOLA 漏洞和特权升级。
阶段 7:WebView 强化
- 仅在需要时使用 WebView;尽可能首选浏览器进行身份验证和付款。
默认值
- 除非严格必要,否则禁用 JavaScript。
- 如果启用 JavaScript,请限制功能并禁用
setAllowFileAccessFromFileURLs和setAllowUniversalAccessFromFileURLs。 - 不要在暴露敏感方法的对象上启用
addJavascriptInterface,特别是在 API < 17 上。 - 强制执行导航允许列表(主机/路径);拦截
file://、javascript:和不受信任的方案。 - 尽可能为敏感会话使用非持久性 WebView 存储。
- 在注销时清除 cookies、缓存和历史记录。
内容安全
- 仅通过 HTTPS 提供第一方 Web 内容。
- 不允许混合内容(
MIXED_CONTENT_NEVER_ALLOW)。 - 确保远程内容的完整性经过检查和控制(除非严格必要,否则不使用不受信任的第三方脚本)。
阶段 8:应用间通信和深层链接 (deep links)
- 首选 App Links(已验证的链接)以进行深层链接;验证传入链接的来源和参数。
自定义方案和意图 (intents)
- 如果使用自定义方案或隐式意图,请确保唯一性并对所有传入数据进行可靠的验证。
- 使用权限或显式意图保护导出的 Activities/Services/BroadcastReceivers;避免过于宽泛的意图过滤器。
- 通过检查调用者身份和参数,防御意图欺骗 (intent spoofing)、URL 劫持和开放重定向。
阶段 9:逆向工程和防篡改能力 (tampering)
减少静态泄露
- 从发布构建中删除调试构建、详细日志、调试菜单和测试端点。
- 混淆和缩减代码(ProGuard/R8)和资源。
- 避免在资源或本地库中留下有意义的字符串(API 密钥、机密)。
环境检查
- 检测 root/钩子/调试器指示器(常见 root 路径、可写的系统目录、检测框架)并记录信号。
- 在维持 UX 的同时,根据风险按比例响应(减少功能、附加验证或阻止)。
篡改测试 (Tamper testing)
- 确保重新打包或重新签名的应用程序在完整性检查或服务器证明(例如,安全/证明 API)中失败。
- 将客户端信号视为后端的提示;绝不能作为唯一的安全控制手段。
阶段 10:密码学和随机性
- 使用平台提供的加密原语(Java Cryptography Architecture、Android Keystore);不要构建自定义算法。
- 对认证加密使用现代 AEAD 模式,例如 AES-GCM/ChaCha20-Poly1305。
- 使用成熟的密钥派生函数 (KDF)(PBKDF2/HKDF),配合适当的参数和盐 (salts)。
- 使用安全的 RNG(
SecureRandom或平台 API)生成密钥/初始化向量 (IVs)。 - 绝不重复使用 IV/随机数 (nonces)。
- 将密钥保留在 Keystore 或处于静态加密状态。
阶段 11:权限、通知和标识符
- 要求最小权限,在使用的地点,并提供清晰的对用户有价值的情境说明。
隐私相关权限
- 将对位置、联系人、摄像头、麦克风、存储和电话状态的访问限制为严格必要。
- 优雅地处理拒绝和撤销。
- 避免对用户施加压力的黑暗模式 (dark patterns)。
标识符和追踪
- 首选应用程序范围或可重置的标识符;避免建立跨应用程序的持久指纹识别 (fingerprinting)。
- 仅根据平台策略使用广告或分析 ID。
- 绝不使用禁止的硬件标识符进行跟踪。
通知
- 最大程度减少通知内容中的敏感数据。
- 尽可能使用通用措辞并在锁定屏幕上隐藏详细信息。
阶段 12:日志记录、分析 (analytics) 和崩溃报告
- 使用带严重级别的结构化日志记录;在发布版本中禁用详细/调试日志记录。
- 从日志、分析事件和崩溃报告中隐去 PII 和机密。
- 尽可能聚合和采样分析数据。
- 确保混淆构建的符号/映射文件安全地存储在服务器端,以用于解除崩溃混淆。
- 不要将符号/映射文件发送在应用程序内。
阶段 13:测试、DAST 和运行时验证
流量拦截测试
- 验证对 MITM(无效证书、主机名不匹配、不受信任的 CA)的抵抗力。
- 验证在配置了固定 (pinning) 的地方可以阻止拦截。
运行时检查
- 确认没有机密/PII 出现在日志中。
- 验证敏感数据在静态时已加密。
- 审查运行时的 WebView 行为(导航、JS、存储)。
- 审查运行时的本地存储行为(文件、SharedPreferences、数据库)。
- 验证在预期的地方使用了由 Keystore 支持的密钥。
- 验证 Keystore 密钥的访问控制行为是否符合设计。
API 滥用测试
- 测试速率限制 (rate limiting)。
- 测试重放保护 (replay protection)。
- 测试分页边界。
- 测试批量赋值 (mass assignment)。
- 测试后端 API 的错误消息卫生。
动态分析
- 运行移动 DAST,同时执行核心流程以发现传输错误配置、WebView 问题、存储泄漏和日志记录问题。
- 导出 DAST 的证据用于分类和重新测试。
阶段 14:SBOM 和证据
SBOM 和依赖项
- 为所有 Gradle 模块和嵌入式 SDK 生成 SBOM。
- 跟踪 CVE 并定期更新依赖项。
- 将监控依赖项运行状况作为发布就绪状态的一部分。
- 将监控许可证合规性作为发布就绪状态的一部分。
证据包
- 将扫描工件附加到发布记录。
- 将 SBOM 附加到发布记录。
- 将 manifest 差异附加到发布记录。
- 将权限和组件导出列表附加到发布记录。
- 将自动化/手动测试报告附加到发布记录。
阶段 15:报告和标准对齐
报告
- 提供执行摘要、范围/方法、带有 PoC/证据的详细发现结果、风险评级、修复指导和重新测试结果。
标准映射(示例)
- ARCH:阶段 1–2
- PLATFORM:阶段 2、7–8、11
- STORAGE:阶段 3–4
- CRYPTO:阶段 10
- AUTH:阶段 6
- NETWORK:阶段 5
- CODE:阶段 2、9、12
- RESILIENCE:阶段 9
从业人员快速参考
- Manifest/Network:发布中没有明文流量;严格的
networkSecurityConfig域;没有强有力的理由避免导出组件。 - 机密和存储:使用 Android Keystore 获取长效密钥;加密数据库/文件;绝不将机密存储在
SharedPreferences或日志中。 - Network:仅 HTTPS;针对关键端点具备备份功能的证书/SPKI 固定;验证 MITM 尝试失败。
- Auth:OIDC + PKCE;短期访问令牌;将刷新令牌保留在加密存储中;服务器端授权;测试 IDOR/BOLA。
- WebView:首选浏览器进行身份验证;如果使用 WebView,默认关闭 JS,限制界面,列入导航允许列表,敏感流程使用非持久存储。
- 隐私:为敏感屏幕使用
FLAG_SECURE;避免剪贴板机密;最小化权限并提供清晰的解释。 - 发布卫生:混淆和缩减;删除调试日志/菜单/测试端点;每次发布时验证权限和导出的组件。