使用内容安全策略 (CSP) 来控制哪些资源可以运行
若要控制扩展可以运行哪些内容,请在扩展的 manifest.json
文件中,根据以下语法使用 content_security_policy
密钥及其策略字符串值:
{
...,
"content_security_policy": "[policy string]"
...
}
例如,以下是默认内容安全策略,如下文默认 策略限制中所述:
{
...,
"content_security_policy": "script-src 'self'; object-src 'self'; worker-src 'self'"
...
}
为了缓解大量潜在的跨站点脚本问题,Microsoft Edge 扩展系统将内容安全策略 (CSP) 。 CSP 引入了一些严格的策略,使扩展在默认情况下更安全,并让你能够创建和强制执行规则,以管理扩展和应用程序可以加载和运行的内容类型。
通常,CSP 充当扩展加载或运行的资源的阻止/允许列表机制。 通过为扩展定义合理的策略,可以仔细考虑扩展所需的资源,并要求浏览器确保这些资源是扩展有权访问的唯一资源。 这些策略在扩展请求的主机权限之上提供安全性;它们是额外的保护层,而不是替代层。
相反,在网页中,此类策略是通过 HTTP 标头或通过 meta
元素定义的。 但在 Microsoft Edge 扩展系统中,HTTP 标头或 meta
元素不是适当的机制。
请参阅:
- MDN 上的内容安全策略 (CSP) 。
- 清单 - Chrome 扩展>参考中的内容安全策略。
默认策略限制
未定义 manifest_version
的包没有默认的内容安全策略。
使用的 manifest_version
包具有以下默认内容安全策略:
该策略通过以下三种方式限制扩展和应用程序来增加安全性:
Eval 和相关函数已禁用
如下所示的代码不起作用:
alert(eval("foo.bar.baz"));
window.setTimeout("alert('hi')", 10);
window.setInterval("alert('hi')", 10);
new Function("return foo.bar.baz");
评估此类 JavaScript 字符串是一种常见的 XSS 攻击途径。 相反,应编写如下代码:
alert(foo && foo.bar && foo.bar.baz);
window.setTimeout(function() { alert('hi'); }, 10);
window.setInterval(function() { alert('hi'); }, 10);
function() { return foo && foo.bar && foo.bar.baz };
内联 JavaScript 未运行
不会运行内联 JavaScript 。 此限制禁止内联 <script>
块和内联事件处理程序,例如 <button onclick="...">
。
第一个限制通过使你无法意外运行恶意第三方提供的脚本来消除大量跨站点脚本攻击。 但是,它确实要求你编写代码时,在内容和行为之间完全分离。 示例可能会使这一点更加清晰。 可以尝试将浏览器操作弹出窗口编写为包含以下内容的单个 pop-up.html
:
<!doctype html>
<html>
<head>
<title>My Awesome Pop-up!</title>
<script>
function awesome() {
// do something awesome!
}
function totallyAwesome() {
// do something TOTALLY awesome!
}
function clickHandler(element) {
setTimeout("awesome(); totallyAwesome()", 1000);
}
function main() {
// Initialization work goes here.
}
</script>
</head>
<body onload="main();">
<button onclick="clickHandler(this)">
Click for awesomeness!
</button>
</body>
</html>
但必须改变三件事才能使它按预期方式工作:
定义
clickHandler
必须移动到外部 JavaScript 文件中, (popup.js
可能是一个很好的目标) 。内联事件处理程序定义必须重写
addEventListener
,并将其提取到 中popup.js
。 如果当前使用 等<body onload="main();">
代码启动程序,请考虑根据要求,通过挂钩到DOMContentLoaded
文档的 事件或load
窗口的事件来替换程序。 使用前者,因为它通常会更快地触发。setTimeout
必须重写调用以避免将字符串"awesome(); totallyAwesome()"
转换为 JavaScript 以运行。
这些更改可能如下所示:
function awesome() {
// Do something awesome!
}
function totallyAwesome() {
// do something TOTALLY awesome!
}
function awesomeTask() {
awesome();
totallyAwesome();
}
function clickHandler(e) {
setTimeout(awesomeTask, 1000);
}
function main() {
// Initialization work goes here.
}
// Add event listeners once the DOM has fully loaded by listening for the
// `DOMContentLoaded` event on the document, and adding your listeners to
// specific elements when it triggers.
document.addEventListener('DOMContentLoaded', function () {
document.querySelector('button').addEventListener('click', clickHandler);
main();
});
<!doctype html>
<html>
<head>
<title>My Awesome Pop-up!</title>
<script src="popup.js"></script>
</head>
<body>
<button>Click for awesomeness!</button>
</body>
</html>
仅加载本地脚本和对象资源
脚本和对象资源只能从扩展包加载,不能从整个 Web 加载。 这可确保扩展仅运行专门批准的代码,防止活动网络攻击者恶意重定向对资源的请求。
请考虑在扩展包中包含特定版本的 jQuery,而不是编写依赖于 jQuery (或任何其他库的代码) 从外部 CDN 加载。 也就是说,而不是:
<!doctype html>
<html>
<head>
<title>My Awesome Pop-up!</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
</head>
<body>
<button>Click for awesomeness!</button>
</body>
</html>
请改用以下方法。 下载文件,将其包含在包中,然后写入:
<!doctype html>
<html>
<head>
<title>My Awesome Pop-up!</title>
<script src="jquery.min.js"></script>
</head>
<body>
<button>Click for awesomeness!</button>
</body>
</html>
放宽默认策略
可以允许运行以下类型的脚本:
详细信息如下所示。
内联脚本
可以通过在策略中指定源代码的 base64 编码哈希来允许内联脚本。 此哈希必须由使用的哈希算法 (sha256、sha384 或 sha512) 作为前缀。 有关示例,请参阅脚本>元素的 <W3C > 哈希用法。
远程脚本
如果需要一些外部 JavaScript 或对象资源,可以通过允许列出应接受脚本的安全源,在有限范围内放宽策略。 验证使用扩展的提升权限加载的运行时资源是否与预期资源完全一致,并且未被活动网络攻击者替换。 由于 中间人攻击 通过 HTTP 是微不足道且无法检测到的,因此不会接受这些来源。
目前,可以允许列出具有以下方案的源: blob
、 filesystem
、 https
和 extension
。 必须为 和 extension
方案显式指定https
源的主机部分。 不允许使用 https: https://*
和 https://*.com
等泛型通配符;允许使用子域通配符(如 https://*.example.com
)。
公共后缀列表中的域也被视为泛型顶级域。 若要从这些域加载资源,必须显式列出子域。 例如, https://*.cloudfront.net
无效,但 https://XXXX.cloudfront.net
和 https://*.XXXX.cloudfront.net
可以是 allowlisted
。
为了便于开发,通过 HTTP 从本地计算机上的服务器加载的资源可以是 allowlisted
。 可以在 或 http://localhost
的任何端口http://127.0.0.1
上将脚本和对象源列入允许列表。
注意
对通过 HTTP 加载的资源的限制仅适用于那些直接运行的资源。 例如, XMLHTTPRequest
你仍然可以自由地连接到任何喜欢的源;默认策略不会以任何方式限制 connect-src
或任何其他 CSP 指令。
允许通过 HTTPS 加载 example.com
脚本资源的宽松策略定义可能如下所示:
"content_security_policy": "script-src 'self' https://example.com; object-src 'self'"
注意
和 object-src
都script-src
由策略定义。 Microsoft Edge 不接受不将每个值限制为至少) “”self
(的策略。
已评估的 JavaScript
可以通过将 添加到策略来放宽针对 eval()
和 等new Function(String)
setTimeout(String)
setInterval(String)
相关函数的策略:unsafe-eval
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
但是,应避免放宽策略。 这些类型的函数是臭名昭著的 XSS 攻击途径。
收紧默认策略
可以在扩展允许的任何范围内收紧此策略,以牺牲便利性为代价来增加安全性。 若要指定扩展只能加载任何类型的资源 (映像等,请从关联的扩展包) ,例如,策略 default-src 'self'
可能合适。
内容脚本
正在讨论的策略适用于扩展的背景页和事件页。 内容脚本如何应用于扩展的内容脚本更为复杂。
内容脚本通常不受扩展的 CSP 的约束。 由于内容脚本不是 HTML,因此main影响是,即使扩展的 CSP 未指定 unsafe-eval
,它们也可以使用 eval
,尽管不建议这样做。 此外,页面的 CSP 不适用于内容脚本。 更复杂的是 <script>
内容脚本创建并放入其运行页面的 DOM 中的标记。 这些脚本将作为 DOM 注入脚本进行引用。
DOM 注入的脚本在注入页后立即按预期运行。 假设一个内容脚本,其中包含以下代码作为一个简单的示例:
document.write("<script>alert(1);</script>");
此内容脚本在 上document.write()
立即导致 alert
。 请注意,无论页面指定的策略如何,此都会运行。 但是,该行为变得更加复杂,无论是在 DOM 注入的脚本内部,还是对于注入时不会立即运行的任何脚本。
假设扩展在提供指定 script-src 'self'
的关联 CSP 的页面上运行。 现在,假设内容脚本运行以下代码:
document.write("<button onclick='alert(1);'>click me</button>'");
如果用户单击该按钮,脚本 onclick
不会运行。 这是因为脚本未立即运行,在事件发生前 click
未解释的代码不被视为内容脚本的一部分,因此页面的 CSP (不是扩展) 限制行为。 由于 CSP 未指定 unsafe-inline
,因此阻止内联事件处理程序。
在这种情况下,实现所需行为的正确方法是从内容脚本添加 onclick
处理程序作为函数,如下所示:
document.write("<button id='mybutton'>click me</button>'");
var button = document.getElementById('mybutton');
button.onclick = function() {
alert(1);
};
如果内容脚本运行以下命令,则会出现另一个类似的问题:
var script = document.createElement('script');
script.innerHTML = 'alert(1);'
document.getElementById('body').appendChild(script);
在这种情况下,脚本将运行,并显示警报。 但是,请考虑这种情况:
var script = document.createElement('script');
script.innerHTML = 'eval("alert(1);")';
=document.getElementById('body').appendChild(script);
运行初始脚本时,将阻止对 eval
的调用。 也就是说,虽然允许初始脚本运行时,但脚本内的行为由页面的 CSP 管理。 因此,根据你在扩展中编写 DOM 注入脚本的方式,对页面的 CSP 的更改可能会影响扩展的行为。
由于内容脚本不受页面的 CSP 影响,因此这是将扩展尽可能多的行为放入内容脚本而不是 DOM 注入脚本的一个很好的理由。
另请参阅
- MDN 上的内容安全策略 (CSP) 。
- 清单 - Chrome 扩展>参考中的内容安全策略。
注意
此页面的某些部分是根据 Google 创建和共享的作品所做的修改,并根据 Creative Commons Attribution 4.0 International License 中描述的条款使用。 原始页面 在此处找到。
本作品根据 Creative Commons Attribution 4.0 International License 获得许可。