Web 开发人员需要了解的内容安全策略 (CSP) - 第二部分





5.00/5 (3投票s)
本文继续讨论内容安全策略,包括 unsafe-inline、unsafe-eval、nonce、加密哈希等。
引言
在文章第一部分中,我们研究了如何通过白名单信任源来阻止不受信任网站上的恶意代码执行。在本文的第二部分,我们将深入探讨 CSP 1 中的 unsafe-inline
和 unsafe-eval
关键字,并结合 CSP 2 中引入的 nonce 和哈希,通过只允许信任的内联代码执行来避免使用 unsafe-inline
。请注意,unsafe-inline
和 unsafe-eval
等关键字必须用单引号括起来。
unsafe-inline
有时,网页会包含一些内联 JavaScript 或样式表,由于涉及的工作量巨大,无法将其外部化到单独的文件中。这时 unsafe-inline
就派上用场了。在这个例子中,我们有一个 HTML,它会定期显示时间。
<html>
<head>
<title>unsafe-line in Action</title>
<head>
<body>
<p id="time"></p>
<script type="text/javascript">
function displayTime()
{
var d = new Date();
var n = d.toLocaleTimeString();
document.getElementById('time').innerHTML = n;
setTimeout(function () {
displayTime()
}, 500);
}
displayTime();
</script>
</body>
</html>
复制 HTML 并将其保存为 HTML 文件。然后在 Web 浏览器中打开查看。我们可以看到时间已显示。您的时间很可能与我的不同。让我们在 <head>
部分添加一个 CSP <meta>
标签。
下午 6:32:32
<meta http-equiv="Content-Security-Policy" content="default-src 'self';">
砰!时间不显示了,现在出现了错误。这是我在 Chrome 上遇到的错误。
由于违反了以下内容安全策略指令,拒绝执行内联脚本:“default-src 'self'”。必须使用 'unsafe-inline' 关键字、哈希 ('sha256-TVjy1frkE+v+8vB4X884wNJ7xy5bKc32l3WYqLZZ44o=') 或 nonce ('nonce-...') 来启用内联执行。另外请注意,'script-src' 未显式设置,因此 'default-src' 作为回退使用。
让我们使用 unsafe-inline
来启用我们的内联代码。
<meta http-equiv="Content-Security-Policy" content="default-src 'self';
script-src 'unsafe-inline';">
这次,HTML 显示了时间。
下午 6:35:32
Nonce 和哈希来拯救
unsafe-inline
是一种全有或全无的解决方案,存在很多不足之处。启用 unsafe-inline
后,我们也有可能允许恶意注入的代码。
CSP 2 引入了 nonce 和哈希来解决 unsafe-inline 暴露出的这个巨大的安全漏洞。它们的工作原理是:允许具有相同 nonce 值或正确加密哈希的 JavaScript 或 CSS 部分执行。Nonce 和哈希必须用单引号括起来。请记住,nonce 是仅使用一次的 base64
编码数字,每次获取页面时都需要更新。只要 CSP 中的 nonce 与脚本/样式部分中的 nonce 匹配,JavaScript 或 CSS 就会被允许。下面是 script-src
nonce 和 style-src
nonce 的示例。
<meta http-equiv="Content-Security-Policy" content="default-src 'self';
script-src 'nonce-2726c7f26c';">
<script nonce="2726c7f26c">
// code remains unchanged, so it is not shown.
</script>
<meta http-equiv="Content-Security-Policy" content="default-src 'self';
style-src 'nonce-5823c7f85c';">
<style nonce="5823c7f85c">
// CSS code not shown
</style>
加密哈希的工作原理是计算内联代码(包括其空格)的加密消息摘要,然后将哈希以 base64
格式编码。对于 Chrome 用户来说,您很幸运,因为 Chrome 在开发者控制台中显示错误时会为您计算此哈希。我再次重现了上述错误。
由于违反了以下内容安全策略指令,拒绝执行内联脚本:“default-src 'self'”。必须使用 'unsafe-inline' 关键字、哈希 ('sha256-TVjy1frkE+v+8vB4X884wNJ7xy5bKc32l3WYqLZZ44o=') 或 nonce ('nonce-...') 来启用内联执行。另外请注意,'script-src' 未显式设置,因此 'default-src' 作为回退使用。
您需要做的就是通过将 SHA256 哈希复制到 script-src
指令中来修复错误。
<meta http-equiv="Content-Security-Policy" content="default-src 'self';
script-src 'sha256-TVjy1frkE+v+8vB4X884wNJ7xy5bKc32l3WYqLZZ44o=';">
<script>
// code remains unchanged, so it is not shown.
</script>
对于样式部分,概念也是一样的。
<meta http-equiv="Content-Security-Policy" content="default-src 'self';
style-src 'sha256-pkvqLyskjufPOv5VOGnLcoqyD2oDwsfaPxxvXCQdq9Y=';">
<style>
// CSS code not shown
</style>
看,错误消失了,时间也回来了!
Nonce 与加密哈希的比较
在 nonce 和加密哈希之间进行选择时,哪种方法更可取?对于后者,每次更新代码时都必须重新计算哈希,而前者需要精心设计的随机 nonce 生成策略,以确保 nonce 不易被猜测。
外部 JS 和 CSS 的加密哈希
通过子资源完整性 (SRI),可以对外部 JavaScript 和 CSS 文件采用相同的加密哈希方法。子资源完整性是一项安全功能,它允许浏览器验证获取的资源(例如来自 CDN 的资源)是否已按原样交付。要使用 SRI,只需计算哈希,将哈希以 base64
格式编码,并将其添加到 script
或 style
标签的 integrity
属性下。并且请记住,分别在 JS 或 CSS 下启用 require-sri-for
指令,如下所示。
Content-Security-Policy: require-sri-for script;
<script src="https://mysite.com/example.js"
crossorigin="anonymous"></script>
Content-Security-Policy: require-sri-for style;
<link href="https://mysite.com/example.css" rel="stylesheet" type="text/css"
crossorigin="anonymous">
当脚本或样式表与其完整性值不匹配时,浏览器将拒绝执行脚本或应用样式表。
unsafe-eval
有时,遗留库无法轻松修改,并且使用 eval()
来动态生成 JavaScript 代码。在这种情况下,解决方案是:如果可行,替换库;或者作为最后的手段,通过 unsafe-eval
关键字允许动态 JavaScript 代码的生成。
防止点击劫持
点击劫持是一种恶意技术,它会欺骗用户点击与他们所看到的(通常是不可见的)内容不同的东西。例如,一个网页被一个透明度设置为零的 iframe
覆盖,当用户点击一个合法的链接时,他并不知道自己实际上是在点击那个不可见 iframe 上的一个链接或按钮。CSP 2 引入了 frame-ancestors
指令来白名单化允许嵌入您网页的 URL。
将 HTTP 请求升级到 HTTPS
通过设置 upgrade-insecure-requests
指令,可以指示 Web 浏览器使用 HTTPS 方案获取所有资源。另一个指令 block-all-mixed-content
则禁止在 HTTPS 页面加载时使用 HTTP 加载任何资源。实际上,您只需要设置 upgrade-insecure-requests
或 block-all-mixed-content
中的一个,而不是两者都设置。
零风险 CSP:仅报告模式
CSP 白名单方法本身存在固有的风险,因为合法的内容来源可能会被忽略或遗漏在 CSP 之外,导致某些功能中断。这根本是不可接受的。在 CSP 2 中,可以通过将 Content-Security-Policy
重命名为 Content-Security-Policy-Report-Only
来关闭执行并切换到仅报告模式,同时请记住添加 report-uri
和 report-to
指令作为报告目的地。请注意,report-uri
和 report-to
也可以添加到正常的阻止违规的 Content-Security-Policy
中。
为什么需要指定两个指向同一报告目的地的指令?简而言之,在 CSP 3 中,report-uri
已重命名为 report-to
,但在撰写本文时,还没有任何 Web 浏览器支持 report-to
指令。为了使您的 CSP 具有前瞻性,最好同时指定 report-to
和 report-uri
。开发者需要注意的一点是,这些报告指令在 <meta>
元素中不支持,这意味着它必须在 CSP 响应头中指定。违规报告以 JSON 格式通过 HTTP POST 方法发送。每当有违规报告时,可能意味着两种情况之一:一个受信任的源未被列入白名单,或者网页正在遭受 XSS 攻击。
结论
在本篇 2 部分文章的最后一节中,我们回顾了 unsafe-inline
和 unsafe-eval
以启用内联代码和动态代码生成。我们还研究了 nonce 和加密哈希如何帮助开发者只运行自己的代码。我们简要介绍了防止点击劫持、将请求升级到 HTTPS 以及 CSP 的仅报告模式(其中 CSP 不被强制执行)。