关于 canvas2d 的一些思考
我们可以从这里这个叫做 HTML-in-Canvas 的技术说起,如果有一个 Chrome 157 and later 的话(不瞒你说,我也是备有一个 Chrome 160 的),你就可以在它的主页知道这是什么玩意。总的来说,或者顾名思义,这就是允许在 canvas 中渲染 html 文档(以及 css),用来搞出很多 fancy 特效的 api。
不过大家都知道 canvas 是允许读回像素的,也就是说,用 2d 举例(反正我也没有 webgl/webgpu),网页可以 getImageData() 或者直接 createImageBitmap() 截图从而获得渲染结果,由于我们电脑的 GPU 存在各种复杂的区别,网页记录渲染结果的特征就可以得到所谓 canvas 指纹。那么,对于有 HTML-in-Canvas 技术的浏览器来说,canvas 能绘制 DOM 快照,网页应该是能测量到更多东西(排版、字体、选择、表单控件、滚动条),于是你就得到了传说级别的指纹面。
这是 WICG 对此的解释,确实禁止了一些敏感东西,当然我是依旧不敢苟同。不过这里提到了另一个 foreignObject 是在 svg 嵌入里面 html 的规范,倒是比较有趣:
Scrollbar and form element appearance (these are already detectable in Blink and WebKit through foreignObject).
这里凑一下文章字数给这段演示代码也放进来:
<!DOCTYPE html>
<h2>Using SVG foreignObject to draw html into canvas.</h2>
<br>
SVG DOM:<br>
<svg id="source_svg" width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<foreignObject width="200" height="200">
visited link info: <a href="https://google.com">google.com</a><br>
form control:
<input type="checkbox" checked></input>
<textarea xmlns="http://www.w3.org/1999/xhtml" style="width: 100px; height: 50px;">
This is some scrollable text inside a textarea, which is inside a foreignObject.
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Line 10
Line 11
Line 12
</textarea>
<br>
cross-origin image: <img src="https://pr.gg/happy.png" style="width: 30px; height: 30px;">
</foreignObject>
</svg>
<br>
Canvas:<br>
<canvas id="dest_canvas" width="200" height="200"></canvas>
<div id="readback_status"></div>
<script>
const svgElement = document.getElementById('source_svg');
const canvasElement = document.getElementById('dest_canvas');
const statusDiv = document.getElementById('readback_status');
const ctx = canvasElement.getContext('2d');
// 1. Serialize the SVG element to an XML string.
const svgString = new XMLSerializer().serializeToString(svgElement);
// 2. Encode the string into a base64 data URL.
const svgDataUrl = 'data:image/svg+xml;base64,' + btoa(svgString);
// 3. Create an image and set its source to the data URL.
const img = new Image();
img.onload = function() {
// 4. Once the image loads, draw it to the canvas.
ctx.drawImage(img, 0, 0);
// 5. Try to read pixel data from the canvas.
try {
// This will throw a security error because the canvas is tainted.
ctx.getImageData(0, 0, 1, 1);
statusDiv.textContent = 'Pixel readback is possible.';
} catch (e) {
statusDiv.textContent = 'Pixel readback is not possible.';
}
};
img.src = svgDataUrl;
</script>
确实只暴露了滚动条,不过搞这么复杂也是神了。我能怎么说呢,这种东西很难说不是从设计之初就没安好心了。
说白了对于我们用户来说一个简单的防指纹办法就是销掉所有从 canvas 读回像素的 api,事实上 tor 团队或者 brave 差不多也是有这么做。