Hotdry.

Article

Firefox WebUSB 模拟实现:从 U2F 协议伪裝到 HID 设备固件解析

通过 HID 协议伪装将自定义 USB 设备模拟为 U2F 安全密钥,实现 Firefox 对 WebUSB 设备的访问控制与固件解析。

2026-04-20web

当我们谈论浏览器与硬件设备的交互时,WebUSB API 无疑是近年来最具争议性的技术之一。这项让网页能够直接访问 USB 设备的能力,在提升用户体验的同时也带来了显著的安全风险。然而,截至目前,仅有基于 Chromium 内核的浏览器原生支持 WebUSB,而 Firefox 用户则面临完全无法使用这类硬件的困境。面对这一现状,开源社区涌现出了创新的解决方案:通过 HID 协议伪装,将自定义 USB 设备模拟为 U2F 安全密钥,从而在 Firefox 中实现类似 WebUSB 的功能。

背景:WebUSB 的安全争议与浏览器支持现状

WebUSB API 的设计初衷是让网页能够直接与连接到用户设备的 USB 硬件进行通信。这一特性使得网页可以直接与打印机、扫描仪、开发板等设备交互,无需安装专门的驱动程序或本地应用程序。然而,这种「即插即用」的便利性背后隐藏着深刻的安全隐患。恶意网页可能利用 WebUSB 协议与用户的 USB 设备建立连接,进而实施数据窃取、设备劫持甚至固件篡改等攻击。正因如此,主流浏览器厂商对 WebUSB 的支持态度呈现出明显的分化格局。基于 Chromium 的 Chrome、Edge、Brave 等浏览器已经实现了 WebUSB API,而 Firefox、Safari 等浏览器出于安全考量,选择了更为保守的立场,拒绝在原生层面支持这一标准。

这种浏览器支持的不平衡状态直接导致了用户体验的割裂。对于依赖 WebUSB 的硬件开发者而言,他们不得不为不同浏览器的用户提供差异化的支持策略;对于终端用户来说,则可能面临购买了在某些浏览器中无法正常工作的 USB 设备。正是在这一背景下,社区研究者开始探索替代性的技术方案,以绕过浏览器对 WebUSB 的限制,其中最具代表性的便是利用 U2F 协议进行模拟的技术路线。

核心原理:U2F 协议的数据传输机制

要理解如何利用 U2F 协议实现类似 WebUSB 的功能,首先需要深入了解 U2F(Universal 2nd Factor)协议的工作机制。U2F 是 FIDO 联盟制定的统一双因素认证标准,旨在为在线服务提供更为安全的用户身份验证方式。当用户使用 U2F 设备(如 YubiKey)进行身份验证时,浏览器会通过特定的 JavaScript API 与插入的 USB 设备进行交互,完成挑战 - 响应流程。

关键的技术细节在于 U2F 协议中的两个核心数据结构:密钥句柄(Key Handle)和签名响应(Signature)。密钥句柄是一个不透明的数据块,设备制造商可以在其中存储任意信息,通常包含与用户账号关联的加密标识符。签名响应则包含设备生成的 ECDSA 签名,用于证明设备确实持有与密钥句柄对应的私钥。U2F 协议规范对这两个数据字段的内容并没有严格的格式要求,只要设备能够正确处理认证请求并返回符合规范格式的签名即可正常工作。

正是这种设计上的灵活性,为研究者提供了可乘之机。如果将自定义的数据封装在 U2F 协议的数据字段中,就可以在浏览器与 USB 设备之间建立一条隐蔽的数据传输通道。这条通道虽然名义上用于双因素认证,但实际上可以承载任意的应用层数据,从而实现「挂羊头卖狗肉」的效果 —— 表面上是进行 U2F 认证,实际上是在进行设备控制或数据传输。

实现路径:RP2040 固件与浏览器端的技术细节

具体的技术实现需要固件端和浏览器端的紧密配合。在固件端,研究者使用树莓派 RP2040 微控制器(也就是流行的 Raspberry Pi Pico 开发板)作为硬件平台,编写了专门的 U2F 模拟固件。这段固件的核心逻辑是将被模拟的 USB 设备虚拟为一个 U2F 安全密钥,并妥善处理来自浏览器的 U2F 注册和认证请求。

固件的工作流程可以概括为以下几个关键步骤。首先是设备枚举阶段,RP2040 在接入电脑时会被识别为一个符合 HID 类的 U2F 设备,这是操作系统和浏览器能够正常发现并与之交互的前提。其次是请求处理阶段,当网页通过 JavaScript 的 U2F API 发起认证请求时,固件会解析请求数据并提取其中的应用参数。接下来是数据编码阶段,固件将需要返回给网页的数据(如传感器读数、设备状态等)编码到 U2F 协议的响应字段中。

特别值得注意的是数据编码的两个方向。从设备到网页的数据传输相对直接,固件将数据放入密钥句柄字段,由于该字段对浏览器是完全不透明的,浏览器会将其原封不动地返回给网页,网页端只需解析密钥句柄即可提取数据。从网页到设备的数据传输则更为复杂,需要利用 ECDSA 签名的特殊结构。研究者发现,Firefox 浏览器对 U2F 签名的验证相当宽松,仅进行基本的格式检查而不会验证签名的有效性。Chrome 浏览器虽然会进行范围检查,但同样不会严格验证签名内容。这意味着固件可以在 ASN.1 编码的 ECDSA 签名中嵌入自定义数据,而浏览器会照单全收。

为了让整个过程更加「用户友好」,固件还实现了一个巧妙的小技巧:自动确认用户在场。U2F 协议要求用户实际按下设备上的按钮以确认操作,这是为了防止恶意网页在用户不知情的情况下使用设备。然而,在模拟场景中显然无法要求用户每次操作都按下物理按钮。研究者发现,如果将密钥句柄的开头设置为特定的魔数 0xFEEDFACE,固件就可以自动标记用户已确认,从而绕过这一交互要求。当然,这也会导致浏览器显示一个稍纵即逝的确认对话框,虽然不够优雅,但基本不影响使用体验。

浏览器端:JavaScript API 的调用与数据解析

在浏览器端,开发者需要编写专门的 JavaScript 代码来与模拟的 U2F 设备进行交互。与原生 WebUSB API 相比,U2F API 的接口设计相对简单,主要提供两个核心方法:register 用于注册新的设备,sign 用于发起认证请求。以一个简单的 LED 控制场景为例,网页需要实现的功能是将用户的开 / 关指令发送给 RP2040,并接收设备返回的当前状态。

具体的实现方式是:首先调用 U2F 的 sign 方法,传入应用参数和密钥句柄。在密钥句柄中,网页可以嵌入需要发送给设备的指令数据。设备固件在收到请求后,会解析密钥句柄并执行相应的操作(如点亮或关闭 LED),然后将响应数据编码到返回的签名中。网页端在收到 U2F 响应后,需要解析签名数据结构以提取其中的数据字段。

为了简化开发流程,研究者提供了完整的演示代码,包括预编译的 RP2040 固件文件 u2f-hax.u2f 和一个交互式的 index.html 页面。开发者只需将固件刷写到 Pico 开发板,然后在本地服务器(必须是 HTTPS 或 localhost)环境中打开网页,即可开始实验。通过页面上的按钮,可以实时控制连接在 Pico 上的 LED,并观察设备返回的状态数据。

安全考量与适用边界

任何绕过安全限制的技术方案都需要谨慎对待。首先需要明确的是,这种 U2F 模拟技术并非为了突破浏览器的安全防护而设计,其适用场景应当局限于开发者调试、自定义硬件控制等受控环境。在普通用户的使用场景中,正常的 USB 设备并不会因为这种技术而变得不安全 —— 浏览器对 U2F 设备的访问仍然需要用户明确授权,恶意网页无法在用户不知情的情况下与任意 USB 设备建立连接。

然而,这并不意味着我们可以完全忽视潜在的风险。将自定义数据嵌入 U2F 协议字段的做法,本质上是在利用协议规范中的灵活性空间。如果这种技术被恶意利用,确实可能绕过某些基于协议理解的安全检测机制。因此,对于需要更高安全级别的应用场景,仍然应当优先考虑使用原生支持的 WebUSB API(配合基于 Chromium 的浏览器),或者等待 Firefox 官方提供更为完善的解决方案。

实践参数与开发建议

对于希望尝试这一技术的开发者,以下是一些关键的实践参数和建议。硬件选择方面,推荐使用树莓派 RP2040 及其衍生开发板,如 Raspberry Pi Pico 或 Pico W。固件烧录可以通过 uf2 文件格式直接拖拽到设备的可移动磁盘中完成,无需额外的编程器。开发环境方面,建议使用最新的 Pico SDK 和 CMake 构建系统,以获得完整的调试支持和稳定的编译结果。

在浏览器端开发时,需要注意 U2F API 已经在 Web 标准中演进为 WebAuthn。虽然两者在底层协议上高度兼容,但 WebAuthn 提供了更为丰富的接口和更好的跨平台支持。如果计划在生产环境中部署类似方案,应当仔细测试不同浏览器和操作系统版本之间的兼容性差异。

综上所述,通过 U2F 协议模拟实现 Firefox 中的 WebUSB 功能,是一项极具创意的技术 hack。它不仅为开发者提供了一种在非 Chromium 浏览器中访问 USB 设备的可行路径,也为我们理解浏览器安全模型和协议设计提供了宝贵的案例。虽然这种方法在安全性和用户体验方面还存在一些妥协,但它无疑展示了开源社区在面对技术限制时的创造力和应变能力。

资料来源:Hackaday《Add WebUSB Support To Firefox With A Special USB Device》(2025 年 3 月 15 日)

web