chrome插件之内容安全策略🚫

一、前面的话

今天我们来认识一个chrome插件中的核心概念,它就是chrome插件安全系统的重要组成部分 ------ Content Security Policy (CSP)。中文可以译作内容安全策略

换句话说,chrome插件要保证自己的运行的内容是安全的,不会导致用户的利益和隐私受到恶意的读取。所以chrome插件的设计者为chrome制定的一些限制🚫,这个就是内容安全策略的通俗理解。

为了更好的帮你全面的认识内容安全策略,接下来我们会从下面几个方面来介绍:

  • 表现在哪些方面?
  • 为什么要有这个?
  • 什么是sanbox?

可以使用eval么?

大家都知道chrome插件开发是完全拥抱web技术的,也就是说我们可以使用js、css、html来开发插件就完全足够了,那么在js中,有一个函数叫做eval,它相当于一个javascript执行入口,可以将一段字符串的代码交给js引擎去执行。

js 复制代码
console.log(eval('2 + 2'));
// 输出: 4

console.log(eval(new String('2 + 2')));
// 输出: 2 + 2

console.log(eval('2 + 2') === eval('4'));
// 输出: true

console.log(eval('2 + 2') === eval(new String('2 + 2')));
// 输出: false

以上在web端运行时完全没问题的,但是如果在chrome插件中使用eval就会有问题,它会报下面这个错:

根据这个报错,我们就回答了第一个问题,内容安全策略的第一个表现就是禁止在chrome上下文执行环境中使用eval

如果您读过我的插件系列的文章,您就会知道,除了不能使用eval之外,在pupup.html、option.html中都不能够使用内联的scirpt脚本,像下面这样就是不允许的:

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>chrome page</title>
  </head>
  <body>
    <h1>我是popup.html</h1>
    <script>
      console.log("这是popup.html的内联脚本");
    </script>
  </body>
</html>

必须通过使用一个pupop.js文件引入的方式来注入脚本,这些其实都是内容安全策略的限制。

为什么禁止eval?

其实禁止eval的理由很简单------因为它太强大了,eval可以运行任何一段脚本,那就意味着用户可能会写下类似下面这样的代码:

js 复制代码
const script = `
  chrome.cookies.get({ 
    url: tabUrl, 
    name: "cookieName" 
  });
`

eval(script)

如果是插件作者自己因为业务需求需要获取cookie不要紧,但是要知道这个script可能来自网络,如果网络中有恶意的脚本交给chrome运行了,就会可能导致chromeAPI的滥用,导致用户的隐私信息比如cookie,书签等信息的泄露,这对于用户来说显然是一种损失。

所以禁止eval的原因之一就是因为害怕恶意的脚本借助强大的chromeAPI的能力在插件上下文运行了,从而非法获取用户的隐私信息。

除了禁用eval,还有其他的一些特性,比如你也不能使用new Function()的方式来声明一个函数;

在popup.html、options.html等插件页面中不能有内联的脚本等等,他们都是内容安全策略的限制。

什么是sandbox?

那么如果我们就是想要使用eval怎么办呢?其实也是有办法的,我们只需要找到根源就好:

内容安全策略之所以限制部分部分Javascript特性的使用,就是因为它们可能会导致chromeAPI的滥用,因此只需要让他们在一个没有办法调用chromeAPI的环境中执行就好了,这个环境就是sandbox。

sandbox通常指的是沙箱环境,它是一种安全机制,用于隔离插件的代码和操作,以防止恶意行为或不当访问用户的敏感数据。它有以下几个作用:

  1. 代码隔离: 沙箱环境将插件的代码与浏览器的核心代码和其他插件隔离开来。这意味着插件的JavaScript代码无法直接访问浏览器的敏感信息或其他插件的数据,从而增强了安全性。
  2. 限制执行环境: 沙箱通常会限制插件的代码执行环境,防止插件滥用底层系统资源或执行危险操作。这包括限制文件系统访问、网络请求和系统级操作等。
  3. 权限控制: Chrome插件系统通过权限模型来控制插件的功能。插件需要在其manifest.json文件中声明所需的权限,用户在安装插件时需要授予这些权限。这有助于确保插件只能执行其明确定义的任务。
  4. 内容脚本沙箱: 插件中的内容脚本(Content Scripts)通常在Web页面上运行,它们也受到沙箱机制的保护。这意味着内容脚本的代码只能访问所嵌入页面的内容,而无法直接访问插件的API或其他插件资源。
  5. 沙箱沙盒化: 沙箱环境本身也可以进一步沙盒化,以确保插件中的不同组件之间相互隔离。这意味着插件的不同部分(如背景脚本、内容脚本和弹出窗口)之间通常不能直接共享变量或数据。

二、沙盒解决方案

接下来我们将采用在沙箱环境下运行一段脚本,首先需要在manifest.json中声明一下:

json 复制代码
{
  ...,
  "sandbox": {
     "pages": ["sandbox.html"] // 指定需要沙盒化的page
  },
  ...
}

我们在sandbox.html中测试一下一些特定的特性是否可以使用:

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>sanbox demo</h1>

    <script>
      console.log("这是来自sandbox的chrome", chrome);
      eval('console.log("sandbox")');
    </script>
  </body>
</html>

然后在background.js中设置打开这个sandbox.html

js 复制代码
chrome.runtime.onInstalled.addListener(() => {
  console.log("这是来自background的chrome", chrome);

  chrome.tabs.create({
    url: "sandbox.html",
  });
});

当我们加载插件的时候,就会打开sandbox.html,结果如下:

    1. eval()能够正常执行
    1. 能够使用内联脚本
    1. 不同于background环境中的chrome对象,sandbox环境中的chrome只有极少的属性,也就是说在sandbox环境中,无法调用chromeAPI中类似storage、tabs等高级特性。

所以结论是:我们其实可以在插件中使用eval等在web端常用的特性,只需要将它运行的环境在manifest.json中显示的指定为沙盒环境就好了。

可能有的同学就会问了,虽然解决了使用eval的问题,但是又产生了一个新的问题,那就是既然没办法使用chrome的高级特性,那通信咋办呀,由沙盒环境产出的结果怎么通知给外部呀!

这个其实就要用到我们之前的一个知识了 ------ postMessage,它的使用文档在这里,这个API允许你向同源的所有域发送消息,也可以监听来自同源的域发送的消息。在插件中,我们改动一下上面的案例,使用类似下面的方式:

第一步:在background.js创建一个demo.html,它属于插件的page。

js 复制代码
// background.js
chrome.runtime.onInstalled.addListener(() => {
  console.log("这是来自background的chrome", chrome);

  chrome.tabs.create({
    url: "demo.html",
  });
});

第二步:在demo中嵌入sandbox.html

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>我是parent</h1>
    <button id="sendMessage">向sandbox发送消息</button>

    <script src="./demo.js"></script>
    <iframe
      id="theFrame"
      src="sandbox.html"
      frameborder="0"
      width="500"
      height="300"
    ></iframe>
  </body>
</html>
js 复制代码
// demo.js 通过给iframe发送消息
document.getElementById("sendMessage").addEventListener("click", () => {
  document
    .getElementById("theFrame")
    .contentWindow.postMessage("hello sandbox", "*");
});

第三步:在sandbox.html中做监听

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>我是sandbox</h1>
    <p>我是来自parent的data: <span id="show"></span></p>
    <script>
      eval('console.log("我可以执行eval")');

      window.addEventListener("message", (event) => {
        const receivedMessage = event.data;
        const show = document.getElementById("show");
        show.innerHTML = receivedMessage;
      });
    </script>
  </body>
</html>

效果如下:

至此,我们成功实现了沙盒环境的创建和与插件page进行通信。

三、定制策略

通过上面的学习,我们知道在chrome插件的世界里,有两种page:

  • 插件page,例如:popup.htmloption.html 、通过chrome.tabs.create() 创建出来的page等...
  • 沙盒page,通过在manifest.json中的sandbox指定的page。

它们都不是完美的,正如这个世界上没有完美的人一样。

插件Page不是完美的,因为它无法调用"eval",沙盒page不是完美的,因为它无法调用chromeAPI。他们都不完美,但是他们合作起来(通信),可以做到很多事情。

为什么出现上述的现象,就是因为内容安全策略的限制🚫,实际上在chrome插件的世界里,内容安全策略也有一种描述,默认情况下它在manifest.json中是这样的:

json 复制代码
{
  // ...
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self';",
    "sandbox": "sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self';"
  }
  // ...
}

"extension_pages"的属性值,便是插件page的限制内容。同理,"sandbox"的属性值就是沙盒page的限制内容。

我们当然可以去修改这个策略,但是大部分情况下是不需要的,因为对于"extension_pages"来说,默认策略已经是最宽松的限制了,你不能比它更宽松,你只能更严格的限制它。如果你取消某些规则,例如:把"script-src 'self'"解除掉,就会报错:

所以我的建议是,不要去修改这个策略了,因为完全没有必要,因为它已经是最宽松的策略了,如果您不希望自己的人生更加坎坷,不要去轻易修改它。

四、资源

上面的案例我放在github仓库啦:地址在这里

插件系列文章:

另外我有一个自己的网站,欢迎来看看 new-story.cn

创作不易,如果您觉得文章有任何帮助到您的地方,或者触碰到了自己的知识盲区,请帮我点赞收藏一下,或者关注我,我会产出更多高质量文章,最后感谢您的阅读,祝愿大家越来越好。

相关推荐
f8979070701 小时前
layui动态表格出现 横竖间隔线
前端·javascript·layui
鱼跃鹰飞1 小时前
Leecode热题100-295.数据流中的中位数
java·服务器·开发语言·前端·算法·leetcode·面试
二十雨辰1 小时前
[uni-app]小兔鲜-04推荐+分类+详情
前端·javascript·uni-app
霸王蟹2 小时前
Vue3 项目中为啥不需要根标签了?
前端·javascript·vue.js·笔记·学习
小白求学12 小时前
CSS计数器
前端·css
Anita_Sun2 小时前
🌈 Git 全攻略 - Git 的初始设置 ✨
前端
lucifer3113 小时前
深入解析 React 组件封装 —— 从业务需求到性能优化
前端·react.js
等什么君!3 小时前
复习HTML(进阶)
前端·html
儒雅的烤地瓜3 小时前
JS | 如何解决ajax无法后退的问题?
前端·javascript·ajax·pushstate·popstate事件·replacestate
觉醒法师3 小时前
Vue3+TS项目 - ref和useTemplateRef获取组件实例
开发语言·前端·javascript