“浏览器指纹”是一种通过浏览器对网站可见的配置和设置信息来跟踪Web浏览器的方法,浏览器指纹就像我们人手上的指纹一样,具有个体辨识度,只不过现阶段浏览器指纹辨别的是浏览器。
人手上的指纹之所以具有唯一性,是因为每个指纹具有独特的纹路、这个纹路由凹凸的皮肤所形成。每个人指纹纹路的差异造就了其独一无二的特征。
那么浏览器指纹也是同理,获取浏览器具有辨识度的信息,进行一些计算得出一个值,那么这个值就是浏览器指纹。辨识度的信息可以是UA、时区、地理位置或者是你使用的语言等等,你所选取的信息决定了浏览器指纹的准确性。
对于网站而言,拿到浏览器指纹并没有实际价值,真正有价值的是这个浏览器指纹对应的用户信息。作为网站站长,收集用户浏览器指纹并记录用户的操作,是一个有价值的行为,特别是针对没有用户身份的场景。例如在一个内容分发网站上,用户A喜欢浏览二次元的内容,通过浏览器指纹记录这个兴趣,那么下次用户不需要登录即可向A用户推送二次元的信息。在个人PC如此普及的当下,这也是一种内容分发的方式。
对于用户而言,建立个人上网行为与浏览器指纹之间的联系或多或少都有侵犯用户隐私的意味,特别是将你的浏览器指纹和真实的用户信息相关联起来的时候。所幸的是这种方式对于用户的隐私侵犯比较有限、滥用用户行为也会透支用户对网站的好感。
浏览器指纹追踪技术到目前已经进入2.5代。
第一代是状态化的,主要集中在用户的cookie和evercookie上,需要用户登录才可以得到有效的信息。
第二代才有了浏览器指纹的概念,通过不断增加浏览器的特征值从而让用户更具有区分度,例如(UA、浏览器插件信息)
第三代是已经将目光放在人身上了,通过收集用户的行为、习惯来为用户建立特征值甚至模型,可以实现真正的追踪技术,这部分目前实现比较复杂,依然在探索中。
目前处于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"
}}
除了 http 中拿到的指纹,还可以通过其他方式来获得浏览器的特征信息,例如:
每个浏览器的UA
浏览器发送的 HTTP ACCEPT 标头
浏览器中安装的浏览器扩展/插件,例如 Quicktime,Flash,Java 或 Acrobat,以及这些插件的版本
计算机上安装的字体。
浏览器是否执行 JavaScript 脚本
浏览器是否能种下各种 cookie 和 “super cookies”
是否浏览器设置为“Do Not Track”
系统平台(例如 Win32、Linux x86)
系统语言(例如 cn、en-US)
浏览器是否支持触摸屏
拿到这些值后可以进行一些运算,得到浏览器指纹具体的信息熵以及浏览器的 uuid。
这些信息就类似人类的体重、身高、肤色一样,有很大的重复概率,只能作为辅助识别,所以我们需要更精确的指纹来判断唯一性。
高级指纹
普通指纹是不够区分独特的个人,这时就需要高级指纹,将范围进一步缩小,甚至生成一个独一无二的跨浏览器身份。
用于生产指纹的各个信息,有权重大小之分,信息熵大的将拥有较大的权重。
接下来教大家如何修改浏览器指纹,例如修改navigator全部参数:
(function() {
'use strict';
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来采取浏览器的信息,例如:
@media(device-width: 1080px) {
body {
background: url("https://example.org/1080.png");
}
}
您的浏览器禁用了JS脚本运行,请启用该功能。怎么解除浏览器禁用js?这篇文章将总结整理各个浏览器如何开启、禁用javascript的方法总汇。
浏览器使用流式布局模型 (Flow Based Layout)。浏览器会把HTML解析成DOM,把CSS解析成CSSOM,DOM和CSSOM合并就产生了Render Tree。有了RenderTree,我们就知道了所有节点的样式,然后计算他们在页面上的大小和位置,最后把节点绘制到页面上。
IE6不支持min-height,解决办法使用css hack,ol内li的序号全为1,不递增。解决方法:为li设置样式display: list-item;定位父元素overflow: auto;,包含position: relative;子元素,子元素高于父元素时会溢出。解决办法:
由于不同的浏览器默认的样式也不同,所以在网页开发前设置一个公用样式,来清除各个浏览器的默认样式,已达到做的网页在各个浏览器中达到统一。
浏览器访问网站的步骤:Chrome搜索自身的DNS缓存、读取本地HOST文件、浏览器发起一个DNS的一个系统调用、浏览器获得域名对应的IP地址后,发起HTTP三次握手、TCP/IP连接建立起来、服务器端接受到了这个请求、浏览器根据拿到的资源对页面进行渲染
Browsh是一个纯文本浏览器,可以运行在大多数的TTY终端环境和任何浏览器。目前,终端客户端比浏览器客户端更先进。终端客户端即时更新和交付,以便于体验新的功能,例如,你可以观看视频。
一般说的浏览器内核是指浏览器最重要的核心部分,RenderingEngine,翻译成中文大概意思就是“解释引擎”,我们一般称为浏览器内核。由于不同的内核各自有一套自己的渲染网页和解释页面代码的机制,所以就会有一些问题存在。
主流浏览器之争从上个世纪开就开始,已经持续了很长的时间。人们都在笑话IE,纷纷转向其它浏览器。今天,我向大家分享一下针对IE的搞笑图片,只是逗乐而已,喝杯咖啡,坐下来慢慢享受吧。
有时候我们希望在浏览器中执行一些低优先级的任务,比如记录统计数据、做一些耗时的数据处理等,暂且将其称为后台任务。这些任务跟动画计算、合成帧、响应用户输入等高优先级的任务共享主线程
浏览器的事件循环,前端再熟悉不过了,每天都会接触的东西。但我以前一直都是死记硬背:事件任务队列分为macrotask和microtask,浏览器先从macrotask取出一个任务执行,再执行microtask内的所有任务,接着又去macrotask取出一个任务执行
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!