浏览器指纹追踪技术,如何完整修改浏览器指纹?
点击上方 前端Q,关注公众号
回复加群,加入前端Q技术交流群
来源 | http://www.fly63.com/article/detial/10479
什么是浏览器指纹
浏览器指纹背景
目前处于2.5代是因为现在需要解决的问题是如何解决跨浏览器识别指纹的问题上,稍后会介绍下这方面所取得的成果。
指纹采集
信息熵(entropy)是接收的每条消息中包含的信息的平均量,信息熵越高,则能传输越多的信息,信息熵越低,则意味着传输的信息越少。
浏览器指纹是由许多浏览器的特征信息综合起来的,其中特征值的信息熵也不尽相同。因此,指纹也分为基本指纹和高级指纹。
基本指纹
基本指纹就是容易被发现和修改的部分,如 http 的 header。
{ "headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Host": "httpbin.org",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36"
}}
每个浏览器的UA
浏览器发送的 HTTP ACCEPT 标头
浏览器中安装的浏览器扩展/插件,例如 Quicktime,Flash,Java 或 Acrobat,以及这些插件的版本
计算机上安装的字体。
浏览器是否执行 JavaScript 脚本
浏览器是否能种下各种 cookie 和 “super cookies”
是否浏览器设置为“Do Not Track”
系统平台(例如 Win32、Linux x86)
系统语言(例如 cn、en-US)
浏览器是否支持触摸屏
拿到这些值后可以进行一些运算,得到浏览器指纹具体的信息熵以及浏览器的 uuid。
这些信息就类似人类的体重、身高、肤色一样,有很大的重复概率,只能作为辅助识别,所以我们需要更精确的指纹来判断唯一性。
高级指纹
普通指纹是不够区分独特的个人,这时就需要高级指纹,将范围进一步缩小,甚至生成一个独一无二的跨浏览器身份。
用于生产指纹的各个信息,有权重大小之分,信息熵大的将拥有较大的权重。
如何完整修改浏览器指纹?
接下来教大家如何修改浏览器指纹,例如修改navigator全部参数:
(function() {
;
function fakeActiveVRDisplays() { return "Not Spoofed"; }
function fakeAppCodeName() {
return "Mozilla";
}
function fakeAppName() {
return "Netscape";
}
function fakeAppVersion() {
return "5.0 (Windows)";
}
function fakeBattery() { return "Not Spoofed"; }
function fakeConnection() { return "Not Spoofed"; }
function fakeGeoLocation() { return "Not Spoofed"; }
function fakeHardwareConcurrency() {
return 1;
}
function fakeJavaEnabled() {
return false;
}
function fakeLanguage() {
// NOTE: TOR Browser uses American English
return "en-US";
}
function fakeLanguages() {
// NOTE: TOR Browser uses American English
return "en-US,en";
}
function fakeMimeTypes() { return "Not Spoofed"; }
function fakeOnLine() {
return true;
}
function fakeOscpu() {
return "Windows NT 6.1";
}
function fakePermissions() { return "Not Spoofed"; }
function fakePlatform() {
return "Win32";
}
function fakePlugins() {
return window.navigator.plugins;
}
function fakeProduct() {
return "Gecko";
}
function fakeServiceWorker() { return "Not Spoofed"; }
function fakeStorage() { return "Not Spoofed"; }
function fakeUserAgent() {
// NOTE: Current TOR User Agent as of 19 July 2017
// NOTE: This will need constant updating.
// NOTE: As TOR changes firefox versions each update,
// NOTE: Shape Shifter will need to keep up.
return "Mozilla/5.0 (Windows NT 6.1; rv:52.0) Gecko/20100101 Firefox/52.0";
}
function fakeBuildID() {
return "20100101";
}
const fakeActiveVRDisplaysValue = fakeActiveVRDisplays();
const fakeAppCodeNameValue = fakeAppCodeName();
const fakeAppNameValue = fakeAppName();
const fakeAppVersionValue = fakeAppVersion();
const fakeBatteryValue = fakeBattery();
const fakeConnectionValue = fakeConnection();
const fakeGeoLocationValue = fakeGeoLocation();
const fakeHardwareConcurrencyValue = fakeHardwareConcurrency();
const fakeJavaEnabledValue = fakeJavaEnabled();
const fakeLanguageValue = fakeLanguage();
const fakeLanguagesValue = fakeLanguages();
const fakeMimeTypesValue = fakeMimeTypes();
const fakeOnLineValue = fakeOnLine();
const fakeOscpuValue = fakeOscpu();
const fakePermissionsValue = fakePermissions();
const fakePlatformValue = fakePlatform();
const fakePluginsValue = fakePlugins();
const fakeProductValue = fakeProduct();
const fakeServiceWorkerValue = fakeServiceWorker();
const fakeStorageValue = fakeStorage();
const fakeUserAgentValue = fakeUserAgent();
const fakeBuildIDValue = fakeBuildID();
Object.defineProperties(window.navigator, {
/*
activeVRDisplays: {
configurable: true,
enumerable: true,
get: function getActiveVRDisplays() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.activeVRDisplays");
return fakeActiveVRDisplaysValue;
}
},
*/
appCodeName: {
configurable: true,
enumerable: true,
get: function getAppCodeName() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.appCodeName");
return fakeAppCodeNameValue;
}
},
appName: {
configurable: true,
enumerable: true,
get: function getAppName() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.appName");
return fakeAppNameValue;
}
},
appVersion: {
configurable: true,
enumerable: true,
get: function getAppVersion() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.appVersion");
return fakeAppVersionValue;
}
},
// TODO: This is getBattery() now
/*
battery: {
configurable: true,
enumerable: true,
get: function getBattery() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.battery");
return fakeBatteryValue;
}
},
connection: {
configurable: true,
enumerable: true,
get: function getConnection() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.connection");
return fakeConnectionValue;
}
},
geolocation: {
configurable: true,
enumerable: true,
get: function getGeoLocation() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.geolocation");
return fakeGeoLocationValue;
}
},
*/
hardwareConcurrency: {
configurable: true,
enumerable: true,
get: function getHardwareConcurrency() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.hardwareConcurrency");
return fakeHardwareConcurrencyValue;
}
},
/*
javaEnabled: {
configurable: true,
enumerable: true,
value: function getJavaEnabled() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.javaEnabled");
return fakeJavaEnabledValue;
}
},
*/
language: {
configurable: true,
enumerable: true,
get: function getLanguage() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.language");
return fakeLanguageValue;
}
},
languages: {
configurable: true,
enumerable: true,
get: function getLanguages() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.languages");
return fakeLanguagesValue;
}
},
/*
mimeTypes: {
configurable: true,
enumerable: true,
get: function getMimeTypes() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.mimeTypes");
return fakeMimeTypesValue;
}
},
*/
onLine: {
configurable: true,
enumerable: true,
get: function getOnLine() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.onLine");
return fakeOnLineValue;
}
},
oscpu: {
configurable: true,
enumerable: true,
get: function getOscpu() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.oscpu");
return fakeOscpuValue;
}
},
/*
permissions: {
configurable: true,
enumerable: true,
get: function getPermissions() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.permissions");
return fakePermissionsValue;
}
},
*/
platform: {
configurable: true,
enumerable: true,
get: function getPlatform() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.platform");
return fakePlatformValue;
}
},
/*
plugins: {
configurable: true,
enumerable: true,
get: function getPlugins() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.plugins");
return fakePluginsValue;
}
},
*/
product: {
configurable: true,
enumerable: true,
get: function getProduct() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.product");
return fakeProductValue;
}
},
/*
serviceWorker: {
configurable: true,
enumerable: true,
get: function getServiceWorker() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.serviceWorker");
return fakeServiceWorkerValue;
}
},
storage: {
configurable: true,
enumerable: true,
get: function getStorage() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.storage");
return fakeStorageValue;
}
},
*/
userAgent: {
configurable: true,
enumerable: true,
get: function getUserAgent() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.userAgent");
return fakeUserAgentValue;
}
},
buildID: {
configurable: true,
enumerable: true,
get: function getBuildID() {
console.log("[ALERT] " + window.location.hostname + " accessed property Navigator.buildID");
return fakeBuildIDValue;
}
}
});
})();
另外关于浏览器硬件指纹,包括canvas,webgl,fonts,audio等。
Canvas 指纹
Canvas 是 HTML5 中的动态绘图标签,也可以用它生成图片或者处理图片。即便使用 Canvas 绘制相同的元素,但是由于系统的差别,字体渲染引擎不同,对抗锯齿、次像素渲染等算法也不同,Canvas 将同样的文字转成图片,得到的结果也是不同的。
实现代码大致为:在画布上渲染一些文字,再用 toDataURL 转换出来,即便开启了隐私模式一样可以拿到相同的值。
function getCanvasFingerprint () {
var canvas = document.createElement('canvas');
var context = canvas.getContext("2d");
context.font = "18pt Arial";
context.textBaseline = "top";
context.fillText("Hello, user.", 2, 2);
return canvas.toDataURL("image/jpeg");
}
getCanvasFingerprint()
流程很简单,渲染文字,toDataURL 是将整个 Canvas 的内容导出,得到值。
WebGL 指纹
WebGL(Web图形库)是一个 JavaScript API,可在任何兼容的 Web 浏览器中渲染高性能的交互式 3D 和 2D 图形,而无需使用插件。
WebGL 通过引入一个与 OpenGL ES 2.0 非常一致的 API 来做到这一点,该 API 可以在 HTML5 元素中使用。
这种一致性使 API 可以利用用户设备提供的硬件图形加速。网站可以利用 WebGL 来识别设备指纹,一般可以用两种方式来做到指纹生产:
WebGL 报告——完整的 WebGL 浏览器报告表是可获取、可被检测的。在一些情况下,它会被转换成为哈希值以便更快地进行分析。
WebGL 图像 ——渲染和转换为哈希值的隐藏 3D 图像。由于最终结果取决于进行计算的硬件设备,因此此方法会为设备及其驱动程序的不同组合生成唯一值。
这种方式为不同的设备组合和驱动程序生成了唯一值。
可以通过 Browserleaks test 检测网站来查看网站可以通过该 API 获取哪些信息。
产生WebGL指纹原理是首先需要用着色器(shaders)绘制一个梯度对象,并将这个图片转换为Base64字符串。
然后枚举WebGL所有的拓展和功能,并将他们添加到Base64字符串上,从而产生一个巨大的字符串,这个字符串在每台设备上可能是非常独特的。
例如fingerprint2js库的 WebGL 指纹生产方式:
// 部分代码
gl = getWebglCanvas()
if (!gl) { return null }
var result = []
var vShaderTemplate = 'attribute vec2 attrVertex;varying vec2 varyinTexCoordinate;uniform vec2 uniformOffset;void main(){varyinTexCoordinate=attrVertex+uniformOffset;gl_Position=vec4(attrVertex,0,1);}'
var fShaderTemplate = 'precision mediump float;varying vec2 varyinTexCoordinate;void main() {gl_FragColor=vec4(varyinTexCoordinate,0,1);}'
var vertexPosBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer)
var vertices = new Float32Array([-0.2, -0.9, 0, 0.4, -0.26, 0, 0, 0.732134444, 0])
// 创建并初始化了Buffer对象的数据存储区。
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
vertexPosBuffer.itemSize = 3
vertexPosBuffer.numItems = 3
// 创建和初始化一个WebGLProgram对象。
var program = gl.createProgram()
// 创建着色器对象
var vshader = gl.createShader(gl.VERTEX_SHADER)
// 下两行配置着色器
gl.shaderSource(vshader, vShaderTemplate) // 设置着色器代码
gl.compileShader(vshader) // 编译一个着色器,以便被WebGLProgram对象所使用
var fshader = gl.createShader(gl.FRAGMENT_SHADER)
gl.shaderSource(fshader, fShaderTemplate)
gl.compileShader(fshader)
// 添加预先定义好的顶点着色器和片段着色器
gl.attachShader(program, vshader)
gl.attachShader(program, fshader)
// 链接WebGLProgram对象
gl.linkProgram(program)
// 定义好的WebGLProgram对象添加到当前的渲染状态
gl.useProgram(program)
program.vertexPosAttrib = gl.getAttribLocation(program, 'attrVertex')
program.offsetUniform = gl.getUniformLocation(program, 'uniformOffset') gl.enableVertexAttribArray(program.vertexPosArray)
gl.vertexAttribPointer(program.vertexPosAttrib, vertexPosBuffer.itemSize, gl.FLOAT, !1, 0, 0)
gl.uniform2f(program.offsetUniform, 1, 1)
// 从向量数组中绘制图元
gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexPosBuffer.numItems)
try {
result.push(gl.canvas.toDataURL())
} catch (e) {
/* .toDataURL may be absent or broken (blocked by extension) */
}
防御浏览器指纹追踪
这是一个比较暴力的方法,直接禁止网站使用JavaScript可以非常有效地防御浏览器指纹追踪,但是这样会导致页面较大部分地功能不可用。
而且非常不幸的是,即便禁止了js但是还可以通过css来采取浏览器的信息,例如:
{
body {
background: url("https://example.org/1080.png");
}
}
总结
对于浏览器指纹,攻与防在不断的转换,目前浏览器指纹也不能绝对的标识一台主机,如果用户切换显卡或者双系统,虚拟机这些因素,那么目前的浏览器指纹就无法唯一标识了。
未来随着新的HTML5技术不断更新,新的浏览器技术会提供更多的API,以及通过侧信道技术,在浏览器指纹会有新的突破。
内推社群
我组建了一个氛围特别好的腾讯内推社群,如果你对加入腾讯感兴趣的话(后续有计划也可以),我们可以一起进行面试相关的答疑、聊聊面试的故事、并且在你准备好的时候随时帮你内推。下方加 winty 好友回复「面试」即可。