WebRTC泄露源IP的防范措施

作者: 安全北北

--------------------------------------------------------------------------

目录:

☆ 背景介绍
☆ 测试WebRTC泄露源IP
☆ 阻止WebRTC泄露源IP
1) 浏览器扩展
1.1) WebRTC Leak Shield (推荐)
1.2) WebRTC Network Limiter (Google)
1.3) 其他扩展
2) 浏览器自带设置
2.1) Firefox
2.2) Safari (未实测)
2.2.1) on macOS
2.2.2) on iOS (before 11)
2.3) Edge (已过时)
2.4) Chrome
2.4.1) on Android
2.4.1.1) 安卓版Chrome的F12
2.4.2) on Win10 (无设置)
2.4.3) Chromium
2.5) Opera
2.6) 利用mDNS阻止泄露内网IP
3) iOS小火箭
☆ WebRTC泄露源IP的基本原理
1) 简述
2) 真实世界中的WebRTC
3) 延伸阅读
3.1)
3.2)
3.3)
3.4)
3.5)
3.6) WebRTC通信的四种模式
4) WebRTC泄露源IP是否构成安全问题
☆ JavaScript调用WebRTC API示例
1) STUN IP Address requests for WebRTC
2) 安全北北截图中的代码
2.1) hackjie网页内容分析 (404)
3) 极简示例
4) Public STUN Server List
5) mDNS安全改进
☆ UDP层面的STUN通信报文
1) Wireshark抓包
2) XOR-MAPPED-ADDRESS
3) Message Cookie: 2112a442
4) 寻找STUN Server
5) PFW阻断周知STUN端口
☆ Media Device Fingerprinting
☆ 其他讨论
☆ FAQ
☆ 后记

--------------------------------------------------------------------------

☆ 背景介绍

参看

https://weibo.com/2635351511/MBMlNlhNq

2023.4.13,网友「安全北北」在微博上分享一则WEB安全相关的信息,以下是他的原
话:「今天看到同事说这个网站(略)可以获取到请求来源真实IP,我试了一下,确实,
无论是机场还是自建(包括gost tls),都可以被探测出源IP。研究了一下,应该是
WebRTC的问题,网站可以利用STUN服务器获取到本地的IP地址。简单解决方案,安装
WebRTC Leak Shield 扩展」

我不懂WEB安全,尤其不懂WEB前端安全,他说的WebRTC、STUN什么的,完全不懂。这
种你问ChatGPT,对于毫无此方向基础的人而言,意义不大,让我们换个更聚焦、更
吓人的方式科普。Chrome/Opera/Edge这些浏览器,很可能支持所谓WebRTC技术;假
设A机使用前述浏览器,挂了线路到B机,以B机为中转站访问C机,你以为C机只能看
到B机的IP,但C机通过WebRTC技术可以看到A机的IP。

完全小白的,看早期科普版本即可,其中有些错误,但大面上没问题

https://mp.weixin.qq.com/s/A5dd7WXojGBzsz52_iLtJQ

☆ 测试WebRTC泄露源IP

最初看到「安全北北」的分享,将信将疑。接完cby回家,赶紧在Win10上测试,
Chrome/Opera/Edge不幸中招,Firefox ESR逃过此劫,我没有其他浏览器,据说
Safari在此问题上表现不错。测试方案很简单,打开浏览器,挂线路访问如下URL之

https://ip8.com/webrtc-test
https://www.vpnmentor.com/tools/ip-leak-test-vpns-tor/
https://www.expressvpn.com/webrtc-leak-test
https://www.hackjie.com/tracking (404)

社会可能毒打你。看到A、B机IP同时出现在C机页面上,内心是崩溃的,颠覆了我本
就不多的WEB安全认知,无知者无畏太可怕。

附送几个DNS泄露测试URL

https://dnsleaktest.org/dns-leak-test
https://www.expressvpn.com/dns-leak-test

检查出口源IP的URL到处都是,用自己常用的好了,给比我还白的小白写几个

https://ip4.me/
https://api.ipify.org (只返回源IP)
http://checkip.dyndns.com
https://ipinfo.io/

☆ 阻止WebRTC泄露源IP

1) 浏览器扩展

1.1) WebRTC Leak Shield (推荐)

以下是Chrome/Edge/Firefox的"WebRTC Leak Shield"扩展所在URL,各自打开相应
URL安装即可,也可在扩展商店中搜索定位,Opera可以装Chrome扩展。装完立即生效,
可重新测试WebRTC泄露源IP情况,一般均被阻止了。

https://chrome.google.com/webstore/detail/webrtc-leak-shield/bppamachkoflopbagkdoflbgfjflfnfl
https://microsoftedge.microsoft.com/addons/detail/pblfgfehcokbglafpcldgjpmknildihk
https://addons.mozilla.org/en-US/firefox/addon/webrtc-leak-shield/

根据我这儿的测试情况,Firefox ESR不装该扩展也不泄露源IP,保险起见,还是装
吧。粗略看了该插件js源码,试图理解某些代码逻辑。

若是Firefox,调

browser.privacy.network.peerConnectionEnabled.set({ value: false })

这会禁用WebRTC PeerConnection。

若是Chrome,调

--------------------------------------------------------------------------
chrome.privacy.network.webRTCIPHandlingPolicy.set({
value: "disable_non_proxied_udp"
}, function () {
...
});
--------------------------------------------------------------------------

该API第一形参可选值有

"default"
"default_public_interface_only"
"disable_non_proxied_udp"
"disable_all_interfaces"

该API第二形参对应一个回调函数,不必理会。实际管事的是

chrome.privacy.network.webRTCIPHandlingPolicy.set({ value: "disable_non_proxied_udp" })

禁止非代理的UDP通信。

参看

--------------------------------------------------------------------------
《离线安装Chrome插件》
https://scz.617.cn/web/202205271527.txt

《如何查看Chrome插件js源码》
https://scz.617.cn/web/202305310924.txt
--------------------------------------------------------------------------

1.2) WebRTC Network Limiter (Google)

https://chrome.google.com/webstore/detail/webrtc-network-limiter/npeicpdbkakmehahjeeohfdhnlpdklia

该扩展是Google自家的,可阻止WebRTC使用某些IP地址、协议

private IP addresses not visible to the public internet (e.g. addresses
like 192.168.1.2)

any public IP addresses associated with network interfaces that are not
used for web traffic (e.g. an ISP-provided address, when browsing through
a VPN)

Require WebRTC traffic to go through proxy servers as configured in Chrome.
Since most of the proxy servers don't handle UDP, this effectively turns
off UDP until UDP proxy support is available in Chrome and such proxies
are widely deployed.

The extension may also disable non-proxied UDP, but this is not on by
default and must be configured using the extension's Options page.

This extension may affect the performance of applications that use WebRTC
for audio/video or real-time data communication. Because it limits the
potential network paths and protocols, WebRTC may pick a path which
results in significantly longer delay or lower quality (e.g. through a VPN
) or use TCP only through proxy servers which is not ideal for real-time
communication.

看其介绍,该扩展似乎有边际效应,防DNS泄露?因为不走代理的UDP被阻断。该扩展
只防IP泄露,并不彻底禁用WebRTC。

1.3) 其他扩展

下面这些扩展均未实测,仅供参考

--------------------------------------------------------------------------
WebRTC Control (在Edge版评论区有质疑评论,怀疑有猫腻)

https://chrome.google.com/webstore/detail/webrtc-control/fjkmabmdepjfammlpliljpnbhleegehm

WebRTC Protect - Protect IP Leak

WebRTC Leak Prevent

https://chrome.google.com/webstore/detail/webrtc-leak-prevent/eiadekoaikejlgdbkbdfeijglgfdalml

uBlock Origin (不只是防IP泄露,可彻底禁用WebRTC)

https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm
--------------------------------------------------------------------------

禁用JavaScript可以阻止WebRTC泄露源IP,JavaScript真是万恶之源啊。不过,禁用
JavaScript的副作用太大,根据自己的实际需求决定吧。

--------------------------------------------------------------------------
NoScript

https://addons.mozilla.org/firefox/addon/noscript/

ScriptSafe

https://chrome.google.com/webstore/detail/scriptsafe/oiigbmnaadbkfbmpbfijlflahbdbdgdf
--------------------------------------------------------------------------

2) 浏览器自带设置

2.1) Firefox

Firefox有个配置项

about:config
media.peerconnection.enabled

据说从缺省的true设为false,会禁用WebRTC。但我这儿测下来,无论true还是false,
Firefox ESR都没有泄露源IP。保险起见,将之设为false。另有"about:webrtc",可
以看看,在"本地SDP"中未见源IP。

Firefox有一些更细化的设置,不适用于普通用户,高级用户可以看看

https://tools.ietf.org/html/draft-ietf-rtcweb-ip-handling-06

--------------------------------------------------------------------------
media.peerconnection.ice.default_address_only

setting this to true essentially provides mode 2

media.peerconnection.ice.no_host

setting this and the default_address_only provides mode 3

media.peerconnection.ice.proxy_only

setting this to true forces mode 4, disabling UDP
--------------------------------------------------------------------------

若你的Firefox非ESR版本,实测确实泄露了NAT出口IP,可以考虑最后这个设置,强
制使用模式4,此时未禁用WebRTC,同时阻止WebRTC泄露NAT出口IP。

2.2) Safari (未实测)

2.2.1) on macOS

Safari->Preferences->Advanced->Show Develop menu in menu bar

Develop->WebRTC->Enable Legacy WebRTC API (禁用它)
Develop->Experimental Features->Remove Legacy WebRTC API (选中它)

二者并不同时存在,有哪个用哪个。

2.2.2) on iOS (before 11)

iOS 11及之前版本可以禁用WebRTC

Settings->Safari->Advanced->Experimental Features->Remove Legacy WebRTC API (选中它)

从iOS 12开始,苹果移除了该设置

2.3) Edge (已过时)

Edge过去有个配置,但现在已经不在了,此处记一笔

about:flags
Hide my local IP over WebRTC connections

即使存在,应该只能阻止WebRTC泄露内网IP,不能阻止WebRTC泄露公网IP。

另有

edge://webrtc-internals/

类似Firefox的"about:webrtc"

2.4) Chrome

2.4.1) on Android

chrome://flags/#disable-webrtc
WebRTC STUN origin header

禁用之

微博网友UID(7800576625)反馈,安卓版Chrome 112.0.5615.101已不可用前述设置。

2.4.1.1) 安卓版Chrome的F12

Is it possible to open developer tools console in Chrome on Android phone - [2016-05-16]
https://stackoverflow.com/questions/37256331/is-it-possible-to-open-developer-tools-console-in-chrome-on-android-phone

2.4.2) on Win10 (无设置)

Chrome未提供便捷配置禁用WebRTC,因为Google大力推介WebRTC以对抗Skype。

另有

chrome://webrtc-internals/

类似Firefox的"about:webrtc"

2.4.3) Chromium

网友Welkin在公众号留言,Chromium参数"--disable-webrtc-encryption"可以阻止
WebRTC泄露源IP。Chromium与Chrome虽然有关联,但不是一个东西。在微博上请其他
网友验证这个说法,微博网友UID(7128188018、5245763474、7551644722)各自独立
测试,证实至少Chromium 108.0.5359.125及之后版本该参数有此效果,那些测试URL
确实没有显示源IP。前两位说是该参数导致无STUN通信报文,UID(7551644722)提供
了一批pcap文件做为对比,符合理论预期。我没有Chromium环境,未就此抓包分析。

2.5) Opera

opera://config/
Privacy protection->Advanced->WebRTC->Disable non-proxied UDP

另有

opera://webrtc-internals/

类似Firefox的"about:webrtc"

2.6) 利用mDNS阻止泄露内网IP

Chrome/Opera/Edge有个配置项

chrome://flags/#enable-webrtc-hide-local-ips-with-mdns
opera://flags/#enable-webrtc-hide-local-ips-with-mdns
edge://flags/#enable-webrtc-hide-local-ips-with-mdns

显示的信息是

Anonymize local IPs exposed by WebRTC.
Conceal local IP addresses with mDNS hostnames

2022年该配置缺省启用中,可以阻止WebRTC泄露内网IP,但不能阻止WebRTC泄露公网
IP。

3) iOS小火箭

微博网友UID(3717584663)提到,iOS小火箭-设置-UDP-禁用STUN,在各个浏览器测
试了,可以防源IP泄漏。

☆ WebRTC泄露源IP的基本原理

1) 简述

SDP (Session Description Protocol)是一种用于描述媒体会话的协议,它包含有关
音视频编解码器、媒体格式、传输协议等信息。

当一个WebRTC客户端想要与另一个WebRTC客户端通信时,它会将自己的SDP信息提交
给信令服务器(Signaling Server)。信令服务器会将这些信息转发给对端。对端会解
析SDP信息,并生成自己的SDP信息,然后将其提交给信令服务器。信令服务器会将对
端SDP信息转发给发起端。通过交换SDP信息,双方客户端可以协商并建立点对点的音
视频通信。在SDP信息交换过程中,信令服务器类似于中继站,它负责转发SDP信息,
但不会参与实际的音视频数据传输。

STUN Server用于帮助WebRTC客户端发现其NAT (Network Address Translation)类型
和公网IP。WebRTC客户端与STUN Server通信,获取NAT出口IP,填写到SDP信息中。

SDP信息中可能包含多种IP地址,比如本机网卡绑定的公有和私有IP(称为"host
candidate")、通过STUN (Session Traversal Utilities for NAT)协议获取的NAT出
口IP(称为"server reflexive candidate"),这些IP地址信息在WebRTC建立连接时被
对端获取。并非所有的SDP信息中都会提供"server reflexive candidate",取决于
WebRTC应用程序具体实现。

自2013年始,安全专家就开始担心WebRTC带来的内网信息泄露。在浏览器中进行音频
/视频通信、P2P文件共享,很可能用到WebRTC,很多手机APP也用。很多场景会用
WebRTC,包括但不限于

Google Meet and Google Hangouts
Facebook Messenger
Discord
Amazon Chime

2015年《纽约时报》曾使用WebRTC收集访问者内网IP,被人给扒出来。

Dear NY Times, if you’re going to hack people, at least do it cleanly!


https://bloggeek.me/webrtc-new-york-times/

2) 真实世界中的WebRTC

真实世界中的WebRTC STUN TURN and signaling - [2018-08-01]
https://michaelyou.github.io/2018/08/01/%E7%9C%9F%E5%AE%9E%E4%B8%96%E7%95%8C%E4%B8%AD%E7%9A%84WebRTC/

上文从正常开发者角度详解WebRTC相关组件、通信原理,虽然不直接涉及安全视角,
但明白原理后自然会有安全思考,推荐阅读。

3) 延伸阅读

3.1)

https://www.expressvpn.com/webrtc-leak-test

--------------------------------------------------------------------------
Web Real-Time Communication (WebRTC) is a collection of standardized
technologies that allows web browsers to communicate with each other
directly without the need for an intermediate server. Benefits of WebRTC
include: faster speeds and less lag for web apps like video chat, file
transfer, and live streaming.

Any two devices talking to each other directly via WebRTC, however, need
to know each other’s real IP addresses. In theory this could allow a
third-party website to exploit the WebRTC in your browser to detect your
real IP address and use it to identify you. This is what we call a WebRTC
leak.

WebRTC discovers IPs via the Interactive Connectivity Establishment (ICE)
protocol. This protocol specifies several techniques for discovering IPs,
two of which are covered below.

STUN/TURN servers

STUN/TURN servers play two key roles in WebRTC: They allow web browsers to
ask the question "What are my public IPs?" and they also facilitate two
devices talking to each other even if they are behind NAT firewalls. The
former is the one that can affect your privacy. STUN/TURN servers discover
your IPs much as a website sees your IPs when you visit it.

Host candidate discovery

Most devices have multiple IP addresses associated with their hardware.
Usually these are hidden from websites and STUN/TURN servers via firewalls.
However, the ICE protocol specifies that browsers can gather these IPs
simply by reading them off your device.

A malicious website could use STUN/TURN servers or host candidate
discovery to trick your browser into revealing an IP address that could
identify you, all without your knowledge.
--------------------------------------------------------------------------

3.2)

https://www.vpnmentor.com/blog/disable-webrtc-in-seconds/

--------------------------------------------------------------------------
WebRTC works directly in the browser using JavaScript. A website's code
can be written with scripts that open a WebRTC connection to any browser
where it's actively running. This means that WebRTC can leak your IP
address even if you've never used the technology for its intended purpose.
It doesn't matter whether you've actually accepted a direct audio/video
connection or not. As long as WebRTC is fully enabled in your browser,
it's putting you at risk.
--------------------------------------------------------------------------

https://www.vpnmentor.com/blog/ip-leaks-how-to-check-vpn/

--------------------------------------------------------------------------
The browsers that support WebRTC - like Chrome and Firefox - utilize a
STUN server (Session Traversal Utilities for NAT) to obtain an external
network address. A website that wants to know your real IP address can
very easily conceal a piece of Javascript code to make UDP requests to
this STUN server, which would then route these requests to all the
available network interfaces. In this situation, both your real IP address
and VPN IP address can be exposed, and it's worryingly easy to embed such
a code in a supposedly innocent website. To make the situation worse,
since these requests are not like typical HTTP requests, the developer
console cannot detect them and so browser plugins cannot reliably block
this kind of leak (even if they advertise such an ability).
--------------------------------------------------------------------------

3.3)

WebRTC IP Leaks: Should You Still Be Worried - Gordon H [2022-11-28]
https://getstream.io/blog/webrtc-ip-leaks

--------------------------------------------------------------------------
How WebRTC works and how it determines the optimal route to connect two or
more devices is quite complicated. Let's just assume that establishing a
connection is a matter of exchanging connection information (IP addresses)
between peers. This connection information is referred to as ICE (
Interactive Connectivity Establishment) candidates, a standard method of
NAT (Network Address Translation) traversal used in WebRTC, which shows
the available methods the peer can communicate through (directly or
through a TURN server).

There are three types of addresses that a WebRTC client tries to negotiate

Local IP addresses
Public IP addresses found through STUN servers
Public IP addresses found through TURN servers.

For peers to establish an optimal connection, this information is needed.
For example, if two devices are connected to the same local network, they
need access to each other's local IP addresses. While if they're not on
the same network, they will need an accessible public IP.

With modern browsers and the growing need to access resources such as
devices' webcams and microphones, an easy-to-use API was needed. This is
where WebRTC came in, a JavaScript library built into modern browsers and
enabled by default.

It provides a convenient API for developers to interact with and makes it
easy to create a media connection between multiple peers. Part of this
ease-of-use requires a way to retrieve the ICE candidates for the local
device, as that information must be shared with any other peer to create a
connection. As such, WebRTC exposes a convenient way to access ICE
candidates and, indirectly, an easy way to access a device's local and
public IP address.
--------------------------------------------------------------------------

3.4)

PSA: mDNS and .local ICE candidates are coming - [2019-07-09]
https://bloggeek.me/psa-mdns-and-local-ice-candidates-are-coming/

--------------------------------------------------------------------------
Why do we need a local IP address?

If both machines that need to connect to each other using WebRTC sit
within the same private network, then there's no need for the
communication to leave the local network either.

Why do we need a public IP address through STUN?

If the machines are on different networks, then by punching a hole through
the NAT/firewall, we might be able to use the public IP address that gets
allocated to our machine to communicate with the remote peer.

Why do we need a public IP address on a TURN server?

If all else fails, then we need to relay our media through a "third party".
That third party is a TURN (Traversal using Relays around NAT) server.
--------------------------------------------------------------------------

WebRTC通信双方在同一子网时,各自需要对方的子网IP。通信双方位于不同子网时,
比如跨了NAT,各自需要对方的NAT出口IP,此时先各自通过"STUN Server"收集己方
NAT出口IP,再通过"Signaling Server"完成信息交换。前面两种情况都不可行时,
通信双方经"TURN Server"中转通信。

3.5)

Why Doesn't Google Provide a Free TURN Server - [2017-04-03]
https://bloggeek.me/google-free-turn-server/

此文简介了STUN/TURN的作用

Neither Denied nor Exposed: Fixing WebRTC Privacy Leaks - [2020-04]
https://www.mdpi.com/1999-5903/12/5/92
https://github.com/IncredibleMe/WebRTC-IP-Leak-Chrome-Extension
https://github.com/IncredibleMe/WebRTC-IP-Leak-Gateways

信息量较大、较专业,有STUN/TURN的图解、JavaScript示例、媒体列表指纹的讨论。

3.6) WebRTC通信的四种模式

So your VPN is leaking because of Chrome’s WebRTC…


https://tools.ietf.org/html/draft-ietf-rtcweb-ip-handling-06

--------------------------------------------------------------------------
Mode 1

Enumerate all addresses: WebRTC MUST use all network interfaces to attempt
communication with STUN servers, TURN servers, or peers. This will
converge on the best media path, and is ideal when media performance is
the highest priority, but it discloses the most information.

Mode 2

Default route + associated local addresses: WebRTC MUST follow the kernel
routing table rules, which will typically cause media packets to take the
same route as the application's HTTP traffic. In addition, the private
IPv4 and IPv6 addresses associated with the kernel-chosen interface MUST
be discovered and provided to the application. This ensures that direct
connections can still be established in this mode.

Mode 3

Default route only: This is the the same as Mode 2, except that the
associated private addresses MUST NOT be provided; the only IP addresses
gathered are those discovered via mechanisms like STUN and TURN (on the
default route). This may cause traffic to hairpin through a NAT, fall back
to an application TURN server, or fail altogether, with resulting quality
implications.

Mode 4:

Force proxy: This is the same as Mode 3, but all WebRTC media traffic is
forced through a proxy, if one is configured. If the proxy does not
support UDP (as is the case for all HTTP and most SOCKS [RFC1928] proxies),
or the WebRTC implementation does not support UDP proxying, the use of UDP
will be disabled, and TCP will be used to send and receive media through
the proxy. Use of TCP will result in reduced media quality, in addition to
any performance considerations associated with sending all WebRTC media
through the proxy server.
--------------------------------------------------------------------------

WebRTC Network Limiter扩展可以迫使Chrome进入模式4。Firefox有原生配置项强制
进入模式4,前面有讲。进入模式4后,客户端与STUN Server的通信必须经过Proxy,
但所有HTTP Proxy以及大多数SOCKS5 Proxy不支持UDP转发,于是事实上阻断了客户
端与STUN Server的通信。

4) WebRTC泄露源IP是否构成安全问题

本文关注的真不是传统意义的安全问题,本文关注的是,在挂线路的前提下泄露源IP
的问题,是特定历史时期、特定区域、特定人群面临的"安全"问题,这个不用展开说
了吧。

关于WebRTC相关的传统安全问题,参看

--------------------------------------------------------------------------
NAT Slipstreaming
https://samy.pl/slipstream/
https://github.com/samyk/slipstream

A browser-based network IP scanner and local IP detector
https://github.com/samyk/webscan

WebRTC Fingerprinting + Media Device Fingerprinting
https://web-tracking.allenchou.cc/docs/browser-fingerprinting/techniques/webrtc-and-media-device-fingerprinting/

Everything you need to know about WebRTC security - [2020-04-06]
https://bloggeek.me/is-webrtc-safe/
--------------------------------------------------------------------------

2022年之后,WebRTC泄露内网IP的问题得到治理,泄露NAT出口IP仍在继续。严格说
来,是STUN通信导致NAT出口IP泄露,这本就是STUN通信存在之目的,协议设计来就
是干这事的。但在挂线路上网的特定语境下,NAT出口IP泄露成为一个"安全"问题。
网页中的JavaScript触发STUN通信,获取NAT出口IP,再用XMLHttpRequest向外传送
之。

有些人说WebRTC泄露源IP是Feature,不是Issue,可以理解。这些人多半不是网络安
全行业的,TA们对Feature的教条主义理解我见过太多。这是好事,这群大聪明的存
在,让这个世界充满爱,精神的、物质的爱。千万不要毒打TA们,至少面上不要,那
样会减少爱。

☆ JavaScript调用WebRTC API示例

1) STUN IP Address requests for WebRTC

https://github.com/diafygi/webrtc-ips

--------------------------------------------------------------------------
Firefox and Chrome have implemented WebRTC that allow requests to STUN
servers be made that will return the local and public IP addresses for the
user. These request results are available to javascript, so you can now
obtain a users local and public IP addresses in javascript. This demo is
an example implementation of that.

These STUN requests are made outside of the normal XMLHttpRequest
procedure, so they are not visible in the developer console or able to be
blocked by plugins such as AdBlockPlus or Ghostery. This makes these types
of requests available for online tracking if an advertiser sets up a STUN
server with a wildcard domain.
--------------------------------------------------------------------------

是时候让ChatGPT为D国做贡献了,指挥它改代码,改了几轮后,如下

--------------------------------------------------------------------------
function getIPs(callback, server, port) {
var ip_dups = {};

// compatibility for firefox and chrome
var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
var useWebKit = !!window.webkitRTCPeerConnection;

// bypass naive webrtc blocking using an iframe
if (!RTCPeerConnection) {
// NOTE: you need to have an iframe in the page right above the script tag
//
//<iframe id="iframe" sandbox="allow-same-origin" style="display: none"></iframe>
//<script>...getIPs called in here...
//
var win = iframe.contentWindow;
RTCPeerConnection = win.RTCPeerConnection || win.mozRTCPeerConnection || win.webkitRTCPeerConnection;
useWebKit = !!win.webkitRTCPeerConnection;
}

// minimal requirements for data connection
var mediaConstraints = {
optional: [{ RtpDataChannels: true }]
};

// set STUN server address and port
var stunServer = server || "stun:stun.l.google.com";
if (port) {
stunServer += ":" + port;
}
var servers = {
iceServers: [{ urls: stunServer }]
};

// construct a new RTCPeerConnection
var pc = new RTCPeerConnection(servers, mediaConstraints);

function handleCandidate(candidate) {
// match just the IP address
var ip_regex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/
var match = ip_regex.exec(candidate);
if (match) {
var ip_addr = match[1];
if (ip_dups[ip_addr] === undefined) {
callback(ip_addr);
}
ip_dups[ip_addr] = true;
}
}

// listen for candidate events
pc.onicecandidate = function (ice) {

// skip non-candidate events
if (ice.candidate) {
handleCandidate(ice.candidate.candidate);
}
};

// create a bogus data channel
pc.createDataChannel("");

// create an offer sdp
pc.createOffer(function (result) {

// trigger the stun server request
pc.setLocalDescription(result, function () { }, function () { });

}, function () { });

// wait for a while to let everything done
setTimeout(function () {
// read candidate info from local description
var lines = pc.localDescription.sdp.split('\n');

lines.forEach(function (line) {
if (line.indexOf('a=candidate:') === 0) {
handleCandidate(line);
}
});
}, 1000);
}

// Test: Print the IP addresses into the console
// change to your desired server and port number
getIPs(function (ip) {
console.log(ip);
}, "stun:stun.l.google.com", 19302);

getIPs(function (ip) {
console.log(ip);
}, "stun:stun.voippro.com", 3478);

getIPs(function (ip) {
console.log(ip);
}, "stun:stun.voipraider.com", 3478);
--------------------------------------------------------------------------

在Chrome中挂线路,F12执行上述代码,返回NAT出口公网IP,没有返回内网IP。在
Firefox ESR中挂线路,F12执行上述代码,没有返回任何IP,无论怎么调整设置,俱
如此,与前面其他测试结论相符,至少我的Firefox ESR够结实。

2) 安全北北截图中的代码

下面是「安全北北」提供的图片中的代码,我稍微改了两处,一是调console.log(),
二是pc['setLocalDescription']['sdp']有可能是undefined,我用了"?.",之后可
用F12执行之。

--------------------------------------------------------------------------
<script>
{
const RTCPeerConnection = window['RTCPeerConnection'] || window['mozRTCPeerConnection'] || window['webkitRTCPeerConnection'],
ips = {},
print = (id, str) => {
const address = (/([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/ ['exec'](str) || [])[1];
if (ips[id] = ips[id] || [], address && -1 === ips[id]['indexOf'](address)) {
ips[id]['push'](address);
console.log(address);
// const div = window["document"]['createElement']("div");
// div['textContent'] = address;
// const parent = window["document"]['getElementById'](id);
// "null" === parent['textContent'] && (parent['textContent'] = ""), parent['appendChild'](div), append(address)
}
},
ip = (service, port = 3478) => {
if (void 0 != RTCPeerConnection) {
const pc = new RTCPeerConnection({
iceServers: [{
urls: "stun:" + service + ":" + port
}]
});
pc['onicecandidate'] = ice => {
ice['candidate'] && print(service, ice['candidate']['candidate'])
}, pc['createDataChannel'](""), pc['createOffer'](result => {
result['sdp']['split']("\n")['forEach'](line => {
-1 !== line['indexOf']("candidate") && print(service, line)
}), pc['setLocalDescription'](result, () => {}, () => {})
}, () => {}), setTimeout(() => {
pc['setLocalDescription'] && pc['setLocalDescription']['sdp']?.['split']("\n")['filter'](l => 0 ===
l['indexOf']("a=candidate:"))['forEach'](line => print(service, line))
}, 1e3)
}
};
ip("stun4.l.google.com", 19302), ip("stun.voippro.com"), ip("stun.voipraider.com")
}
</script>
--------------------------------------------------------------------------

十分不习惯JavaScript代码风格,改成下面这样稍微增加点可读性。

--------------------------------------------------------------------------
const RTCPeerConnection = window['RTCPeerConnection'] || window['mozRTCPeerConnection'] || window['webkitRTCPeerConnection'];
const ips = {};

const print = (id, str) => {
const address = (/([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/ ['exec'](str) || [])[1];
if (ips[id] = ips[id] || [], address && -1 === ips[id]['indexOf'](address)) {
ips[id]['push'](address);
console.log(address);
}
};

const ip = (service, port = 3478) => {
if (void 0 != RTCPeerConnection) {
const pc = new RTCPeerConnection({
iceServers: [{
urls: "stun:" + service + ":" + port
}]
});
pc['onicecandidate'] = ice => {
ice['candidate'] && print(service, ice['candidate']['candidate'])
};
pc['createDataChannel']("");
pc['createOffer'](result => {
result['sdp']['split']("\n")['forEach'](line => {
-1 !== line['indexOf']("candidate") && print(service, line)
});
pc['setLocalDescription'](result, () => {}, () => {});
}, () => {});
setTimeout(() => {
pc['setLocalDescription'] && pc['setLocalDescription']['sdp']?.['split']("\n")['filter'](l => 0 ===
l['indexOf']("a=candidate:"))['forEach'](line => print(service, line));
}, 1e3);
}
};

ip("stun4.l.google.com", 19302);
ip("stun.voippro.com");
ip("stun.voipraider.com");
--------------------------------------------------------------------------

我这都是在Chrome中实测过的,不是理论可行版本。

2.1) hackjie网页内容分析 (404)

「安全北北」提供的截图内容应该源自hackjie网页

https://www.hackjie.com/tracking (404)

该网页禁止Ctrl-U查看页面源代码,禁止F12呼出"开发者工具"。这种可通过菜单调
出"开发者工具",Elements->Copy->Copy outerHTML,得到网页源码。

在网页源码中搜"<script",有多处,其中一处正好位于"匿名代理追踪"上方

--------------------------------------------------------------------------
<script>{const RTCPeerConnection=window['\x52\x54\x43\x50\x65\x65\x72\x43\x6f\x6e\x6e\x65\x63\x74\x69\x6f\x6e']||window['\x6d\x6f\x7a\x52\x54\x43\x50\x65\x65\x72\x43\x6f\x6e\x6e\x65\x63\x74\x69\x6f\x6e']||window['\x77\x65\x62\x6b\x69\x74\x52\x54\x43\x50\x65\x65\x72\x43\x6f\x6e\x6e\x65\x63\x74\x69\x6f\x6e'],ips={},print=(id,str)=>{const address=(/([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/['\x65\x78\x65\x63'](str)||[])[1];if(ips[id]=ips[id]||[],address&&-1===ips[id]['\x69\x6e\x64\x65\x78\x4f\x66'](address)){ips[id]['\x70\x75\x73\x68'](address);const div=window["\x64\x6f\x63\x75\x6d\x65\x6e\x74"]['\x63\x72\x65\x61\x74\x65\x45\x6c\x65\x6d\x65\x6e\x74']("\x64\x69\x76");div['\x74\x65\x78\x74\x43\x6f\x6e\x74\x65\x6e\x74']=address;const parent=window["\x64\x6f\x63\x75\x6d\x65\x6e\x74"]['\x67\x65\x74\x45\x6c\x65\x6d\x65\x6e\x74\x42\x79\x49\x64'](id);"\x6e\x75\x6c\x6c"===parent['\x74\x65\x78\x74\x43\x6f\x6e\x74\x65\x6e\x74']&&(parent['\x74\x65\x78\x74\x43\x6f\x6e\x74\x65\x6e\x74']=""),parent['\x61\x70\x70\x65\x6e\x64\x43\x68\x69\x6c\x64'](div),append(address)}},ip=(service,port=3478)=>{if(void 0!==RTCPeerConnection){const pc=new RTCPeerConnection({iceServers:[{urls:"\x73\x74\x75\x6e\x3a"+service+"\x3a"+port}]});pc['\x6f\x6e\x69\x63\x65\x63\x61\x6e\x64\x69\x64\x61\x74\x65']=ice=>{ice['\x63\x61\x6e\x64\x69\x64\x61\x74\x65']&&print(service,ice['\x63\x61\x6e\x64\x69\x64\x61\x74\x65']['\x63\x61\x6e\x64\x69\x64\x61\x74\x65'])},pc['\x63\x72\x65\x61\x74\x65\x44\x61\x74\x61\x43\x68\x61\x6e\x6e\x65\x6c'](""),pc['\x63\x72\x65\x61\x74\x65\x4f\x66\x66\x65\x72'](result=>{result['\x73\x64\x70']['\x73\x70\x6c\x69\x74']("\n")['\x66\x6f\x72\x45\x61\x63\x68'](line=>{-1!==line['\x69\x6e\x64\x65\x78\x4f\x66']("\x63\x61\x6e\x64\x69\x64\x61\x74\x65")&&print(service,line)}),pc['\x73\x65\x74\x4c\x6f\x63\x61\x6c\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e'](result,()=>{},()=>{})},()=>{}),setTimeout(()=>{pc['\x6c\x6f\x63\x61\x6c\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e']&&pc['\x6c\x6f\x63\x61\x6c\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e']['\x73\x64\x70']['\x73\x70\x6c\x69\x74']("\n")['\x66\x69\x6c\x74\x65\x72'](l=>0===l['\x69\x6e\x64\x65\x78\x4f\x66']("\x61\x3d\x63\x61\x6e\x64\x69\x64\x61\x74\x65\x3a"))['\x66\x6f\x72\x45\x61\x63\x68'](line=>print(service,line))},1e3)}};ip("\x73\x74\x75\x6e\x34\x2e\x6c\x2e\x67\x6f\x6f\x67\x6c\x65\x2e\x63\x6f\x6d",19302),ip("\x73\x74\x75\x6e\x2e\x76\x6f\x69\x70\x70\x72\x6f\x2e\x63\x6f\x6d"),ip("\x73\x74\x75\x6e\x2e\x76\x6f\x69\x70\x72\x61\x69\x64\x65\x72\x2e\x63\x6f\x6d")}</script>
--------------------------------------------------------------------------

这段脚本大量使用\xHH转义序列,就是下面这种操作

$ python3 -c "import sys;print(''.join([f'\x5cx{ord(c):02x}' for c in sys.argv[1]]))" something
\x73\x6f\x6d\x65\x74\x68\x69\x6e\x67

将<script>内容存到some.js中,执行如下命令

echo -ne $(cat some.js)

得到

--------------------------------------------------------------------------
<script>{const RTCPeerConnection=window['RTCPeerConnection']||window['mozRTCPeerConnection']||window['webkitRTCPeerConnection'],ips={},print=(id,str)=>{const address=(/([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/['exec'](str)||[])[1];if(ips[id]=ips[id]||[],address&&-1===ips[id]['indexOf'](address)){ips[id]['push'](address);const div=window["document"]['createElement']("div");div['textContent']=address;const parent=window["document"]['getElementById'](id);"null"===parent['textContent']&&(parent['textContent']=""),parent['appendChild'](div),append(address)}},ip=(service,port=3478)=>{if(void 0!==RTCPeerConnection){const pc=new RTCPeerConnection({iceServers:[{urls:"stun:"+service+":"+port}]});pc['onicecandidate']=ice=>{ice['candidate']&&print(service,ice['candidate']['candidate'])},pc['createDataChannel'](""),pc['createOffer'](result=>{result['sdp']['split']("
")['forEach'](line=>{-1!==line['indexOf']("candidate")&&print(service,line)}),pc['setLocalDescription'](result,()=>{},()=>{})},()=>{}),setTimeout(()=>{pc['localDescription']&&pc['localDescription']['sdp']['split']("
")['filter'](l=>0===l['indexOf']("a=candidate:"))['forEach'](line=>print(service,line))},1e3)}};ip("stun4.l.google.com",19302),ip("stun.voippro.com"),ip("stun.voipraider.com")}</script>
--------------------------------------------------------------------------

有个在线网站,可以直接处理转义序列并美化输出

https://beautifier.io

选中"Unescape printable chars encoded as \xNN or \uNNNN",将网页源码中的
JavaScript贴进去,得到可读性更好的输出

--------------------------------------------------------------------------
<script > {
const RTCPeerConnection = window['RTCPeerConnection'] || window['mozRTCPeerConnection'] || window['webkitRTCPeerConnection'],
ips = {},
print = (id, str) => {
const address = (/([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/ ['exec'](str) || [])[1];
if (ips[id] = ips[id] || [], address && -1 === ips[id]['indexOf'](address)) {
ips[id]['push'](address);
const div = window["document"]['createElement']("div");
div['textContent'] = address;
const parent = window["document"]['getElementById'](id);
"null" === parent['textContent'] && (parent['textContent'] = ""), parent['appendChild'](div), append(address)
}
},
ip = (service, port = 3478) => {
if (void 0 !== RTCPeerConnection) {
const pc = new RTCPeerConnection({
iceServers: [{
urls: "stun:" + service + ":" + port
}]
});
pc['onicecandidate'] = ice => {
ice['candidate'] && print(service, ice['candidate']['candidate'])
}, pc['createDataChannel'](""), pc['createOffer'](result => {
result['sdp']['split']("\n")['forEach'](line => {
-1 !== line['indexOf']("candidate") && print(service, line)
}), pc['setLocalDescription'](result, () => {}, () => {})
}, () => {}), setTimeout(() => {
pc['localDescription'] && pc['localDescription']['sdp']['split']("\n")['filter'](l => 0 === l['indexOf']("a=candidate:"))['forEach'](line => print(service, line))
}, 1e3)
}
};ip("stun4.l.google.com", 19302),
ip("stun.voippro.com"),
ip("stun.voipraider.com")
} < /script>
--------------------------------------------------------------------------

我不懂WEB前端的七七八八,将就着分析一下,见笑。

后来bluerust提及某javascript反混淆工具,第二个URL是online版本

https://github.com/relative/synchrony
https://deobfuscate.relative.im/

3) 极简示例

WebRTC Fingerprinting + Media Device Fingerprinting
https://web-tracking.allenchou.cc/docs/browser-fingerprinting/techniques/webrtc-and-media-device-fingerprinting/

--------------------------------------------------------------------------
var pc = new RTCPeerConnection({
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
});

//create a bogus data channel
pc.createDataChannel("");

// create offer and set local description
pc.createOffer(function(sdp) {
sdp.sdp.split('\n').forEach(function(line) {
if (line.indexOf('candidate') < 0) return;
line.match(ipRegex).forEach(console.log);
});

pc.setLocalDescription(sdp, () => {}, () => {});
}, () => {});

//listen for candidate events
pc.onicecandidate = function(ice) {
console.log(ice?.candidate?.candidate);
};
--------------------------------------------------------------------------

输出形如

candidate:725477399 1 udp 2113937151 331935e4-eaf2-4cb1-a4c9-7de89b1fd498.local 49908 typ host generation 0 ufrag Wwjk network-cost 999
candidate:440841444 1 udp 1677729535 x.x.x.x 49908 typ srflx raddr 0.0.0.0 rport 0 generation 0 ufrag Wwjk network-cost 999

其中"x.x.x.x"即NAT出口IP。"*.local"是mDNS标识符,2022年之前此处是内网IP。
"typ host"即"host candidate","typ srflx"即"server reflexive candidate"。
2022年之前,"raddr 0.0.0.0 rport 0"处应该是内网IP与端口,现在都是零了。

4) Public STUN Server List

github上至少有两份

https://gist.github.com/mondain/b0ec1cf5f60ae726202e
https://github.com/pradt2/always-online-stun

5) mDNS安全改进

https://getstream.io/blog/webrtc-ip-leaks

过去,很容易从"ICE candidate"中析取内网IP

--------------------------------------------------------------------------
const pc = new RTCPeerConnection();
pc.onicecandidate = e => {
if (e.candidate) {
console.log(e.candidate.candidate.split(" ")[4]);
}
}
pc.createOffer({offerToReceiveAudio: true})
.then(offer => pc.setLocalDescription(offer));
--------------------------------------------------------------------------

但到了2022年,如上代码不再返回内网IP,而是返回这种标识符

b9adb5e2-13ab-4666-ad74-926bc9c2483c.local

这是mDNS (Multicast DNS)地址。之所以出现此种变化,正是为了阻止泄露内网IP。
mDNS的用途类似DNS,将主机名解析成IP。但mDNS没有DNS Server,每次需要解析主
机名时,发送多播查询。局域网内所有参与者都将收到多播查询,但只有匹配者产生
多播响应。所有参与者将收到并缓存多播响应,以供后续使用。WebRTC试图以最优线
路连接对端,比如能P2P时,绝不走中转。若通信双方在同一子网,WebRTC将用内网
IP通信。但这样会泄露内网IP,后来的安全改进就是用mDNS标识符代替内网IP。

--------------------------------------------------------------------------
Multicast DNS
https://tools.ietf.org/html/rfc6762
https://datatracker.ietf.org/doc/html/rfc6762

Using Multicast DNS to protect privacy when exposing ICE candidates
https://datatracker.ietf.org/doc/draft-ietf-rtcweb-mdns-ice-candidates/
--------------------------------------------------------------------------

☆ UDP层面的STUN通信报文

1) Wireshark抓包

假设JavaScript用了"stun.l.google.com:19302",用Wireshark抓包

ip host ( 172.217.211.127 or 172.217.213.127 ) and udp port 19302

此处假设FQDN解析到上述IP之一,实际操作时临时解析一下好了

F12执行JavaScript触发STUN通信,看到

Binding Request
Binding Success Response XOR-MAPPED-ADDRESS: <srcip>:<srcport>

有些Binding Request比较复杂,会带Attributes。在WebRTC语境中抓到的,多是不
带Attributes的最简形式Binding Request。

2) XOR-MAPPED-ADDRESS

参看

Session Traversal Utilities for NAT (STUN) - [2020-02]
https://www.rfc-editor.org/rfc/rfc8489.html
14.2. XOR-MAPPED-ADDRESS

同样的Binding Request,其Binding Success Response可能不同,STUN Server可能
返回MAPPED-ADDRESS或XOR-MAPPED-ADDRESS,二者区别是,前者包含明文IP+PORT,
后者包含XOR过的IP+PORT,后者并不是加密,只是混淆,XOR Key也在响应报文中。

用Wireshark抓IPv4的STUN通信报文,以下是一次STUN响应报文示例

--------------------------------------------------------------------------
Message Cookie : 21 12 a4 42
Attribute Type : 00 20 (XOR-MAPPED-ADDRESS)
Port (XOR-d) : 24 30 (1314)
IP (XOR-d) : 59 6b de 39 (120.121.122.123)
--------------------------------------------------------------------------

"Message Cookie"就是XOR Key

(24 30) xor (21 12) => (05 22) => 1314
(59 6b de 39) xor (21 12 a4 42) => (78 79 7a 7b) => 120.121.122.123

3) Message Cookie: 2112a442

参看

RFC 8489
5. STUN Message Structure

现在抓STUN通信报文,基本上都能看到

Message Cookie: 2112a442

RFC 8489要求该值必须如此。STUN Server收到Cookie为此值的请求时,假设STUN
Client理解RFC 5389增加上来的某些Attributes。4字节Cookie变了也没事,STUN
Server有向后兼容性处理,会产生旧格式的明文MAPPED-ADDRESS响应,Wireshark会
识别成CLASSIC-STUN。

下面第一例用"21 12 a4 42",第二例用了"51 20 13 14",分别抓包观察,加深理解

$ echo -ne "\x00\x01\x00\x00\x21\x12\xa4\x42\x78\x47\x48\x42\x78\x61\x4c\x66\x36\x43\x43\x6d" | nc -w 30 -u -n 172.217.211.127 19302 | xxd -g 1
(略)
(Ctrl-C)

$ echo -ne "\x00\x01\x00\x00\x51\x20\x13\x14\x78\x47\x48\x42\x78\x61\x4c\x66\x36\x43\x43\x6d" | nc -w 30 -u -n 172.217.211.127 19302 | xxd -g 1
(略)
(Ctrl-C)

4) 寻找STUN Server

STUN Server是可以批量远程扫描的,空间测绘引擎可以搜索STUN Server,比如

https://fofa.info/result?qbase64=KHBvcnQ9IjM0NzgiIHx8IHBvcnQ9IjE5MzAyIikgJiYgY291bnRyeT1VUw%3D%3D

fofa for

(port="3478" || port="19302") && country=US

5) PFW阻断周知STUN端口

在Win10中wf.msc,新建出站规则,阻断3478、19302/UDP出站报文,侦听这些端口的
STUN Server将被阻断。

netsh.exe advfirewall firewall add rule name="Block STUN Server" dir=out action=block profile=any protocol=udp localip=any remoteip=any localport=any remoteport=3478,19302

netsh.exe advfirewall firewall show rule name="Block STUN Server" dir=out verbose

Rule Name: Block STUN Server
----------------------------------------------------------------------
Enabled: Yes
Direction: Out
Profiles: Domain,Private,Public
Grouping:
LocalIP: Any
RemoteIP: Any
Protocol: UDP
LocalPort: Any
RemotePort: 3478,19302
Edge traversal: No
InterfaceTypes: Any
Security: NotRequired
Rule source: Local Setting
Action: Block
Ok.

netsh.exe advfirewall firewall set rule name="Block STUN Server" new enable=yes // 启用
netsh.exe advfirewall firewall set rule name="Block STUN Server" new enable=no // 禁用

Linux可用iptables

iptables -A OUTPUT -p udp --dport 3478 -j DROP
iptables -A OUTPUT -p udp --dport 19302 -j DROP

之后挂线路访问测试URL检验效果,即使未动用浏览器内建配置或外部扩展,NAT出口
IP也不再泄露

https://ip8.com/webrtc-test

PFW不靠谱,STUN Server可在任意IP侦听任意端口,只能阻断周知服务器周知端口,
因此很可能侧漏,进而导致NAT出口IP泄露。用PFW,不只是侧漏,有可能误伤,你不
知道被阻断的目标端口是否正好用于其他正常通信过程。

但PFW有意义,不必每种浏览器装扩展,不必寻找其他内建配置。非浏览器应用也可
利用STUN Server获取NAT出口IP,它们不一定有内建配置、外部扩展可用,此时PFW
就派上用场。

手机用户理论上也可配置PFW,有此需求又不知如何操作者,问ChatGPT吧。

☆ Media Device Fingerprinting

此事与泄露源IP无关,但与WebRTC有关,顺带记之

WebRTC Fingerprinting + Media Device Fingerprinting
https://web-tracking.allenchou.cc/docs/browser-fingerprinting/techniques/webrtc-and-media-device-fingerprinting/

--------------------------------------------------------------------------
navigator.mediaDevices.enumerateDevices()
.then((devices) => {
devices.forEach((device) => {
console.log(`${device.kind}: ${device.label ? device.label : "no label"}, id=${device.deviceId}`);
});
})
--------------------------------------------------------------------------

F12执之上述代码,若浏览器未允许目标网站访问客户端摄像头、麦克风,可能看到

audioinput: no label, id=
audiooutput: no label, id=
videoinput: no label, id=

若允许,将看到

audioinput: <audio label>, id=<...>
videoinput: <video label>, id=<...>

这类信息可用作客户端指纹成份。假设用浏览器访问某URL,突然提示要访问摄像头、
麦克风,务必拒绝。

上文作者指出,为了降低媒体列表指纹风险,标准制定者做了一些努力。id并不固定,
不同origin下有不同id;清除cookie会导致重新生成id;无痕模式每个session有自
己的id,并于session结束时清空。因此,id最多用于"same-site tracking",无法
用于"cross-site tracking",其存活时间最多同cookie。不过需要注意,id只有通
过浏览器内置方法清除cookie时会被顺便reset,若用第三方扩展清cookie,id会被
保留。虽然id不固定,label是固定的。有些线上会议网站本来就要申请摄像头、麦
克风权限,无法阻止这类网站获取label。

☆ 其他讨论

不少人提及几个非主流浏览器的特性,那个我知道,但目录里没有它们,为什么?这
涉及一种普惠信念,不反复解释了。

自然语言描述时,有各种妥协,考虑到受众技术水平、技术领域千差万别,刻意没有
严格定义A机出口IP是什么意思,否则需要排列组合式细分说明。本文不对抗浏览器
指纹画像,仅聚焦朴素的科学裸奔问题。最常见的科学裸奔方式是,加密SOCKS5代理。
在全局代理、全局VPN、全局路由等技术介入的情况下,表面现象有所不同。比如,
有多名网友反馈,用WireGuard似乎没有问题。不只是WG,可能还能其他七七八八的
路数,也说无问题,再就是手机用户的反馈各不相同。针对此类反馈,从程序员角度
做一次性补充说明。

会协议分析的,只需要Wireshark抓STUN通信报文,再对一下A机与S机之间的网络拓
扑,就能覆盖所有组合,这是万变不离其宗的终极分析方案,不需要黑盒测试。漏没
漏都有合理解释,且避免了自然语言带来的歧义,比如不必再纠结什么叫A机出口IP。

☆ FAQ

Q:

到底是WebRTC泄露源IP,还是STUN通信获取源IP?

A:

在浏览器语境下,无法直接发起STUN通信,但JavaScript WebRTC API可以间接触发
STUN通信并从响应中获取公网源IP。2022年之前,JavaScript WebRTC API还可以直
接获取内网IP,此过程与STUN通信无关。

Q:

A机用浏览器挂线路,串行经B0、B1、BN访问C机页面,C机有可能获取B0、B1、BN的
出口IP吗?

A:

C机页面嵌入的JavaScript在A机浏览器中执行,A机与S机上的STUN Server直接通信,
此过程不经过B0、B1、BN。S机向A机返回后者出口IP,A机向C机提交自己的出口IP。
C机同时看到A机出口IP及BN出口IP,C机无法看到B0、B1出口IP,中间结点未暴露。

之前有家伙瞎特么吹,说该机制可将线路上B0、B1、BN出口IP全数暴露给C机,真能
扯淡。

☆ 后记

「安全北北」就此事评论,还好我在外网都是只围观,从不评论或转发,也不主动发
布任何吐槽信息。我也是这样,主要以获取技术资源为目的,不涉Z。虽然不懂WEB安
全,今晚之前并不知道这个天坑等着我,但我在此类问题上谨慎多疑,小心使得万年
船,天晓得老大哥在哪儿看着你。之前和DY看一个专利,他说客户端指纹画像技术很
成熟。

今晚「安全北北」这条信息对我太重要了,名列近10年内的Top 1。得亏我关注着他
的微博,我为啥关注呢,因为我知道这些都是坏人,坏人知道的歪招多,多关注坏人,
错不了。别看他经常发模型训练出来的妹子图,10年发这一条微博我就赚到了。感谢!

要还没明白发生啥了,再多说一句,「你以为你在科学上网,其实你在科学裸奔」。

检测泄露网站
https://ip.voidsec.com/
https://surfshark.com/zh/webrtc-leak-test
https://ip8.com/webrtc-test

匿名代理追踪真实ip


https://dnsleaktest.org/dns-leak-test
https://www.ipdog.io/zh/ipcheck/

版权声明:
作者:agsec
链接:https://agsec.xyz/archives/158
来源:AG安全团队博客
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>