Puppeteer-extra: Add evasion for navigator.mediaDevices

Created on 3 Jan 2020  路  14Comments  路  Source: berstend/puppeteer-extra

Example of how a site (AliExpress.com) uses this to fingerprint browsers:

async function getMediaDeviceIds() {
  if (/Android/.test(navigator.userAgent) || !navigator.mediaDevices?.enumerateDevices) return null;

  const devices = await navigator.mediaDevices.enumerateDevices();
  const deviceIds = devices.map(mediaDevice => mediaDevice.deviceId);
  return deviceIds?.join(",") ?? null;
}
enhancement stealth 銑欙笍

Most helpful comment

I think based on the fingerprinting logic that AliExpress uses (the one that is readable, they also have one I still am not able to deobfuscate), this would do, but according to this article it seems like they are labelled as fakes. I'll spend some more time on this later this week so might get something usable.

All 14 comments

Interesting.

(async () => {
    const devices = await navigator.mediaDevices.enumerateDevices();
    const deviceIds = devices.map(mediaDevice => mediaDevice.deviceId);
    console.log(deviceIds)
})()

In regular Chrome this will spit out a bunch of 64 character ids. Will need to read up a bit on how they're generated so we can spoof them. :)

Mhm, are we sure this is a problem?

const puppeteer = require("puppeteer")

puppeteer
  .launch({ headless: true, args: ["--remote-debugging-port=9222"] })
  .then(async browser => {
    const page = await browser.newPage()
    await page.goto("https://example.com")

    const data = await page.evaluate(async () => {
      const devices = await navigator.mediaDevices.enumerateDevices()
      const deviceIds = devices.map(mediaDevice => mediaDevice.deviceId)
      return deviceIds
    })

    console.log(data)
    await browser.close()
  })

This will print ids as expected (also changing each run):

[
  'default',
  '1c3dcd6602054cd782d424118dec84d38f17f7984a9d7ad000850a32b05b8dd7',
  '1ef49c0f08f40e27be3c7d385617a22c72808281b975386095458b897fd898e1',
  '568e48cfcb730a3fc529d1d47b7d2ea47f8329d3873150dd349faf74df229e20',
  'default',
  'e719ef731ddf7a689b52b701b58e328234cda18409eb692e6842d919a7132949',
  '450fbd67757957e5186d27a480bf554900eb68357fcbbc502298d02c1111f5bc'
]
[
  'default',
  'c6ade38461e827c1a5823ad72498b8ef4f7bdcc63630dd232130b07eb551781f',
  '75d9e3e730bc4e5feddf636f907978bfdd6902fa63ae55fe7a1e85d8e3442d7c',
  '0b2a41e4504a05973cc17dada0c9f655a36033970506ad807fdd755d6b1df718',
  'default',
  '8f0338ee6a3f8f7471b43d94f8ae5c46207c8bfd07c409b88408fbcc23dee2e9',
  '6bc57060f1e80d6c44c80db1a3494b205c56bd3b5ca707908dd489a3f3cafb56'
]

I think it's not only the deviceIds itself, but the number of them, and it allowing to fingerprint the device. For instance, on the browserless/base image, the number of mediaDevices is zero, which isn't something one might want.

I just read about this:

google-chrome --use-fake-device-for-media-stream --use-fake-ui-for-media-stream

Haven't tried it myself but this might solve this issue on hosts without devices.

I think based on the fingerprinting logic that AliExpress uses (the one that is readable, they also have one I still am not able to deobfuscate), this would do, but according to this article it seems like they are labelled as fakes. I'll spend some more time on this later this week so might get something usable.

@jeroenvisser101 keep us posted with your findings :) Happy to improve the stealth plugin when needed.

In case we fully need to mock navigator.mediaDevices from scratch we can use navigator.plugins as reference, where we did a similar thing:
https://github.com/berstend/puppeteer-extra/blob/master/packages/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/index.js

I just read about this:

google-chrome --use-fake-device-for-media-stream --use-fake-ui-for-media-stream
Haven't tried it myself but this might solve this issue on hosts without devices.

but according to this article, it seems like they are labeled as fakes.

This is the media devices shown by https://amiunique.org/fp when using @berstend suggestion:

audioinput (Fake Default Audio Input)
audioinput (Fake Audio Input 1)
audioinput (Fake Audio Input 2)
videoinput (fake_device_0)
audiooutput (Fake Default Audio Output)
audiooutput (Fake Audio Output 1)
audiooutput (Fake Audio Output 2)

Contrast this to what is returned when running in my standard Chrome browser:


@TheBestMoshe can you try again with just --use-fake-device-for-media-stream?

Also if someone could extract the exact code amiunique.org is using here that'd be helpful and accelerate the discussion :-)

PS: An alternative to spoofing this through chrome args or JS could be to create fake devices in a docker container

It seems like a recent Chrome(ium) release removed the deviceID and label properties as returned by await navigator.mediaDevices.enumerateDevices(). Or not completely removed, but they're empty - both on my tests in macOS and Linux with a beta build of Chrome.

@berstend When I run it without any of the fake device flags I get similar results to a regular Chrome browser.

Chrome browser:


Chromium with Puppeteer Stealth:

audiooutput
audiooutput

can you try again with just --use-fake-device-for-media-stream?

Results:

audioinput
audioinput
videoinput
audiooutput
audiooutput
audiooutput

So we're good then? :-) We can't control the number of devices using this method but as there's fluctuation in the wild as well this shouldn't be an issue.

I see the result is different (detailed info) if you first run await navigator.mediaDevices.getUserMedia({audio: true, video: true}). But that spawns a permissions popup. Adding --use-fake-ui-for-media-stream will auto-accept the permissions hence the leaking info. So: don't use --use-fake-ui-for-media-stream, just --use-fake-device-for-media-stream or use something like pulseaudio with a dummy device as @berstend mentioned.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

engineswap picture engineswap  路  7Comments

ukrexpo picture ukrexpo  路  5Comments

KorrGoFF picture KorrGoFF  路  7Comments

jhgeluk picture jhgeluk  路  4Comments

AsianPotato picture AsianPotato  路  8Comments