夜雨聆风学习资料网

ARTICLE · 1135491

银行App一检测到你开了VPN,就秒变砖头

银行App一检测到你开了VPN,就秒变砖头

你肯定遇到过:打开银行 App 转账,结果弹出来一句"检测到您正在使用 VPN/代理,请关闭后重试"。

你把 VPN 关了, App 立刻恢复正常。

问题来了:App 怎么知道你开了 VPN ?它又没偷看你的系统设置——或者说,它偷看了吗?

答案比你想的更技术。银行 App 没有读你的 VPN 配置文件,它只是查了一下系统网络接口列表,发现多了一个"不该出现的网卡"。

VPN 在系统层面到底做了什么

要理解检测原理,先搞清楚 VPN 打开时系统发生了什么。

你的 iPhone 不开 VPN 时,网络路径是这样的:

App → Wi-Fi/蜂窝网络 → 目标服务器(银行)

开了 VPN 之后,变成这样:

App → utun0(虚拟网卡)→ VPN 服务器 → 目标服务器(银行)

关键在那个 utun0。 VPN 在系统里创建了一个虚拟网络接口( tunnel interface ),所有流量先走这个虚拟网卡,再从 VPN 服务器出去。

这就像你寄快递,正常从北京发到上海。但如果你用了 VPN ,相当于快递先绕到新加坡再发到上海。包裹内容没变,但路径变了——多了一个中转站。

银行 App 的检测原理,就是查这个"中转站"是否存在。

第一层检测: CFNetwork 的 __SCOPED__ 字典

这是最核心、最可靠的一层检测,而且不需要任何特殊权限——App Store 审核 100% 通过。

iOS 系统里有一个 C 级别的函数叫 CFNetworkCopySystemProxySettings(),它返回当前系统的代理配置。返回值里有个关键字段叫 __SCOPED__,是一个字典,里面列出了所有当前活跃的网络接口。

不开 VPN 时,__SCOPED__ 长这样:

["en0":{...}]// en0 = Wi-Fi 网卡,正常

开了 VPN 后,变成这样:

["en0":{...},"utun0":{...}]// 多了个 utun0!

检测逻辑就是:遍历 __SCOPED__ 的所有 key ,看看有没有这些名字——

funcisVPNConnected()->Bool{
guardletsettings=CFNetworkCopySystemProxySettings()?
.takeRetainedValue()as?[String:Any],
letscopes=settings["__SCOPED__"]as?[String:Any]else{
returnfalse
}
for(key,_)inscopes{
// VPN 协议的接口名前缀
ifkey.contains("tap")||key.contains("tun")||
key.contains("ppp")||key.contains("ipsec")||
key.contains("utun"){
returntrue// 检测到了
}
}
returnfalse
}

就这么几行。核心是 __SCOPED__ 字典里的 key——VPN 创建的虚拟网卡接口名(utun、tun、tap、ppp、ipsec)和普通 Wi-Fi/蜂窝网络接口名(en0、pdp0)完全不同。只要发现了这些前缀的接口,就说明有 VPN 在跑。

这就像你检查一个人身上有没有多带一个钱包——正常出门带一个钱包(en0),突然多了一个暗袋(utun0),那就说明有问题。

第二层检测: NWPathMonitor 实时监控

CFNetworkCopySystemProxySettings 有个弱点:它是一次性调用。调一次返回当前状态,不会在 VPN 状态变化时主动通知你。

银行 App 需要的是实时监控——用户可能先开 App 再开 VPN ,也可能开 VPN 后关了再开 App 。这需要 NWPathMonitor。

NWPathMonitor 是 iOS 12 引入的 Network 框架 API ,它可以监控网络路径的实时变化:

letmonitor=NWPathMonitor()
monitor.pathUpdateHandler={pathin
ifpath.status==.satisfied{
// path.interfaces 里包含当前所有网络接口
lethasVPN=path.interfaces.contains{interfacein
interface.type==.other// VPN 接口通常报为 .other
}
ifhasVPN{
// 触发 VPN 告警
DispatchQueue.main.async{
self.showVPNAlert()
}
}
}
}
monitor.start(queue:DispatchQueue.global())

和第一层配合使用:CFNetworkCopySystemProxySettings 负责"首次检查",NWPathMonitor 负责"持续监控"。两层交叉验证,基本上没有 VPN 能逃过去。

第三层检测: SSL 证书指纹校验

前两层检测的是"系统有没有虚拟网卡"。但有些高级 VPN (比如 Quantumult X 的"虚拟网卡模式")不走系统代理,而是直接接管网络层,__SCOPED__ 字典里可能不会出现 utun 接口。

这时候银行 App 会用第三层:SSL 证书指纹校验。

原理是:你用 VPN 连银行服务器时, SSL 握手过程中的证书可能经过中间人代理( MITM )。 VPN 软件为了解密 HTTPS 流量,会用自己的证书替换银行的真实证书。银行 App 在 URLSessionDelegate 里校验证书指纹:

funcurlSession(_session:URLSession,
didReceivechallenge:URLAuthenticationChallenge,
completionHandler:@escaping(URLSession.AuthChallengeDisposition,URLCredential?)->Void){
guardlettrust=challenge.protectionSpace.serverTrust,
letcert=SecTrustGetCertificateAtIndex(trust,0)else{
completionHandler(.cancelAuthenticationChallenge,nil)
return
}
// 对比证书指纹
letfingerprint=certificateSHA256(cert)
iffingerprint!=BANK_CERT_FINGERPRINT{
// 证书被替换了 → 有中间人代理 → 拒绝
completionHandler(.cancelAuthenticationChallenge,nil)
}else{
completionHandler(.useCredential,URLCredential(trust:trust))
}
}

这层的逻辑是:如果 SSL 证书指纹和银行预置的不一致,说明流量被中间人截获了——正常用户不会出现这种情况,只有 VPN 做 MITM 解密时才会。

为什么不用 NEVPNManager ?

iOS 还有一个 NEVPNManager 类,能直接读取系统 VPN 配置信息。但它有两个限制:

1.需要 NetworkExtension entitlement——这是苹果的受限权限,普通 App 申请不到,只有 VPN 应用本身才能用
2.只能看到自己创建的 VPN 配置——银行 App 不是 VPN 应用,看不到别人创建的 VPN

所以银行 App 不能用 NEVPNManager。它们用的是不需要任何特殊权限的 CFNetworkCopySystemProxySettings + NWPathMonitor 组合——用公共 API 做到的事情,比用受限 API 还多。

生活化比喻:快递的三个检查站

把你发网络请求比作寄快递,银行 App 就是收件方。

第一层检查( CFNetwork ):收件人查快递面单,看寄件路径有没有经过新加坡中转。正常从北京到上海不经过新加坡,但 VPN 的流量会经过。如果发现路径里有中转站,就是 VPN 。

第二层检查( NWPathMonitor ):收件人装了个实时监控摄像头,不是只在收件时查一次,而是持续盯着——快递在路上突然改道了(用户中途开 VPN ),也能立刻发现。

第三层检查( SSL 指纹):收件人拆开快递,验防伪码。正常银行的防伪码是 A ,如果发现变成了 B ,说明快递在中转站被人拆开过又封上了( MITM 解密)。正常用户不会换防伪码,只有 VPN 做 HTTPS 解密才会。

三层检查,一层比一层深。第一层查路径,第二层持续查,第三层查内容完整性。

为什么银行这么在意 VPN

三个原因:

1.合规要求:银行受银保监会监管,要求"客户端到服务端的通信必须在可信网络环境内"。 VPN 绕过了运营商网络,银行无法验证用户真实 IP 归属地,有跨境欺诈风险
2.中间人攻击风险:部分 VPN 会做 HTTPS 解密( MITM ),银行流量经过 VPN 服务商的服务器,存在凭证被截获的风险
3.反欺诈:大量盗刷案例的 IP 地址来自 VPN 出口。银行的风控模型依赖 IP 归属地做地理围栏, VPN 破坏了这个前提

这也解释了为什么有的银行检测到 VPN 只是弹个提示"建议关闭",有的是直接拒绝服务——风险等级不同,处理策略不同。

不同 VPN 的"对抗"策略

V2EX 上有大量讨论,核心结论是:

VPN 类型
会被检测到吗
为什么
系统代理模式( Surge/Shadowrocket 代理模式)
会
__SCOPED__
 里有代理接口
VPN 模式(系统 VPN 配置)
会
创建 utun 接口
虚拟网卡模式( Quantumult X 的 TUN 模式)
部分能绕过
不走系统代理,但可能仍有虚拟网卡
全局路由模式( WireGuard )
会
创建 utun 接口

V2EX 帖子里的结论是:用 Quantumult X 的"虚拟网卡模式"且不使用系统代理接管,是最可能绕过检测的方式。但这不是一劳永逸——银行可以加 SSL 指纹校验层来覆盖这种情况。

本质上这是一个检测方和规避方的持续对抗——和反爬虫 vs 爬虫的博弈完全同构。

面试速记卡

Q : iOS 上怎么检测用户是否开了 VPN ?

A :三层检测。第一层用 CFNetworkCopySystemProxySettings 读 __SCOPED__ 字典,看有没有 utun/tun/tap/ppp/ipsec 接口。第二层用 NWPathMonitor 持续监控网络路径变化。第三层用 URLSessionDelegate 校验 SSL 证书指纹,检测 MITM 代理。

Q :为什么不用 NEVPNManager ?

A : NEVPNManager 需要 NetworkExtension entitlement ,普通 App 申请不到;而且它只能看到自己创建的 VPN 配置,看不到别的 App 创建的 VPN 。CFNetworkCopySystemProxySettings 不需要任何特殊权限, App Store 审核通过。

Q : VPN 为什么会改变系统网络接口?

A : VPN 在系统里创建虚拟网络接口(如 utun0),所有流量先走这个虚拟网卡,再从 VPN 服务器出去。这个虚拟接口会出现在系统的网络接口列表里, App 可以通过公共 API 查到。

Q : SSL 指纹校验为什么能检测 VPN ?

A :部分 VPN 会做 HTTPS 中间人解密( MITM ),用自己的证书替换服务器的真实证书。银行 App 预置了服务器证书指纹,如果握手时收到的证书指纹不匹配,说明流量被中间人截获了。

Q : VPN 检测和 HTTPS 加密是什么关系?

A : VPN 不破坏 HTTPS 加密——你的流量在 VPN 隧道里是加密的。但 VPN 改变了网络路由路径,系统层面会多出虚拟网卡接口。检测 VPN 不是"破解加密",而是"检测系统网络配置变化"。 HTTPS 保证的是传输内容安全, VPN 检测保证的是传输路径可信。

这篇在知识体系里的位置

属于「 Web 」分类,是「 HTTPS 」和「 CORS 」的延伸。之前讲过 HTTPS (传输加密)和 CORS (跨域限制),这篇讲的是网络层的环境检测——App 怎么感知系统网络配置的变化。

同时关联「安全」分类:之前讲过 SQL 注入和 XSS (输入侧防御), VPN 检测是环境侧防御——不防攻击者输入恶意数据,而是防用户运行环境不可信。

新增知识点:CFNetworkCopySystemProxySettings 的 __SCOPED__ 字典、NWPathMonitor 实时网络路径监控、 SSL 证书指纹校验机制、 VPN 虚拟网卡接口命名规则。

相关学习资料