在 Android 上,「拦截明文」被用来指两件完全不同的事。一件是应用内部的策略,命令自己的网络栈拒绝普通 HTTP。另一件是设备层面的安排,试图一次性保护手机上的所有流量,通常是把流量交给 VPN。它们解决的是不同问题,后者无法修复前者:隧道保护的是手机与出口之间的那一段,而一个说 HTTP 的应用,对出口之外的一切仍然在说 HTTP。

同一个名字,两个问题

第一个问题是载荷:应用把凭据或令牌写进一个没有端到端加密的请求里,于是每一个能看到它的跳点都能读到它。第二个是路径:应用可能完全规矩,而你仍然不希望本地 Wi-Fi 运营商知道你连向何处。VPN 只处理第二个,而且仅此而已。

这个区分决定了修复归谁。载荷归发布应用的人,因为只有那段代码决定调用哪个 URL、是否校验证书。路径归设备或网络,因为它讲的是数据包去哪里,而不是里面装什么。

路径之一:你自己掌控的应用

如果你发布这个应用,修复方式是一个传输决定,加上一条强制执行它的策略。把所有端点指向 HTTPS,保持主机名与证书校验开启,并把普通 HTTP 的回退删掉,而不是把它留在一个配置开关之外。重定向在这里帮不上忙:收到 301 的 HTTP 请求早已把头部和正文明文发了出去,所以绝不能让凭据坐上「重定向会保护它」的假设。

从 Android 7.0 起,Network Security Configuration 表达这条策略。下面这个文件是最小的可用版本:除非有针对某个域名的例外,它一律拒绝明文。

<network-security-config>
    <base-config cleartextTrafficPermitted="false" />
</network-security-config>

在清单里引用这份资源,然后在发布前把已经存在的例外读一遍。一个旧的调试域名,或者支付服务商 SDK 的条目,都可能悄悄让你想保护的那个主机重新允许明文。

有两个版本细节值得知道。以 API 28 或更高为目标的应用本来就默认拒绝明文,所以一个还在说普通 HTTP 的应用,要么目标是更低的 API 级别,要么在某处有例外。而当存在网络安全配置时,较早的 android:usesCleartextTraffic 清单属性不再决定这个问题,这就是为什么改那个开关有时看起来毫无作用。

路径之二:你无法掌控的应用

对于已安装的第三方应用,你无法通过用户设置改写它的安全配置。现实的选择是产品自带的安全连接控制、一个已经使用 TLS 且在维护的替代品,或者一条限制设备可运行内容的受管设备策略。这些是不同层次的承诺,只有第三条能扩展到整个车队。

网络规则是比看上去更弱的替代品。只放行 443 端口并不强制经过认证的 TLS,因为很多明文协议都能搬到那个端口;而封堵 80 端口也不覆盖其他端口上的明文。浏览器的仅 HTTPS 模式同样只作用于那个浏览器,不覆盖同一部手机上的另一个邮件或聊天应用。

为了让流量看起来是加密的而安装一张中间人证书,是改变信任关系,而不是修复。它把应用的服务器认证换成你自己的认证;一个本来就不校验证书的应用并不会因此被修好,你只是转移了谁能读这条流。

VPN 在哪里有用,在哪里没用

当要求是本地网络对这条连接一无所知,或者应用要到达一个否则无法到达的私有服务时,隧道才是正确的工具。它不适合让一个 HTTP 客户端改说 HTTPS,因为请求离开隧道时和进去时一模一样。

Android 还加了一条现实约束:平台同一时间只运行一个 VPN 服务。占用这个位置的本地过滤应用,会和你真正需要的远程隧道争夺它,而在两者之间切换是设备层面的操作,不是应用层面的。正是这个唯一的位置,让我们有充分理由把载荷策略放进应用,而把隧道留给路径。

两者唯一的重叠情形,是你自己也运营的私有服务器。在那里,即使协议仍是明文,隧道也能让路径变成私有的,这降低了暴露面,但不改变载荷是什么。请把它当作风险降低,而不是数据加密。

在宣布「已拦截」之前该测什么

从那个让人意外的默认值开始:以 API 28 或更高为目标的应用本来就该拒绝明文,所以在添加一段重复默认值的配置之前,先确认目标级别。然后故意测失败情形:发往普通 HTTP 主机的请求必须失败,而不是经由回退悄悄成功。

adb shell dumpsys package com.example.app | grep -E 'targetSdk|versionName'
adb logcat | grep -i cleartext

对于成功情形,要确认 HTTPS 调用校验的是真实的证书链,并且持有不可信证书的代理会被拒绝而不是被容忍。对任何证书都能成功的握手,并没有证明传输层安全。

对于第三方应用,写下你真正能强制执行的边界,无论是受管设备策略、网络规则,还是什么都没有,并把它写进验收标准。Android 安全配置指南 列出了平台默认值,以及哪些组件会遵守这条策略——这一部分,才决定你的配置到底有没有效果。

相关文章