一、前面的话
今天我们来认识一个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
通常指的是沙箱环境,它是一种安全机制,用于隔离插件的代码和操作,以防止恶意行为或不当访问用户的敏感数据。它有以下几个作用:
- 代码隔离: 沙箱环境将插件的代码与浏览器的核心代码和其他插件隔离开来。这意味着插件的JavaScript代码无法直接访问浏览器的敏感信息或其他插件的数据,从而增强了安全性。
- 限制执行环境: 沙箱通常会限制插件的代码执行环境,防止插件滥用底层系统资源或执行危险操作。这包括限制文件系统访问、网络请求和系统级操作等。
- 权限控制: Chrome插件系统通过权限模型来控制插件的功能。插件需要在其
manifest.json
文件中声明所需的权限,用户在安装插件时需要授予这些权限。这有助于确保插件只能执行其明确定义的任务。 - 内容脚本沙箱: 插件中的内容脚本(Content Scripts)通常在Web页面上运行,它们也受到沙箱机制的保护。这意味着内容脚本的代码只能访问所嵌入页面的内容,而无法直接访问插件的API或其他插件资源。
- 沙箱沙盒化: 沙箱环境本身也可以进一步沙盒化,以确保插件中的不同组件之间相互隔离。这意味着插件的不同部分(如背景脚本、内容脚本和弹出窗口)之间通常不能直接共享变量或数据。
二、沙盒解决方案
接下来我们将采用在沙箱环境下运行一段脚本,首先需要在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,结果如下:
-
eval()
能够正常执行
-
- 能够使用
内联脚本
- 能够使用
-
- 不同于background环境中的chrome对象,
sandbox
环境中的chrome只有极少的属性,也就是说在sandbox
环境中,无法调用chromeAPI中类似storage、tabs等高级特性。
- 不同于background环境中的chrome对象,
所以结论是:我们其实可以在插件中使用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.html 、option.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
创作不易,如果您觉得文章有任何帮助到您的地方,或者触碰到了自己的知识盲区,请帮我点赞收藏一下,或者关注我,我会产出更多高质量文章,最后感谢您的阅读,祝愿大家越来越好。