速卖通网页用 WebAudio 指纹“霸占”多点连接蓝牙耳机
本文为编译导读,内容基于 Matt Callaghan 于 2026/08/20 发表的英文文章,并非逐字全文翻译。 原文:AliExpress webpage keeping multipoint Bluetooth headphones active with WebAudio fingerprinting
一句话概括
打开速卖通(AliExpress)首页后,页面里的反爬 / 风控脚本会悄悄创建 WebAudio 音频图并连接到系统音频输出。虽然音量为零、根本听不到,但它让浏览器的音频通道一直处于“活跃”状态,结果导致多点连接(multipoint)蓝牙耳机无法切回手机。
现象:没放任何声音,耳机却被电脑“占住”了
作者用的是一副支持多点连接的蓝牙耳机,同时连着电脑和手机。正常情况下,电脑没声音时,手机一播放,耳机就会自动切过去。
但只要电脑浏览器里开着速卖通页面,手机的声音就是过不来;关掉这个标签页,一切恢复正常。页面上既没有视频,也没有任何可见的媒体播放。Firefox 和 Chrome 下都能复现。
排查过程
常规检查都一无所获:
- 页面中没有
<audio>/<video>元素在播放 - 没有明显的媒体相关调用
- Media Session 里也没有元数据
于是作者换了思路:对 Web Audio API 做插桩。
- 包装
AudioContext构造函数,每当页面创建音频上下文时就记录下来; - 包装
AudioNode.prototype.connect(),观察哪些节点最终连到了destination(即系统扬声器输出)。
一个最小化的插桩思路大致如下(示意代码,非原文):
const NativeAC = window.AudioContext
window.AudioContext = function (...args) {
const ctx = new NativeAC(...args)
console.trace('[AudioContext created]', ctx)
return ctx
}
window.AudioContext.prototype = NativeAC.prototype
const nativeConnect = AudioNode.prototype.connect
AudioNode.prototype.connect = function (target, ...rest) {
if (target instanceof AudioDestinationNode) {
console.trace('[connect -> destination]', this)
}
return nativeConnect.call(this, target, ...rest)
}结果发现:页面空闲几秒后,会创建两个隐藏的 AudioContext,并且都连到了 destination。
元凶:阿里 AWSC 风控脚本
调用栈指向两个来自阿里 AWSC(安全 / 反滥用工具集)目录的脚本:
| 脚本 | 路径 | 版本 |
|---|---|---|
collina.js | assets.aliexpress-media.com/g/AWSC/uab/... | 1.140.0 |
fireyejs.js | assets.aliexpress-media.com/g/AWSC/fireyejs/... | 1.231.67 |
它们搭建的音频图是经典的 AudioContext 指纹 套路:
锯齿波 OscillatorNode
→ AnalyserNode
→ ScriptProcessorNode
→ GainNode(增益设为 0)
→ AudioContext.destination原理简述:不同设备、系统、浏览器在处理同一段音频信号时,浮点运算结果会有细微差异,把这些输出做哈希,就能得到一个相对稳定的设备标识。
问题在于最后一步——即使增益为 0,只要连到了 destination,浏览器就认为“正在输出音频”,系统音频路径保持开启,蓝牙耳机也就一直认为电脑在播放,自然不会让位给手机。
不止音频:一整套浏览器指纹
除了 WebAudio,这两个脚本还采集了大量信息,包括:
- Canvas 渲染结果、WebGL 能力
- 屏幕尺寸、设备像素比(DPR)
- 硬件参数(CPU 核数、内存等)
- 浏览器插件列表
- 音视频编解码格式支持情况
- WebRTC 行为
- 性能计时数据
- 用户交互行为模式(鼠标、键盘等)
而且代码经过混淆,在用户还没进行任何登录、支付等敏感操作之前就已经在运行了。
解决方案:用 uBlock Origin 屏蔽
作者给出的做法是在 uBlock Origin 的「我的过滤规则」中添加两条规则,仅在速卖通域名下拦截这两个脚本:
||assets.aliexpress-media.com/g/AWSC/uab/*/collina.js$script,domain=aliexpress.com
||assets.aliexpress-media.com/g/AWSC/fireyejs/*/fireyejs.js$script,domain=aliexpress.com作者认为屏蔽是合理的取舍:这些脚本在没有任何告知的情况下对真实硬件产生了干扰,同时收集了大量行为数据;而“静音标签页”这类常规手段对 Web Audio 的这种用法并不奏效。当然,屏蔽风控脚本也可能导致部分功能(如登录验证、下单)触发额外校验,需要自行权衡。
个人思考
这篇文章对前端开发者有几点启发:
- “听不见”不等于“没播放”。
GainNode.gain = 0只是静音,音频管线仍然在跑。如果只是想做离线计算,应该用OfflineAudioContext,它不会连接真实的输出设备,也就不会产生这类副作用。 - 指纹脚本的副作用是可观测的。风控 / 指纹方案往往默认“用户无感”,但它们会实实在在地影响耗电、硬件状态乃至外设行为。
- 插桩原生 API 是排查“幽灵行为”的利器。当 DevTools 面板看不出问题时,包装构造函数和原型方法再打印调用栈,往往能快速定位到真正的调用方。
