声享正努力加载中...
Hex
【前端冷知识】
通常情况下,在一个 HTML 页面上,我们总能够通过 DOM API 访问想要访问的 HTML 元素,进行操作。
那么,如果我们允许用户注入代码,而不允许操作 DOM 怎么办呢?
这没问题吧?
首先我们想到的是劫持 window 和 document 对象
window.document = 111;
console.log(window.document); // #document很不巧,我们失败了...
为什么呢?
因为 window.document 实际上是一个 getter,而且它的 configurable 是 false
Object.getOwnPropertyDescriptor(window, 'document');
// {get: ƒ, set: undefined, enumerable: true, configurable: false}此路不通我们继续尝试~
(function(window, document) {
// 用户代码
console.log(this, window, document); // {}, null, null
const win = (function () {
return this;
}());
console.log(win); // Window
// ---
}).call({}, null, null)不出意外的我们又失败了 (滑跪.jpg)
我们给用户注入的 JS 外面包裹函数作用域!行不行!
用户还是通过函数调用的 this 拿到了 window 对象
我们可以在包裹的时候使用严格模式(use strict)
我们继续解决问题 🤔
(function(window, document) {
'use strict'
// 用户代码
console.log(this, window, document); // {}, null, null
const win = (function () {
return this;
}());
console.log(win); // undefined
// ---
}).call({}, null, null)这次看起来 work 了?😏
实践证明你想多了~ 😜
(function(window, document) {
'use strict'
console.log(this, window, document); // {}, null, null
const win = (function () {
return this;
}());
console.log(win); // undefined
setTimeout(function() {
console.log(this); // Window
});
}).call({}, null, null)所以上面的方法都无法让用户彻底禁止访问 window 和 document 对象
那么,我们还有没有其他方法呢?
为了折腾用户我们真是尽力了!🤣
我们想到了 Web Workerrrrr(破音儿)
我们知道 worker 的环境是和浏览器环境互相独立的线程,所以跑在 worker 里的代码是不能访问 window 和 document 对象的
function execCodeInWorker(code) {
const blob = new Blob([code]);
const url = URL.createObjectURL(blob);
const worker = new Worker(url);
return worker;
}
const userCode = `
// 用户代码
console.log(typeof window, typeof document); // undefined undefined
`;
execCodeInWorker(userCode);使用 worker 的问题是,当 worker 和浏览器环境通讯时,需要采用 postMessage,如果有较多的交互操作,性能开销比较大,而且对写代码的开发者也不够友好
使用 worker 就行了?😌
不!!!!!
(黑人问号脸,我都限制你了我还要对你友好也是醉了.....)
如果我们只是不允许用户注入的代码修改 UI
我们也可以将整个 UI 通过 Shadow DOM 渲染
并且将 ShadowRoot 的模式设置为 closed 封闭起来
用户就无法拿到 Shadow DOM 的 ShadowRoot 对象,从而无法进行操作
我们来看一个例子~
(function() {
const root = document.body.attachShadow({mode: 'closed'});
let list;
function init() {
root.innerHTML = `
<h1>Todo List</h1>
<ul></ul>
`
list = root.querySelector('ul');
}
function addTask(desc) {
const task = document.createElement('li');
task.textContent = desc;
list.appendChild(task);
return list.children.length - 1;
}
function removeTask(index) {
const task = list.children[index];
if (task) task.remove();
}
window.init = init;
window.addTask = addTask;
window.removeTask = removeTask;
}());
init();
addTask('task1');通过 document.body.attachShadow({mode: 'closed'}); 创建 ShadowRoot,使用 Shadow DOM API 来创建 UI,因为这个 root 对象没有暴露给用户,且 mode 是 closed,所以用户无法通过 DOM 操作我们的 UI,只能使用 addTask 和 removeTask 来操作
注意,用户仍可往 body 中插入其他内容,但是,当一个元素创建了 Shadow DOM,浏览器会优先渲染 Shadow DOM,而忽略其他子元素,所以用户插入的不会被渲染出来
唯一的例外是如果插入 script 标签,脚本会被执行,但是我们可以简单通过防止 xss 的代码过滤来阻止用户插入 script 标签
当然 Shadow DOM 也有弊端...........
比如用户虽然不能改写当前 body 元素中渲染的内容了,但是可以彻底删掉 body 元素重新创建一个
document.documentElement.removeChild(body);
const newBody = document.createElement('body');
document.documentElement.appendChild(newBody);此处求开发者的心理阴影面积......
(完)
参考文章:
.webp)