【前端冷知识】如何禁止开发者操作网页上的DOM对象?

通常情况下,在一个HTML页面上,我们总能够通过DOM API访问想要访问的HTML元素,进行操作。

如果基于某种原因,允许用户注入代码到网页上,但又要禁止用户对DOM对象进行操作,即只允许用户调用我们提供的API,不允许用户通过注入的JS来修改我们创建的UI组件甚至整个网页内容,那么我们要怎么做呢?

我们基本上无法通过JS来禁止用户通过注入的JS操作DOM,因为window对象、document对象这些对象以及它们的API是无法通过JS改写的:

 
  

window.document = 111;

console.log(window.document); // #document

如果我们用Object.getOwnPropertyDescriptor查看,会发现window.document实际上是一个getter,而且它的configurable是false。

 
  

Object.getOwnPropertyDescriptor(window, 'document');

// {get: ƒ, set: undefined, enumerable: true, configurable: false}

即使我们对允许用户合法注入的JS外面包裹函数作用域,我们还是无法彻底阻止用户访问document和window对象。

 
  

(function(window, document) {

  // user code

  console.log(this, window, document); // {}, null, null

  const win = (function () {

    return this;

  }());

  console.log(win); // Window

  // ---

}).call({}, null, null)

比如说上面的代码,我们在用户注入的代码前后增加包装代码,把window和document对象通过函数参数覆盖,我们还是能通过函数调用的this拿到window对象。

要防住这个漏洞,我们可以在包装的时候使用严格模式:

 
  

(function(window, document) {'use strict'

  // user code

  console.log(this, window, document); // {}, null, null

  const win = (function () {

    return this;

  }());

  console.log(win); // undefined

  // ---

}).call({}, null, null)

但是这个依然不解决问题:

 
  

(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对象。

那么我们要怎么做既能合法让用户注入代码实现功能,又能够隔离window和document对象呢?

用worker做沙箱

第一种办法是只允许用户的代码跑在worker里。

我们知道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,如果有较多的交互操作,性能开销比较大,而且对写代码的开发者有使用成本。

使用Shadow DOM

如果我们只是不允许用户注入的JS修改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来操作。

?注意,用户当然仍可以通过DOM API来往body中插入其他内容,但是,当一个元素创建了Shadow DOM,浏览器会优先渲染Shadow DOM,而忽略它的其他子元素,所以用户往body中插入任何内容都不会被渲染出来。唯一的例外是如果插入script标签,脚本会被执行,但是我们可以简单通过防止xss的代码过滤来阻止用户插入script标签。

这样,我们就通过Shadow DOM获得了一个相对安全的环境,对比worker的方式,可以避免postMessage的开销和拥有更简单的写法。当然Shadow DOM也有弊端,比如用户虽然不能改写当前body元素中渲染的内容了,但是可以彻底删掉body元素重新创建一个:

 
  

document.documentElement.removeChild(body);

const newBody = document.createElement('body');

document.documentElement.appendChild(newBody);

但是那样的话,原body中的所有内容也需要重建了。因此,使用Shadow DOM API至少大大增加了用户侵入的成本。

关于禁止开发者操作DOM对象的话题就谈到这里,还有什么可行的方法或者大家有什么想补充的,欢迎在issue中讨论。

关于奇舞周刊

《奇舞周刊》是360公司专业前端团队「奇舞团」运营的前端技术社区。关注公众号后,直接发送链接到后台即可给我们投稿。

640?wx_fmt=png

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
后台采用apache服务器下的cgi处理c语言做微信小程序后台逻辑的脚本映射。PC端的服务器和客户端都是基于c语言写的。采用mysql数据库进行用户数据和聊天记录的存储。.zip C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。下面详细介绍C语言的基本概念和语法。 1. 变量和数据类型 在C语言,变量用于存储数据,数据类型用于定义变量的类型和范围。C语言支持多种数据类型,包括基本数据类型(如int、float、char等)和复合数据类型(如结构体、联合等)。 2. 运算符 C语言常用的运算符包括算术运算符(如+、、、/等)、关系运算符(如==、!=、、=、<、<=等)、逻辑运算符(如&&、||、!等)。此外,还有位运算符(如&、|、^等)和指针运算符(如、等)。 3. 控制结构 C语言常用的控制结构包括if语句、循环语句(如for、while等)和switch语句。通过这些控制结构,可以实现程序的分支、循环和多路选择等功能。 4. 函数 函数是C语言用于封装代码的单元,可以实现代码的复用和模块化。C语言定义函数使用关键字“void”或返回值类型(如int、float等),并通过“{”和“}”括起来的代码块来实现函数的功能。 5. 指针 指针是C语言用于存储变量地址的变量。通过指针,可以实现对内存的间接访问和修改。C语言定义指针使用星号()符号,指向数组、字符串和结构体等数据结构时,还需要注意数组名和字符串常量的特殊性质。 6. 数组和字符串 数组是C语言用于存储同类型数据的结构,可以通过索引访问和修改数组元素。字符串是C语言用于存储文本数据的特殊类型,通常以字符串常量的形式出现,用双引号("...")括起来,末尾自动添加'\0'字符。 7. 结构体和联合 结构体和联合是C语言用于存储不同类型数据的复合数据类型。结构体由多个成员组成,每个成员可以是不同的数据类型;联合由多个变量组成,它们共用同一块内存空间。通过结构体和联合,可以实现数据的封装和抽象。 8. 文件操作 C语言通过文件操作函数(如fopen、fclose、fread、fwrite等)实现对文件的读写操作。文件操作函数通常返回文件指针,用于表示打开的文件。通过文件指针,可以进行文件的定位、读写等操作。 总之,C语言是一种功能强大、灵活高效的编程语言,广泛应用于各种领域。掌握C语言的基本语法和数据结构,可以为编程学习和实践打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值