Skip to main content

Hardware fingerprinting with a web browser

While I was updating jQuery Fancy File Uploader to support recording video and audio from webcams, microphones, and other media sources, I ran into an interesting web browser security related problem that appears to affect all major web browsers that support the MediaRecorder API.

From the Developer Tools console of your favorite web browser, run this one-liner:
navigator.mediaDevices.enumerateDevices().then(function(devices) { console.log(devices); });
Then go to another page on the same domain and repeat the process. Try it in a new tab. As of this date, in both Firefox and Chrome (untested in Edge), it looks like the 'deviceId' of each attached hardware audio/video device remains static across a domain during a single browser session. Since a lot of people leave their web browsers open for long periods of time, this information can be used to track a user's activity across a single domain without using cookies or localStorage. The user is also not alerted to the fact that the website is accessing information about their hardware.

A search for fingerprinting via this mechanism turns up two tickets for both web browsers:

Both tickets are closed with the claim that this issue has been fixed. Chromium looks like it had fixed the issue at one point but is no longer the case. Firefox only partially fixed the issue because the setting for 'privacy.resistFingerprinting' defaults to false, which is the opposite of what users probably expect. That is, most users assume their web browser runs in the most secure way possible, which apparently isn't true.

The correct solution is to do two things: First, alert the user that a webpage has requested information about devices attached to the machine whenever that happens. Second, every time a device ID list is requested, regenerate fake device IDs and if the user approves a MediaRecorder request with a specific fake device ID, then it maps to the real device behind the scenes.

There's also never a valid need to get a device list in the first place. The user is going to choose a device from a popup that appears asking which device to use. There are only two reasons someone might call this API: To determine if there is a device that supports video and/or audio to display a UI element such as a button OR to fingerprint the user. In neither case is the deviceId actually necessary.

Actually, for the first case, I wandered down that path myself. I figured, "Why show a button for a microphone if the user doesn't have a microphone attached?" I did that but then reverted my code back to always showing the button. It allows the user to click it, realize that they could plug a microphone in and the button would work without having to reload the page. I could poll for newly attached devices to show/hide buttons accordingly, but that's a lot of extra work and wasted CPU for minimal gain. So even the first use-case presented above is on shaky ground - the user already knows if they have a microphone/webcam attached and will or won't use a given feature when presented with the option. IMO, there is no valid reason for the enumerateDevices() API to exist but should probably be altered to always report a set of fixed deviceIds that every browser presents so that existing code won't break.

Detaching all hardware recording devices isn't possible these days as most hardware has integrated solutions. Desktop PCs are probably the only piece of hardware that won't have a hardware device attached. Even then that doesn't preclude things like screen recording software from presenting itself to the browser as a video capture device.

Fortunately for users who have both Ghostery and AdBlock Plus/uBlock Origin installed, any attempt to send hardware fingerprints somewhere has a pretty good chance of being stopped.