前端不死水印的对抗方法 作者: Semesse 时间: 2022-06-24 分类: 千叶 有时候企业不希望内网页面上的内容被截图泄漏出去,或者希望在事情闹大之后追踪到在产品上截图的员工 / 用户,这时候前端水印就派上用场了。使用纯前端添加的水印大抵有这么几种形式 - 明(文)水印 - 一类是非常直白的肉眼可见的水印,一般是为了警告用户这个页面包含敏感内容,不要截图分享 - 还有一些隐蔽性更强的水印,只会放在页面上部分关键区域,通过降低对比度隐藏自己,这种水印一般事后用于追溯  - 暗水印 - 前端使用的暗水印通常不是通过隐写的方式如 LSB、频域水印等,这样开销会比较大,此外也只能对单个图片生效,不能覆盖整个页面 - 一般使用非肉眼可辨识的图层重复覆盖整个页面,需要通过算法逆向提取出其中的水印内容,各家算法可能不一样 不过放在前端的水印都是比较容易被去除的,只要在浏览器 Devtools 中找到水印所在 DOM 元素删除即可。于是聪明的前端开发们使用 [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) 造出了不死水印:只要水印被删除或者样式被修改,就会重新创建一个水印。 以某个国产[水印库](https://github.com/saucxs/watermark-dom)为例,不死水印的原理如下(有简化) ```tsx //监听dom是否被移除或者改变属性的回调函数 var domChangeCallback = function (records){ // ... if ((globalSetting && records.length === 1) || records.length === 1 && records[0].removedNodes.length >= 1) { // 如果被删除,则重新创建水印图层 loadMark(globalSetting); } }; var hasObserver = MutationObserver !== undefined; var watermarkDom = hasObserver ? new MutationObserver(domChangeCallback) : null; var option = { 'childList': true, // 防止被删除,水印外层需要额外包一层 div 'attributes': true, // 防止 style 被修改 'subtree': true, // 防止被删除 }; // ... watermarkDom.observe(document.getElementById(defaultSettings.watermark_id).shadowRoot, option); ``` 当水印被删除 (`childList`) 或者 style 被修改 (`attributes`) 时,`MutationObserver` 就会触发回调,重新绘制水印,让我们无法通过正常方式删除。当然还有一些变种的不死水印,在被删除 / 修改时会清空页面或者后台上报删除事件,让人防不胜防。对付这些水印,我们可以在控制台上禁用 JavaScript 来防止这些骚操作,然后尽情地去除水印再进行截图。 不过这样还是有点麻烦,如果想要一个没有水印的干净清爽的世界,有更好的方法吗? 既然不死水印使用的是 `MutationObserver`,我们可以通过油猴脚本,我们可以在页面脚本之前把这个东西干掉。 ```tsx // ==UserScript== // @run-at document-start // ==/UserScript== delete window.MutationObserver delete window.WebKitMutationObserver delete window.MozMutationObserver ``` 大公告成!不过有些站点本身就会使用 `MutationObserver` ,贸然删除会导致功能不可用,如果要精准控制的话就需要自己写 `Proxy` 劫持 `construct` 来处理了,这样又会麻烦起来。那么有没有一劳永逸的方法呢? 其实还是有的,也就是本文的正题了—— 水印制作者需要花费很大力气保证水印可见并且可以恢复出信息,而去除水印只需要让它不可见就可以了。那么有没有办法在不修改水印本身样式的情况下让它不可见呢?答案就是使用 CSS 从“旁路”攻击水印。 假设我们有一个水印图层长这样 ```html ``` 虽然水印已经内联了 `important` 样式防止被 `display: none` 掉,但我们还有非常多的方式可以让它不可见: ```css #wm_div_id { opacity: 0; visibility: hidden; width: 0; height: 0; position: absolute; top: -9999; // and much more } ``` 让水印不可见并不是我们对水印元素做了任何修改,我们只是让 CSS selector **命中**了这个元素并且让它看不见而已。`MutationObserver` 是不会触发任何回调的,而目前的 Web 规范中也暂时没有能监听*计算样式*变化的 API,好耶! 此外前端生成的水印图层常常会有很明显的特征可以被 CSS selector 选中,发动一点小脑筋很快就可以写出一个能处理掉大部分站点水印的样式规则,再使用 stylish 之类的浏览器插件加载到每个网页上,这下我们可以在完全不被水印影响的情况下尽情冲浪了。 尽管 CSS 样式攻击很难防御,JS 也不是没有应对方法:`Element.getComputedStyle()` 可以获取特定元素的计算样式,JS 获取到计算样式之后可以检查是否被修改再重新绘制水印。获取计算样式由于会触发 RecalculateStyle 和 Layout 所以对性能可能会有影响(不过可以在 rAF 里面处理)。这样做的成本真的很高,应该不会真的有人这么做吧.jpg 相信大家已经完全掌握了这门技术,学会编写自己的攻击样式了吧。我可没有传授什么去内网水印的教程( 冲浪愉快 :) 标签: none