浏览器支持
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
跨站脚本攻击 (XSS) 向 Web 应用注入恶意脚本的功能已经成为 最大的网络安全漏洞
内容安全政策 (CSP)
是一层额外的安全防护,有助于缓解 XSS。要配置 CSP
将 Content-Security-Policy
HTTP 标头添加到网页,并设置
控制用户代理可以为该网页加载哪些资源。
本页介绍了如何使用基于 Nonce 或哈希值的 CSP 来减少 XSS。 而不使用经常离开网页的常用基于主机许可名单的 CSP 因为它们在大多数配置中可以被绕过。
关键术语:Nonce 是一个随机数,只能使用一次,可用于标记
已将 <script>
个代码标记为受信任。
关键术语:哈希函数是将输入值转换为
值转换为压缩的数值(称为哈希)。您可以使用哈希
(例如 SHA-256),用于标记内嵌
已将 <script>
个代码标记为受信任。
基于 Nonce 或哈希值的内容安全政策通常称为 严格的 CSP。当应用使用严格的 CSP 时,发现 HTML 的攻击者 注入缺陷通常不能利用它们来强制浏览器执行 添加恶意脚本这是因为,只有严格的 CSP 允许经过哈希处理的脚本或在 这样一来,攻击者在不知道正确的 Nonce 的情况下就无法执行脚本 。
为何应使用严格的 CSP?
如果您的网站已有类似script-src www.googleapis.com
的 CSP,
它可能对跨网站没有什么效果。这种类型的 CSP 称为
将 CSP 列入许可名单。它们需要大量的自定义,
被攻击者绕过。
基于加密 Nonce 或哈希值的严格 CSP 可避免这些误区。
严格的 CSP 结构
基本严格的内容安全政策使用以下 HTTP 响应之一 标头:
基于 Nonce 的严格 CSP
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
<ph type="x-smartling-placeholder">
基于哈希的严格 CSP
Content-Security-Policy:
script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
以下属性使类似本例的 CSP“严格”从而保障安全:
- 它使用 Nonce
'nonce-{RANDOM}'
或哈希值'sha256-{HASHED_INLINE_SCRIPT}'
来指明网站开发者信任在哪些<script>
标记中执行 用户的浏览器。 - 它会设置
'strict-dynamic'
减少部署基于 Nonce 或基于哈希的 CSP 的工作量,方法是自动 允许执行受信任的脚本创建的脚本。这也 取消屏蔽大多数第三方 JavaScript 库和微件。 - 它并非基于网址许可名单,因此不会受到 常见 CSP 绕过方式。
- 它会屏蔽不受信任的内嵌脚本,例如内嵌事件处理脚本或
javascript:
URI。 - 它会限制
object-src
以停用 Flash 等危险插件。 - 它会限制
base-uri
,以阻止<base>
标记的注入。这可防止 更改从相对网址加载的脚本的位置。
采用严格的 CSP
如需采用严格的 CSP,您需要执行以下操作:
- 确定您的应用是否应设置基于 Nonce 或基于哈希的 CSP。
- 复制严格 CSP 结构部分中的 CSP 并进行设置 作为应用的响应标头
- 重构 HTML 模板和客户端代码,以删除 与 CSP 不兼容。
- 部署您的 CSP。
您可以使用 Lighthouse
(带 --preset=experimental
标志的 v7.3.0 及更高版本)最佳实践审核
检查您的网站是否实施了内容安全政策
能够有效抵御 XSS。
第 1 步:确定是需要基于 Nonce 还是基于哈希的 CSP
这两种严格 CSP 的运作方式如下:
基于 Nonce 的 CSP
使用基于 Nonce 的 CSP 时,您可以在运行时生成一个随机数,并将其包含在 内容安全政策,并将其与网页中的每个脚本标记相关联。攻击者 不能在网页中包含或运行恶意脚本,因为它们需要 猜出该脚本的正确随机数。这只有在 不可猜测,并且是在运行时为每个响应新生成的。
对在服务器上呈现的 HTML 网页使用基于 Nonce 的 CSP。对于这些网页 则可以为每个响应创建一个新的随机数。
基于哈希的 CSP
对于基于哈希的 CSP,每个内嵌脚本标记的哈希都会添加到 CSP。 每个脚本都有不同的哈希。攻击者无法添加或运行 脚本,因为该脚本的哈希值需要包含在 供其运行的 CSP。
对于以静态方式提供的 HTML 页面或需要 缓存内容。例如,您可以将基于哈希的 CSP 用于单页网站 使用 Angular、React 等框架构建的应用, 静态传送,无需服务器端呈现。
第 2 步:设置严格的 CSP 并准备脚本
设置 CSP 时,您有以下几种选择:
- “仅报告”模式 (
Content-Security-Policy-Report-Only
) 或强制执行模式 (Content-Security-Policy
).在“仅报告”模式下,CSP 不会阻止 因此您的网站上不会出现任何中断 但您可以查看错误并 报告任何被屏蔽的内容在本地,当您 则并不重要,因为这两种模式都会显示 错误。强制执行模式可以帮助您找到 因为阻止资源会使您的 网页看起来已损坏。“仅查看报告”模式在后面的工作过程中会变得最有用 (参见第 5 步)。 - 标头或 HTML
<meta>
标记。对于本地开发,<meta>
标记可能更为 便于您调整 CSP 并快速了解它对网站的影响。 但是:- 稍后,在生产环境中部署 CSP 时,我们建议将其设置为 HTTP 标头。
- 如果您想在“仅报告”模式下设置 CSP,则需要将其设置为 标头中,因为 CSP 元标记不支持“仅限报告”模式。
设置以下 Content-Security-Policy
HTTP 响应
标头:
Content-Security-Policy: script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'; base-uri 'none';<ph type="x-smartling-placeholder">
为 CSP 生成 Nonce
Nonce 是一个随机数,每次网页加载仅使用一次。基于 Nonce 只有在攻击者无法猜测 Nonce 值时,CSP 才能缓解 XSS。答 CSP Nonce 必须:
- 强加密随机值(最好长度为 128 位以上)
- 为每次回复新生成的内容
- Base64 编码
以下示例说明了如何在服务器端框架中添加 CSP nonce:
- Django (python)
- 极速 (JavaScript):
const app = express(); app.get('/', function(request, response) { // Generate a new random nonce value for every response. const nonce = crypto.randomBytes(16).toString("base64"); // Set the strict nonce-based CSP response header const csp = `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`; response.set("Content-Security-Policy", csp); // Every <script> tag in your application should set the `nonce` attribute to this value. response.render(template, { nonce: nonce }); });
向 <script>
元素添加 nonce
属性
对于基于 Nonce 的 CSP,每个 <script>
元素都必须
具有与随机 Nonce 匹配的 nonce
属性
CSP 标头中指定的值。所有脚本都可以有相同的
Nonce。第一步是将这些属性添加到所有脚本中,以便
而 CSP 则允许这种行为。
设置以下 Content-Security-Policy
HTTP 响应
标头:
Content-Security-Policy: script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic'; object-src 'none'; base-uri 'none';
对于多个内嵌脚本,语法如下:
'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'
。
动态加载源代码
由于只有内嵌脚本跨浏览器支持 CSP 哈希, 您必须使用内嵌脚本动态加载所有第三方脚本。 来源脚本的哈希无法在浏览器之间得到很好的支持。
<script> var scripts = [ 'https://example.org/foo.js', 'https://example.org/bar.js']; scripts.forEach(function(scriptUrl) { var s = document.createElement('script'); s.src = scriptUrl; s.async = false; // to preserve execution order document.head.appendChild(s); }); </script><ph type="x-smartling-placeholder">
<script src="https://example.org/foo.js"></script> <script src="https://example.org/bar.js"></script><ph type="x-smartling-placeholder">
脚本加载注意事项
内嵌脚本示例添加了 s.async = false
,以确保
foo
在 bar
之前执行,即使
首先加载 bar
。在此代码段中,s.async = false
在脚本加载时不会阻止解析器,因为脚本
动态添加。只有在脚本执行时,解析器才会停止,因为
对 async
脚本会是如此。不过,有了这个代码段
注意:
-
一个或两个脚本可能会在文档完成之前执行
下载。如果您希望在
脚本执行完毕后,请等待
DOMContentLoaded
事件,然后再 附加脚本。如果这导致性能问题 脚本还未尽早开始下载,请在网页上较早地预加载代码。 -
defer = true
不会执行任何操作。如果您需要 请根据需要手动运行脚本。
第 3 步:重构 HTML 模板和客户端代码
内嵌事件处理脚本(例如 onclick="…"
、onerror="…"
)和 JavaScript URI
(<a href="javascript:…">
) 可用于运行脚本。这意味着
发现 XSS 错误的攻击者可注入此类 HTML 并执行恶意
JavaScript。基于 Nonce 或基于哈希的 CSP 禁止使用此类标记。
如果您的网站使用以上任一格式,您需要将其重构,以使网站更加安全
替代选项。
如果您在上一步中启用了 CSP,将能够在 控制台。
在大多数情况下,解决方法很简单:
重构内嵌事件处理脚本
<span id="things">A thing.</span> <script nonce="${nonce}"> document.getElementById('things').addEventListener('click', doThings); </script><ph type="x-smartling-placeholder">
<span onclick="doThings();">A thing.</span><ph type="x-smartling-placeholder">
重构 javascript:
URI
<a id="foo">foo</a> <script nonce="${nonce}"> document.getElementById('foo').addEventListener('click', linkClicked); </script><ph type="x-smartling-placeholder">
<a href="javascript:linkClicked()">foo</a><ph type="x-smartling-placeholder">
从 JavaScript 中移除 eval()
如果您的应用使用 eval()
将 JSON 字符串序列化转换为 JS
对象,应将此类实例重构为 JSON.parse()
,这也是
更快。
即使无法移除所有使用 eval()
的情况,您仍可设置基于 Nonce 的严格限制
但必须使用 'unsafe-eval'
CSP 关键字,这样会使您的
政策安全性略低。
您可以在此严格 CSP 中找到这些以及此类重构的更多示例 codelab:
第 4 步(可选):添加后备版本以支持旧版浏览器
浏览器支持
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
如果您需要支持旧版浏览器,请执行以下操作:
- 使用
strict-dynamic
需要添加https:
作为早期版本的后备 Safari 版本。执行此操作后:- 所有支持
strict-dynamic
的浏览器都会忽略https:
回退, 因此不会削弱政策的效力 - 在旧版浏览器中,外部来源的脚本只有在来自
HTTPS 源。这种方式的安全性不及严格的 CSP,
可防止一些常见的 XSS 原因,例如
javascript:
URI 注入。
- 所有支持
- 为了确保与非常旧的浏览器版本(4 年以上)兼容,您可以在添加
unsafe-inline
作为后备选项。最近使用过的所有浏览器都会忽略unsafe-inline
如果存在 CSP nonce 或 hash。
Content-Security-Policy:
script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';
第 5 步:部署 CSP
在确认您的 CSP 不会阻止 本地开发环境,因此可以将 CSP 部署到预演环境,然后再部署到 生产环境:
- (可选)使用
Content-Security-Policy-Report-Only
标头。“仅报告”模式非常便于 先在生产环境中测试潜在的破坏性更改(如新的 CSP),然后再 开始实施 CSP 限制在“仅报告”模式下,您的内容安全政策不会 影响应用的行为,但浏览器仍会生成控制台错误 以及遇到与 CSP 不兼容的模式时生成的违规报告。 以便您了解最终用户在哪些方面会遇到问题。有关 信息,请参阅 Reporting API。 - 如果您确信 CSP 不会破坏您的网站对最终用户的影响,
使用
Content-Security-Policy
响应标头部署 CSP。周三 建议您使用 HTTP 标头服务器端设置 CSP 比<meta>
标记更安全。完成此步骤后,您的 CSP 即会启动 保护您的应用免受 XSS 攻击。
限制
严格的 CSP 通常会提供一个更强大的额外安全层,有助于
来缓解 XSS。在大多数情况下,CSP 可以显著缩小攻击面
拒绝 javascript:
URI 等危险模式。不过,根据
您正在使用的 CSP(随机数、哈希值,无论是否包含 'strict-dynamic'
),
CSP 不能保护您的应用的情况:
- 如果您对脚本运行 Nonce,但又直接注入了正文或
该
<script>
元素的src
参数。 - 是否有注入到动态创建的脚本的位置
(
document.createElement('script')
),包括到任何库函数中 。script
这个 包含一些常见 API,例如 jQuery 的.html()
,以及.get()
和 jQuery 中的.post()
<3.0。 - 旧版 AngularJS 应用中是否存在模板注入。攻击者 可以注入 AngularJS 模板 执行任意 JavaScript。
- 如果政策包含
'unsafe-eval'
,则向eval()
注入数据,setTimeout()
以及其他一些很少使用的 API。
开发者和安全工程师应特别注意 安全审核期间的监控模式如需了解更多详情,请参阅 内容安全政策:加固和缓解之间的成功中涉及的案例。