iOS 应用程序安全清单
阶段 0:治理、环境和 CI/CD
实施 CI 门控 (CI gates)
- 在每次 PR 上运行静态检查:swiftlint(或同等工具)、单元/UI 测试、依赖项审计。
- 在出现高/关键 SAST/SCA/DAST 漏洞时阻止发布;在发布分支上保证零 lint 错误。
机密处理
- 确保源代码、Info.plist、故事板或日志中没有机密。
- 使用环境/机密管理器来管理机密。
- 在分析 (analytics) 中隐去机密。
- 定期以及在怀疑泄漏时轮换密钥/令牌。
证据和可追溯性
- 将扫描报告与每个制品一起归档。
- 将 SBOM 与每个制品一起归档。
- 将构建日志与每个制品一起归档。
- 将发布说明与每个制品一起归档。
扫描自动化
- 在 CI/CD 中集成移动 SAST/DAST/SCA 平台,在每个 PR 和预发布中运行(例如,Ostorlab)。
- 当出现来自 SAST/DAST/SCA 的高/关键问题时,自动使 CI 门控失败。
阶段 1:威胁建模 (Threat modeling)、数据分类和清单
- 对数据(PII、凭据、令牌、支付)进行分类,并映射收集、处理、存储和传输。
- 识别信任边界(设备、Keychain/文件、URLSession、WebView、到本地库的平台通道)。
- 清点第三方 SDK;首选维护良好、已签名、权限最小的 SDK;尽量减少数量。
阶段 2:构建和配置强化 (Xcode + Info.plist)
Xcode 发布设置 (Release settings)
- 剥离符号和死代码 (dead code)。
- 启用全模块优化 (whole-module optimization)。
- 在发布版本中禁用 DEBUG 标志。
- 从发布版本中排除测试/调试框架。
- 从发布版本中删除开发端点和功能标志 (feature flags)。
代码签名和授权 (entitlements)
- 仅授予必要的授权(没有未使用的 App Groups、Keychain Sharing、NFC、Bluetooth 等)。
- 使用带有专用发布配置文件的自动签名。
- 保护签名凭据(例如,CI 机密存储、访问控制)。
应用程序传输安全 (App Transport Security - ATS)
- 设置
NSAllowsArbitraryLoads = false。 - 仅首选 TLS 1.2+。
- 仅在必要时且在限定时间内使用严格的
NSExceptionDomains。
Info.plist 卫生
- 除非需要,否则禁用文档共享:
-
UIFileSharingEnabled = false -
LSSupportsOpeningDocumentsInPlace = false - 最小化
LSApplicationQueriesSchemes;首选 Universal Links 而不是自定义方案。 - 为所有权限提供准确的
NS[AppData/AppleEvents/SystemAdministration]UsageDescription字符串。
阶段 3:本地存储和 Keychain 安全
Keychain 使用
- 使用尽可能最强的类将令牌/机密存储在 Keychain 中:
-
kSecAttrAccessibleWhenUnlockedThisDeviceOnly - 或适用情况下的
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly。 - 在 UX 允许的情况下,使用
kSecAccessControl标志(例如,biometryCurrentSet、userPresence)控制敏感项。 - 注销时删除 Keychain 项。
- 考虑对高风险令牌进行设备绑定 (device-binding)。
文件保护和数据库
- 对文件应用数据保护:
NSFileProtectionComplete(默认属性或按文件)。 - 加密敏感数据库/文件(例如 SQLCipher 或等效项)。
- 绝不以明文形式存储机密。
缓存和派生数据
- 避免将 PII 持久化在缓存/临时文件中。
- 根据需要在注销时清除敏感的缓存/临时数据。
- 根据需要在后台清除敏感的缓存/临时数据。
- 不要将机密写入
UserDefaults。
阶段 4:隐私保障(UI、剪贴板、屏幕截图)
- 在退入后台时隐去敏感的 UI(在
sceneWillResignActive/applicationWillResignActive中模糊或使用占位符)。 - 检测屏幕捕获(
UIScreen.main.isCaptured)并针对高度敏感的视图调整行为。 - 避免在应用程序启动时读取剪贴板。
- 绝不将机密放在
UIPasteboard.general上。
阶段 5:网络安全和 TLS/固定 (pinning)
URLSession/ATS
- 强制对受信任主机使用 HTTPS。
- 拒绝无效的证书和主机名。
- 禁用 HTTP 回退 (fallbacks)。
- 设置严格的超时。
- 在适当的情况下实施带有抖动 (jitter) 的重试。
- 在 TLS 错误时采用故障关闭 (fail closed)。
- 强制在服务器 TLS 设置上使用 TLS 1.2+。
证书固定 (Certificate pinning)
- 优先使用 SPKI SHA-256 固定。
- 为每个主机包含至少一个备用固定。
- 通过
URLSessionDelegate信任评估实施固定。 - 验证针对 MITM 尝试的行为。
- 验证过期/轮换的叶子证书的行为。
- 计划安全的固定轮换。
- 包含有关固定失败的遥测数据。
Cookies 和令牌
- 对 Web 流程使用安全、HttpOnly、SameSite 的 cookies。
- 避免在设备上使用长期不记名令牌 (bearer tokens)。
- 在敏感会话后清除 cookies 和 Web 数据。
阶段 6:身份验证、授权和会话
身份验证流程
- 为公共客户端使用带有 PKCE 的 OAuth2/OIDC。
- 绝不能在应用程序中嵌入客户端机密。
- 首选系统浏览器(
ASWebAuthenticationSession/SafariViewController)而不是 WebView 进行身份验证。
令牌
- 使用短期访问令牌 (access tokens)。
- 将刷新令牌 (refresh tokens) 存储在 Keychain 中。
- 在注销时轮换并使令牌失效。
- 在更换设备时轮换并使令牌失效。
- 在适当的情况下将会话绑定到设备风险 (device risk)。
- 检测异常(地理位置/IP/ASN 更改)并做出响应。
授权
- 在服务器上强制执行授权;将客户端检查视为建议性参考。
- 验证是否存在 IDOR/BOLA 漏洞。
- 验证是否存在特权升级问题。
阶段 7:WebView 强化 (WKWebView)
- 仅在需要时使用
WKWebView。 - 首选应用程序绑定的域以实现隔离(
WKAppBoundDomains)。
默认值
- 除非必要,否则禁用 JavaScript。
- 禁用
javaScriptCanOpenWindowsAutomatically。 - 通过
WKNavigationDelegate设置导航允许列表。 - 拦截
file://URLs。 - 拦截
javascript:URLs。 - 为敏感会话使用非持久性数据存储。
- 在注销时清除
HTTPCookieStore和网站数据。 - 确保在发布版本中禁用 Web Inspector(iOS 16+
isInspectable = false)。
内容安全
- 对于第一方 Web 内容,强制执行 CSP。
- 强制执行 HTTPS。
- 不允许混合内容。
阶段 8:应用间通信和深层链接 (deep links)
- 首选 Universal Links。
- 验证传入链接的来源和参数。
如果使用自定义 URL 方案
- 选择一个独特的方案。
- 验证所有参数。
- 防御 URL 劫持。
- 防御开放重定向。
- 将
LSApplicationQueriesSchemes限制为已知且必要的方案。
阶段 9:逆向工程和防篡改能力 (tampering)
减少静态泄露
- 从发布到商店的二进制文件中剥离符号。
- 在服务器端存储 dSYM 以进行崩溃符号化。
- 删除发布版本中的详细日志。
- 删除发布版本中的调试菜单。
- 删除发布版本中的测试端点。
- 删除不打算用于生产环境的功能切换 (feature toggles)。
环境检查
- 记录有关越狱存在(文件、系统调用、可疑库)的信号。
- 记录有关钩子框架的信号。
- 记录有关调试器存在的信号。
- 按比例响应(降级/阻止/遥测),而不完全依赖于客户端检查。
篡改测试 (Tamper testing)
- 验证重新打包的构建是否在完整性检查或服务器证明中失败。
- 验证重新签名的构建是否在完整性检查或服务器证明中失败。
- 将客户端检查视为信号,绝不能作为唯一的安全控制手段。
阶段 10:密码学和随机性
- 使用平台加密(
CryptoKit/CommonCrypto);不要实现自定义加密。 - 对认证加密使用 AES-GCM 或 ChaCha20-Poly1305。
- 使用 HKDF/PBKDF2 作为 KDF,配合适当的盐和充分的参数。
- 使用
SecRandomCopyBytes生成密钥/IVs。 - 绝不重复使用 IV/随机数 (nonces)。
- 将加密密钥存储在 Keychain 中。
阶段 11:权限、通知和标识符
- 仅请求所需的最低权限。
- 在使用的地点请求权限。
- 请求权限时,向用户清楚地解释价值主张。
ATT (应用跟踪透明度)
- 仅在必要时请求 ATT。
- 如果 ATT 被拒绝,则优雅地 (gracefully) 降级。
- 避免指纹识别 (fingerprinting) 和类似的禁止追踪技术。
标识符
- 首选
identifierForVendor。 - 除非已授予 ATT,否则避免使用 IDFA。
- 绝不使用禁止的持久性标识符。
阶段 12:日志记录、分析 (analytics) 和崩溃报告
- 使用带有隐私注释的
os_log。 - 禁用发布版本中的详细日志。
- 从日志中隐去 PII。
- 从日志中隐去机密。
- 从分析事件中隐去 PII 和机密。
- 从崩溃报告中隐去 PII 和机密。
- 尽可能对分析数据进行采样和聚合。
- 确保在服务器端发生崩溃符号化。
- 绝不将符号与应用程序一起发送。
阶段 13:测试、DAST 和运行时验证
流量拦截测试
- 验证对具有无效证书的 MITM 的抵抗力。
- 验证对主机名不匹配的 MITM 的抵抗力。
- 验证对不受信任的 CA 的 MITM 的抵抗力。
- 确认在实施了固定的地方,固定能够阻止拦截。
运行时检查
- 确认日志中没有机密/PII。
- 确认 Keychain 项以正确的 ACL(访问控制列表)存在。
- 确认文件具有
NSFileProtectionComplete(或在需要时更严格)。
API 滥用测试
- 测试速率限制 (rate limiting)。
- 测试重放保护 (replay protections)。
- 测试分页边界。
- 测试批量赋值 (mass assignment) 漏洞。
- 测试详细的错误泄露。
动态分析
- 运行移动 DAST,同时执行核心流程以发现传输错误配置。
- 使用 DAST 发现运行时问题(例如,使用 Ostorlab)。
- 导出证据用于分类和重新测试。
阶段 14:SBOM 和证据
SBOM 和依赖项
- 为 Swift/Objective-C 代码生成 SBOM。
- 为嵌入的 SDK 生成 SBOM。
- 跟踪影响依赖项的 CVE。
- 根据风险定期更新依赖项。
证据包
- 将扫描工件附加到发布版本中。
- 将 SBOM 附加到发布版本中。
- 将 Info.plist 差异附加到发布版本中。
- 将授权 (entitlements) 列表附加到发布版本中。
- 将测试报告附加到发布版本中。
阶段 15:报告和标准对齐
报告
- 制作执行摘要。
- 记录范围和方法。
- 记录包含 PoC 和证据的详细发现。
- 为发现分配风险评级。
- 提供修复指导。
- 包含重新测试结果。
OWASP MASVS 映射
- PLATFORM:阶段 2、7–8、11。
- STORAGE:阶段 3–4。
- CRYPTO:阶段 10。
- AUTH:阶段 6。
- NETWORK:阶段 5。
- CODE:阶段 2、9、12。
- RESILIENCE:阶段 9。
从业人员快速参考
- Info.plist/ATS:没有任意加载;严格的异常域;除非需要,否则禁用文档/文件共享。
- Keychain:
ThisDeviceOnly类;可选的biometryCurrentSet/userPresence;注销时删除机密。 - 文件:
NSFileProtectionComplete;加密数据库/文件;避免将UserDefaults用于机密。 - Network:仅 HTTPS;
URLSessionDelegate固定(带有备份的 SPKI SHA-256);验证 MITM 尝试失败。 - Auth:OIDC + PKCE;短期令牌;Keychain 中的刷新令牌;服务器端授权;测试 IDOR/BOLA。
- WebView:首选系统浏览器进行身份验证;如果是
WKWebView,默认关闭 JS,导航允许列表,应用程序绑定的域,敏感流程的非持久存储。 - 隐私:屏蔽后台快照;避免剪贴板机密;最小化权限并保持准确的使用字符串。
- 发布卫生:剥离应用程序中的符号,保持 dSYM 私密;删除调试日志/菜单;验证授权 (entitlements)。
示例配置和代码片段
ATS (Info.plist)
- 配置
NSAppTransportSecurity: -
NSAllowsArbitraryLoads = false -
NSExceptionDomains = { yourdomain.com: { NSTemporaryExceptionAllowsInsecureHTTPLoads = false, NSIncludesSubdomains = true, NSTemporaryExceptionMinimumTLSVersion = TLSv1.2 } }
文件保护 (Swift)
- 使用完整的文件保护:
-
try data.write(to: url, options: .completeFileProtection)
Keychain 项(Swift 概要)
- 配置 Keychain 项:
-
kSecClass: kSecClassGenericPassword -
kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly -
kSecAttrAccessControl: SecAccessControlCreateWithFlags(..., [.biometryCurrentSet, .userPresence])
URLSession 固定(大纲)
- 在
urlSession(_:didReceive:completionHandler:)中: - 评估
SecTrust。 - 从证书中提取 SPKI。
- 将 SPKI 的 SHA-256 与固定允许列表进行比较。
- 适当地选择
.useCredential或.cancel。
轻量级工作流程
合并前门控 (Pre-merge gate)
- 运行 lint 和测试。
- 运行依赖项审计。
- 触发 SAST/SCA 扫描。
- 在出现高/关键问题时阻止合并。
预发布阶段 (Pre-release staging)
- 验证 Info.plist 和授权 (entitlements)。
- 构建发布二进制文件。
- 确认符号没有随应用程序一起发送。
- 在执行核心用户流程时运行 DAST(例如,使用 Ostorlab)以验证 TLS/固定、存储保护和日志记录卫生。
- 修复问题并重新测试。
发布后监控 (Post-release monitoring)
- 跟踪崩溃和性能。
- 按照定义的计划轮换密钥/令牌。
- 监控 SDK 建议和 CVE。
- 保持 SCA 监视第三方 SDK/库(例如 Ostorlab)以针对新漏洞发出警报。
- 在发现新漏洞时触发有针对性的重新测试。