223 lines
7.0 KiB
JavaScript
223 lines
7.0 KiB
JavaScript
|
|
const test = require('ava')
|
||
|
|
|
||
|
|
const {
|
||
|
|
getVanillaFingerPrint,
|
||
|
|
getStealthFingerPrint
|
||
|
|
} = require('../../test/util')
|
||
|
|
const { vanillaPuppeteer, addExtra } = require('../../test/util')
|
||
|
|
|
||
|
|
const Plugin = require('.')
|
||
|
|
const { errors } = require('puppeteer')
|
||
|
|
|
||
|
|
// FIXME: This changed in more recent chrome versions
|
||
|
|
// test('vanilla: videoCard is Google Inc', async t => {
|
||
|
|
// const pageFn = async page => await page.evaluate(() => window.chrome) // eslint-disable-line
|
||
|
|
// const { videoCard } = await getVanillaFingerPrint(pageFn)
|
||
|
|
// t.deepEqual(videoCard, ['Google Inc.', 'Google SwiftShader'])
|
||
|
|
// })
|
||
|
|
|
||
|
|
test('stealth: videoCard is Intel Inc', async t => {
|
||
|
|
const pageFn = async page => await page.evaluate(() => window.chrome) // eslint-disable-line
|
||
|
|
const { videoCard } = await getStealthFingerPrint(Plugin, pageFn)
|
||
|
|
t.deepEqual(videoCard, ['Intel Inc.', 'Intel Iris OpenGL Engine'])
|
||
|
|
})
|
||
|
|
|
||
|
|
test('stealth: customized values', async t => {
|
||
|
|
const pageFn = async page => await page.evaluate(() => window.chrome) // eslint-disable-line
|
||
|
|
const { videoCard } = await getStealthFingerPrint(Plugin, pageFn, {
|
||
|
|
vendor: 'foo',
|
||
|
|
renderer: 'bar'
|
||
|
|
})
|
||
|
|
t.deepEqual(videoCard, ['foo', 'bar'])
|
||
|
|
})
|
||
|
|
|
||
|
|
/* global WebGLRenderingContext */
|
||
|
|
async function extendedTests() {
|
||
|
|
const results = {}
|
||
|
|
|
||
|
|
async function test(name, fn) {
|
||
|
|
const detectionPassed = await fn()
|
||
|
|
if (detectionPassed) console.log(`Chrome headless detected via ${name}`)
|
||
|
|
results[name] = detectionPassed
|
||
|
|
}
|
||
|
|
|
||
|
|
const canvas = document.createElement('canvas')
|
||
|
|
const context = canvas.getContext('webgl')
|
||
|
|
|
||
|
|
await test('descriptorsOK', _ => {
|
||
|
|
const descriptors = Object.getOwnPropertyDescriptors(
|
||
|
|
WebGLRenderingContext.prototype
|
||
|
|
)
|
||
|
|
const str = descriptors.getParameter.toString()
|
||
|
|
return str === `[object Object]`
|
||
|
|
})
|
||
|
|
|
||
|
|
await test('toStringOK', _ => {
|
||
|
|
const str = context.getParameter.toString()
|
||
|
|
return str === `function getParameter() { [native code] }`
|
||
|
|
})
|
||
|
|
|
||
|
|
await test('toStringOK2', _ => {
|
||
|
|
const str = WebGLRenderingContext.prototype.getParameter.toString()
|
||
|
|
return str === `function getParameter() { [native code] }`
|
||
|
|
})
|
||
|
|
|
||
|
|
// Make sure we not reveal our proxy through errors
|
||
|
|
await test('errorOK', _ => {
|
||
|
|
try {
|
||
|
|
return context.getParameter()
|
||
|
|
} catch (err) {
|
||
|
|
return !err.stack.includes(`at Object.apply`)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
// Should not throw (that was old stealth behavior)
|
||
|
|
await test('elementOK', _ => {
|
||
|
|
try {
|
||
|
|
return context.getParameter(123) === null
|
||
|
|
} catch (_) {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
return results
|
||
|
|
}
|
||
|
|
|
||
|
|
test('vanilla: webgl is native', async t => {
|
||
|
|
const pageFn = async page => {
|
||
|
|
// page.on('console', msg => {
|
||
|
|
// console.log('Page console: ', msg.text())
|
||
|
|
// })
|
||
|
|
return await page.evaluate(extendedTests) // eslint-disable-line
|
||
|
|
}
|
||
|
|
const { pageFnResult: result } = await getVanillaFingerPrint(pageFn)
|
||
|
|
|
||
|
|
const wasHeadlessDetected = Object.values(result).some(e => e === false)
|
||
|
|
if (wasHeadlessDetected) {
|
||
|
|
console.log(result)
|
||
|
|
}
|
||
|
|
t.false(wasHeadlessDetected)
|
||
|
|
})
|
||
|
|
|
||
|
|
test('stealth: webgl is native', async t => {
|
||
|
|
const pageFn = async page => await page.evaluate(extendedTests) // eslint-disable-line
|
||
|
|
const { pageFnResult: result } = await getStealthFingerPrint(Plugin, pageFn)
|
||
|
|
|
||
|
|
const wasHeadlessDetected = Object.values(result).some(e => e === false)
|
||
|
|
if (wasHeadlessDetected) {
|
||
|
|
console.log(result)
|
||
|
|
}
|
||
|
|
t.false(wasHeadlessDetected)
|
||
|
|
})
|
||
|
|
|
||
|
|
/**
|
||
|
|
* A very simple method to retrieve the name of the default videocard of the system
|
||
|
|
* using webgl.
|
||
|
|
*
|
||
|
|
* Example (Apple Retina MBP 13): {vendor: "Intel Inc.", renderer: "Intel(R) Iris(TM) Graphics 6100"}
|
||
|
|
*
|
||
|
|
* @see https://stackoverflow.com/questions/49267764/how-to-get-the-video-card-driver-name-using-javascript-browser-side
|
||
|
|
* @returns {Object}
|
||
|
|
*/
|
||
|
|
function getVideoCardInfo(context = 'webgl') {
|
||
|
|
const gl = document.createElement('canvas').getContext(context)
|
||
|
|
if (!gl) {
|
||
|
|
return {
|
||
|
|
error: 'no webgl'
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info')
|
||
|
|
if (debugInfo) {
|
||
|
|
return {
|
||
|
|
vendor: gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL),
|
||
|
|
renderer: gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
error: 'no WEBGL_debug_renderer_info'
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
test('stealth: handles WebGLRenderingContext', async t => {
|
||
|
|
const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())
|
||
|
|
const browser = await puppeteer.launch({ headless: true })
|
||
|
|
const page = await browser.newPage()
|
||
|
|
|
||
|
|
const videoCardInfo = await page.evaluate(getVideoCardInfo, 'webgl')
|
||
|
|
t.is(videoCardInfo.error, undefined)
|
||
|
|
t.is(videoCardInfo.vendor, 'Intel Inc.')
|
||
|
|
t.is(videoCardInfo.renderer, 'Intel Iris OpenGL Engine')
|
||
|
|
})
|
||
|
|
|
||
|
|
test('stealth: handles WebGL2RenderingContext', async t => {
|
||
|
|
const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())
|
||
|
|
const browser = await puppeteer.launch({ headless: true })
|
||
|
|
const page = await browser.newPage()
|
||
|
|
|
||
|
|
const videoCardInfo = await page.evaluate(getVideoCardInfo, 'webgl2')
|
||
|
|
t.is(videoCardInfo.error, undefined)
|
||
|
|
t.is(videoCardInfo.vendor, 'Intel Inc.')
|
||
|
|
t.is(videoCardInfo.renderer, 'Intel Iris OpenGL Engine')
|
||
|
|
})
|
||
|
|
|
||
|
|
test('vanilla: normal toString stuff', async t => {
|
||
|
|
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||
|
|
const page = await browser.newPage()
|
||
|
|
|
||
|
|
const test1 = await page.evaluate(() => {
|
||
|
|
return WebGLRenderingContext.prototype.getParameter.toString + ''
|
||
|
|
})
|
||
|
|
t.is(test1, 'function toString() { [native code] }')
|
||
|
|
|
||
|
|
const test2 = await page.evaluate(() => {
|
||
|
|
return WebGLRenderingContext.prototype.getParameter.toString()
|
||
|
|
})
|
||
|
|
t.is(test2, 'function getParameter() { [native code] }')
|
||
|
|
})
|
||
|
|
|
||
|
|
test('stealth: will not leak toString stuff', async t => {
|
||
|
|
const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())
|
||
|
|
const browser = await puppeteer.launch({ headless: true })
|
||
|
|
const page = await browser.newPage()
|
||
|
|
|
||
|
|
const test1 = await page.evaluate(() => {
|
||
|
|
return WebGLRenderingContext.prototype.getParameter.toString + ''
|
||
|
|
})
|
||
|
|
t.is(test1, 'function toString() { [native code] }') // returns function () { [native code] }
|
||
|
|
|
||
|
|
const test2 = await page.evaluate(() => {
|
||
|
|
return WebGLRenderingContext.prototype.getParameter.toString()
|
||
|
|
})
|
||
|
|
t.is(test2, 'function getParameter() { [native code] }')
|
||
|
|
})
|
||
|
|
|
||
|
|
test('stealth: sets user opts correctly', async t => {
|
||
|
|
const puppeteer = addExtra(vanillaPuppeteer).use(
|
||
|
|
Plugin({ vendor: 'alice', renderer: 'bob' })
|
||
|
|
)
|
||
|
|
const browser = await puppeteer.launch({ headless: true })
|
||
|
|
const page = await browser.newPage()
|
||
|
|
|
||
|
|
const videoCardInfo = await page.evaluate(getVideoCardInfo, 'webgl')
|
||
|
|
t.is(videoCardInfo.error, undefined)
|
||
|
|
t.is(videoCardInfo.vendor, 'alice')
|
||
|
|
t.is(videoCardInfo.renderer, 'bob')
|
||
|
|
})
|
||
|
|
|
||
|
|
test('stealth: does not affect protoype', async t => {
|
||
|
|
const puppeteer = addExtra(vanillaPuppeteer).use(
|
||
|
|
Plugin({ vendor: 'alice', renderer: 'bob' })
|
||
|
|
)
|
||
|
|
const browser = await puppeteer.launch({ headless: true })
|
||
|
|
const page = await browser.newPage()
|
||
|
|
|
||
|
|
const result = await page.evaluate(() => {
|
||
|
|
try {
|
||
|
|
return WebGLRenderingContext.prototype.getParameter(37445)
|
||
|
|
} catch (err) {
|
||
|
|
return err.message
|
||
|
|
}
|
||
|
|
})
|
||
|
|
t.is(result, 'Illegal invocation')
|
||
|
|
})
|