3. 常见隐私泄露场景:明文传输风险、日志记录残留、缓存数据泄露、第三方SDK外发
3.1 现象识别——四类泄露的场景画像
OkHttp 接口的隐私泄露并非单一模式,而是分布在 传输链路、本地持久化、遗留痕迹、跨进程外发 四个维度。下表提供了第一现场的快速识别对照:
| 泄露类型 | 典型现象 | 常现位置 | OkHttp 相关组件 |
|---|---|---|---|
| 明文传输 | HTTPS 请求体/响应体在抓包中直接呈现未加密内容 | 网络请求/响应 payload | OkHttpClient、SSLSocketFactory |
| 日志残留 | Logcat 或本地日志文件中出现接口 URL、请求头或 JSON body | HttpLoggingInterceptor、自建拦截器 | HttpLoggingInterceptor、自定义 Interceptor |
| 缓存泄露 | 离线或切换账号后仍能访问之前用户的数据 | HTTP 缓存目录、内存缓存 | Cache、CacheStrategy、DiskLruCache |
| 第三方SDK外发 | App 流量监控显示 /ID 被传输至非业务域名 | 第三方 SDK 发起的 OkHttp 请求 | EventListener、Dns、ConnectionPool |
3.2 数据采集——四个维度的取证方法
排查的第一步是确定排查范围。使用以下指令分别获取各维度的证据:
- 明文传输取证:使用抓包工具(如 Wireshark、Charles)限制目标 IP,观察 SSL/TLS 握手过程。如果
Client Hello直接发出http://或不支持 TLS,即可定性。 - 日志残留取证:执行
adb logcat | grep -E "OkHttp|HttpLogging"后重放敏感 API 请求,标记包含手机号、身份证号的日志行。 - 缓存泄露取证:在
/data/data/包名/cache/http-cache/目录下导出.0、.1文件,检查是否包含序列化的敏感响应。 - 第三方SDK外发取证:在 OkHttpClient 上注册
EventListener,打印所有requestHeaders的Host字段,与非业务域名白名单交叉比对。
⚠️ 取证避坑:明文传输和第三方外发需要预先在 OkHttpClient 上添加
EventListener,否则事后无法补采数据。建议排期阶段就在所有 Client 上默认挂载安全监听。
3.3 原因定位——故障树分析
使用下面的故障树逐层解剖,不要跳过中间节点:
隐私泄露(根事件)
├─ 明文传输
│ ├─ 未启用 HTTPS(使用 http:// 或端口 80)
│ ├─ TLS 配置错误(未验证证书、CN 不匹配)
│ └─ 自定义 SSLSocketFactory 信任所有证书
├─ 日志残留
│ ├─ HttpLoggingInterceptor 级别设为 BODY
│ ├─ 自定义拦截器直接打印 request.body()
│ └─ 日志文件未设置过期清理策略
├─ 缓存泄露
│ ├─ Cache 目录权限设置为 MODE_WORLD_READABLE
│ ├─ CacheStrategy 允许缓存带 Authorization header 的响应
│ └─ 未在退出登录时清除 Cache
└─ 第三方SDK外发
├─ SDK 内部使用默认 OkHttpClient,未隔离
├─ SDK 将 放入全局 Header
└─ SDK 穿透了 App 的 Dns 拦截器
3.4 修复动作——按场景锁定修复
针对每种根因,执行对应的修复选项:
| 场景 | 修复前 | 修复后(安全基线) |
|---|---|---|
| 明文传输 | new OkHttpClient.Builder().build() | 显式指定 sslSocketFactory() 并添加 CertificatePinner |
| 日志残留 | addInterceptor(new HttpLoggingInterceptor().setLevel(Level.BODY)) | setLevel(Level.HEADERS) 或仅在 Debug 包使用 Level.BASIC |
| 缓存泄露 | new Cache(cacheDir, 10 * 1024 * 1024) | 添加 CacheStrategy.FORCE_NETWORK 或退出时 cache.evictAll() |
| 第三方SDK外发 | SDK 直接引用全局 OkHttpClient | 为 SDK 构造独立的 OkHttpClient.Builder(),并添加 EventListener 拦截异常域名 |
🔍 现场典型修复代码 —— 日志级别收紧:
// 错误写法(所有环境下都打印 Body)
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
// 正确写法(Release 包关闭日志)
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
if (BuildConfig.DEBUG) {
logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
} else {
logging.setLevel(HttpLoggingInterceptor.Level.NONE);
}
3.5 复测——隐私泄漏的回归验证清单
- 明文复测:再次抓包,确认所有请求均使用
https://且TLSv1.2+。 - 日志复测:运行
adb logcatgrep 排除所有 body 级别的密钥、身份证号输出。 - 缓存复测:退出账号 → 清理缓存 → 离线访问原接口 → 应返回 401 或空数据。
- SDK外发复测:在
EventListener的回调中对非白名单域名throw new IOException("非法外发"),验证是否触发中断。
3.6 预防——在开发流程中嵌入安全检查点
- CI 阶段:对
OkHttpClient.Builder的字节码进行扫描,检测sslSocketFactory是否覆盖了默认验证逻辑。 - Code Review 卡片:合并请求时必须勾选“无明文传输/日志Body输出/缓存/未隔离OKHTTP”。
- 定期水位线检测:每周扫描一次
HttpLoggingInterceptor的 Level 配置与全局Cache实例的生命周期管理。
✅ 通过性标准:如果四个场景中任意一项的复测结果仍为“失败”,则本次迭代不允许发布到生产环境。