如何使用内容安全策略保护Node.js应用程序

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 自由软件基金会 来接受捐赠。

介绍

当浏览器加载页面时,它会执行大量代码来呈现内容。 代码可以来自与根文档相同的来源,也可以来自不同的来源。 默认情况下,浏览器不区分这两者并执行页面请求的任何代码,而不管来源如何。 攻击者利用此漏洞向页面恶意注入脚本,然后执行这些脚本,因为浏览器无法确定内容是否有害。 这些情况是内容安全策略 (CSP) 可以提供保护的地方。

CSP 是一个 HTTP 标头,可提供额外的安全层来抵御代码注入攻击,例如 跨站点脚本 (XSS)clickjacking 和其他类似的漏洞利用。 它有助于创建受信任内容的“允许列表”,并阻止执行来自不允许列表中不存在的源的代码。 它还会向您选择的 URL 报告任何违反政策的行为,以便您及时了解潜在的安全攻击。

使用 CSP 标头,您可以指定浏览器可以加载的网站内容的批准来源。 任何不是来自批准来源的代码都将被阻止执行,这使得攻击者更难以注入内容和虹吸数据。

在本教程中,您将通过在 示例 Node.js 应用程序 中实现一种保护来查看 CSP 标头提供的不同保护。 您还将收集 CSP 违规的 JSON 报告,以快速发现问题并修复漏洞。

先决条件

要学习本教程,您将需要以下内容:

  • 您的机器上安装了最新版本的 Node.js。 按照您的操作系统的相关 如何安装 Node.js 教程中的步骤设置 Node.js 开发环境。

您还应该使用最新的浏览器版本,最好是 Chrome,因为在撰写本文时(2020 年 11 月),它对 CSP 3 级指令 的支持最好。 此外,请确保在测试 CSP 实现时禁用任何第三方扩展,以免它们干扰控制台中呈现的违规报告。

第 1 步 — 设置演示项目

为了演示创建内容安全策略的过程,我们将介绍为这个 演示项目 实施策略的整个过程。 它是一个包含各种内容的单页网站,类似于典型的网站或应用程序。 它包括一个小的 Vue.js 应用程序、YouTube embeds,以及一些来自 Unsplash 的图像。 它还使用 Google 字体Bootstrap 框架 ,它通过 内容交付网络 (CDN) 加载。

在这一步中,您将在测试服务器或本地机器上设置演示项目并在浏览器中查看它。

首先,使用以下命令将项目克隆到您的文件系统:

git clone https://github.com/do-community/csp-demo

设置项目目录后,使用以下命令更改为该目录:

cd csp-demo

接下来,使用下一个命令安装 package.json 文件中指定的依赖项。 您使用 express 包来设置 Web 服务器,而 nodemon 有助于在节点应用程序检测到目录中的文件更改时自动重启:

npm install

一旦您安装了依赖项,请输入以下命令以在端口 5500 上启动 Web 服务器:

npm start

您现在可以在浏览器中访问 your_server_ip:5500localhost:5500 来查看演示页面。 您会在页面上找到文本 Hello World!、YouTube 嵌入和一些图像。

在下一节中,我们将实施仅涵盖最基本保护的 CSP 策略。 然后,我们将在后续部分中以此为基础,发现我们需要在页面上允许的所有合法资源。

第 2 步 — 实施基本 CSP

让我们继续编写一个 CSP 策略,将字体、图像、脚本、样式和嵌入限制为仅来自当前主机的那些。 以下是实现此目的的响应标头:

Content-Security-Policy: default-src 'self'; font-src 'self'; img-src 'self'; script-src 'self'; style-src 'self'; frame-src 'self';

以下是此标头中策略指令的说明:

  • font-src 定义了可以从中加载字体的来源。
  • img-src 定义了允许加载图像的来源。
  • script-src 控制网页上的任何脚本加载权限。
  • style-src 是允许样式表源的指令。
  • frame-src 定义允许的帧嵌入源。 (在 CSP 级别 2 已弃用,但在级别 3 中恢复。)
  • default-src 为某些指令定义后备策略,如果它们没有在标头中明确指定。 这是 完整的指令列表 回退到 default-src

在此示例中,所有指定的指令在其源列表中都分配有 'self' 关键字。 这表示只允许执行来自当前主机的资源(包括 URL 方案和端口号)。 例如,script-src 'self' 允许从当前主机执行脚本,但它会阻止所有其他脚本源。

让我们继续将标头添加到我们的 Node.js 项目中。

让您的应用程序运行并打开一个新的终端窗口以使用您的 server.js 文件:

nano server.js

接下来,将示例中的 CSP 标头添加到 Express 中间件层中。 这可确保您在来自服务器的每个响应中都包含标头:

服务器.js

const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
const app = express();

app.use(function (req, res, next) {
  res.setHeader(
    'Content-Security-Policy',
    "default-src 'self'; font-src 'self'; img-src 'self'; script-src 'self'; style-src 'self'; frame-src 'self'"
  );
  next();
});

app.use(bodyParser.json());
app.use(express.static(path.join(__dirname)));

app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname + '/index.html'));
});

const server = app.listen(process.env.PORT || 5500, () => {
  const { port } = server.address();
  console.log(`Server running on PORT ${port}`);
});

保存文件并在浏览器中重新加载项目。 您会注意到该页面已完全损坏。

我们的 CSP 标头按预期工作,并且我们包含在页面上的所有外部源都已被阻止加载,因为它们违反了定义的策略。 但是,这不是测试全新策略的理想方法,因为它可能会在发生违规行为时破坏网站。

这就是存在 Content-Security-Policy-Report-Only 标头的原因。 您可以使用它代替 Content-Security-Policy 来阻止浏览器执行策略,同时仍报告发生的违规行为——这意味着您可以优化策略而不会使您的网站面临风险。 对策略感到满意后,您可以切换回强制标头,以便激活保护措施。

继续用 server.js 文件中的 Content-Security-Policy-Report-Only 替换 Content-Security-Policy 标头:

nano server.js

添加以下突出显示的代码:

服务器.js

. . .
app.use(function (req, res, next) {
  res.setHeader(
    'Content-Security-Policy-Report-Only',
    "default-src 'self'; font-src 'self'; img-src 'self'; script-src 'self'; style-src 'self'; frame-src 'self'"
  );
  next();
});
. . .

保存文件并在浏览器中重新加载页面。 页面返回工作状态,但浏览器控制台仍报告 CSP 违规。 每个违规都以 [Report Only] 为前缀,表示该策略未强制执行。

在本节中,我们创建了 CSP 的初始实现并将其设置为仅报告模式,以便我们可以在不导致站点中断的情况下对其进行改进。 在下一节中,我们将修复通过初始 CSP 触发的违规行为。

第 3 步 — 修复政策违规

我们在上一节中实施的政策引发了一些违规行为,因为我们将所有资源仅限于来源——但是,我们在页面上有几个第三方资产。

解决 CSP 违规的两种方法是:批准策略中的来源,或删除触发违规的代码。 由于合法资源会触发所有违规行为,因此我们将主要关注本节中的前一个选项。

打开浏览器控制台。 它将显示所有当前违反 CSP 的行为。 让我们解决每个问题。

允许样式表

控制台中的前两个违规来自 Google 字体和 Bootstrap 样式表,您分别从 https://fonts.googleapis.comhttps://cdn.jsdelivr.net 加载它们。 您可以通过 style-src 指令在页面上同时允许它们:

style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net;

这指定应该在页面上执行来自源主机 https://fonts.googleapis.comhttps://cdn.jsdelivr.net 的 CSS 文件。 该策略非常广泛,因为它允许来自白名单域的任何样式表(不仅仅是您当前使用的那些)。

我们可以通过使用我们希望允许的确切文件或目录来更具体:

style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css;

现在,它只允许执行精确的、指定的样式表。 它将阻止所有其他样式表——即使它们来自 https://cdn.jsdelivr.net

您可以使用更新后的 style-src 指令更新 CSP 标头,如下所示。 当您重新加载页面时,两种违规行为都将得到解决:

服务器.js

. . .
app.use(function (req, res, next) {
  res.setHeader(
    'Content-Security-Policy-Report-Only',
    "default-src 'self'; font-src 'self'; img-src 'self'; script-src 'self'; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css; frame-src 'self';"
  );
  next();
});
. . .

允许图像源

您在页面上使用的图像来自单一来源:https://images.unsplash.com。 让我们通过 img-src 指令来允许它,如下所示:

服务器.js

. . .
app.use(function (req, res, next) {
  res.setHeader(
    'Content-Security-Policy-Report-Only',
    "default-src 'self'; font-src 'self'; img-src 'self' https://images.unsplash.com; script-src 'self'; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css; frame-src 'self'"
  );
  next();
});
. . .

允许嵌入 Youtube

您可以通过 frame-src 指令允许嵌套浏览上下文的有效源,这些资源使用 <iframe> 等元素。 如果该指令不存在,浏览器将查找 child-src 指令,该指令随后回退到 default-src 指令。

我们当前的政策限制框架嵌入到源主机。 让我们将 https://www.youtube.com 添加到允许列表中,以便 CSP 在我们执行策略后不会阻止加载 Youtube 嵌入:

frame-src 'self' https://www.youtube.com;

请注意,www 子域在这里很重要。 如果您有来自 https://youtube.com 的嵌入,它将根据此策略被阻止,除非您还将 https://youtube.com 添加到允许列表:

frame-src 'self' https://www.youtube.com https://youtube.com;

这是更新的 CSP 标头。 更改 frame-src 指令如下:

服务器.js

. . .
app.use(function (req, res, next) {
  res.setHeader(
    'Content-Security-Policy-Report-Only',
    "default-src 'self'; font-src 'self'; img-src 'self' https://images.unsplash.com; script-src 'self'; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css; frame-src 'self' https://www.youtube.com https://youtube.com;"
  );
  next();
});
. . .

允许字体文件

Google 字体样式表包含对 https://fonts.gstatic.com 中的几个字体文件的引用。 您会发现这些文件当前违反了定义的 font-src 策略(当前为 'self'),因此您需要在修改后的策略中考虑它们:

font-src 'self' https://fonts.gstatic.com;

更新 CSP 标头中的 font-src 指令,如下所示:

服务器.js

. . .
app.use(function (req, res, next) {
  res.setHeader(
    'Content-Security-Policy-Report-Only',
    "default-src 'self'; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://images.unsplash.com; script-src 'self'; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css; frame-src 'self' https://www.youtube.com https://youtube.com;"
  );
  next();
});
. . .

重新加载页面后,控制台将不再报告 Google 字体文件的违规行为。

允许 Vue.js 脚本

通过 CDN 加载的 Vue.js 脚本正在呈现页面顶部的 Hello world! 文本。 我们将允许它通过 script-src 指令在页面上执行。 如前所述,在允许 CDN 源时要具体说明这一点很重要,这样我们就不会向托管在该域上的其他可能的恶意脚本开放我们的站点。

script-src 'self' https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js;

在此示例中,您只允许该 URL 上的确切脚本在页面上执行。 如果您想在开发和生产版本之间切换,您还需要将其他脚本 URL 添加到允许列表,或者您可以允许 /dist 位置中存在的所有资源,以涵盖这两种情况一次:

script-src 'self' https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/;

这是更新后的 CSP 标头以及相关更改:

服务器.js

. . .
app.use(function (req, res, next) {
  res.setHeader(
    'Content-Security-Policy-Report-Only',
    "default-src 'self'; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://images.unsplash.com; script-src 'self' https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css; frame-src 'self' https://www.youtube.com https://youtube.com;"
  );
  next();
});
. . .

至此,我们已经成功地允许了我们页面所依赖的所有外部文件和脚本。 但是由于页面上存在内联脚本,我们还有一个 CSP 违规需要解决。 我们将在下一节探讨这个问题的一些解决方案。

第 4 步 - 处理内联源

虽然您可以使用 'unsafe-inline' 关键字批准 CSP 中的内联代码(例如 <script> 标签中的 JavaScript 代码),但不建议这样做,因为它会大大增加代码注入攻击的风险.

此示例策略允许在页面上执行任何内联脚本,但由于上述原因,这并不安全。

script-src 'self' 'unsafe-inline' https://unpkg.com/vue@3.0.2/dist/;

避免使用 unsafe-inline 的最佳方法是将内联代码移动到外部文件并以这种方式引用它。 这是一种更好的缓存、缩小和可维护性方法,它还使 CSP 将来更容易修改。

但是,如果您绝对必须使用内联代码,有两种主要方法可以将它们安全地添加到您的许可名单中。

选项 1 — 使用哈希

此方法要求您计算基于脚本本身的 SHA 哈希,然后将其添加到 script-src 指令。 在最新版本的 Chrome 中,您甚至不需要自己生成哈希,因为它已经包含在控制台的 CSP 违规错误中:

[Report Only] Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' https://unpkg.com/vue@3.0.2/dist/". Either the 'unsafe-inline' keyword, a hash ('sha256-INJfZVfoUd61ITRFLf63g+S/NJAfswGDl15oK0iXgYM='), or a nonce ('nonce-...') is required to enable inline execution.

此处突出显示的部分是您需要添加到 script-src 指令以允许执行触发违规的特定内联脚本的确切 SHA256 哈希。

复制哈希并将其添加到您的 CSP,如下所示:

script-src 'self' https://unpkg.com/vue@3.0.2/dist/ 'sha256-INJfZVfoUd61ITRFLf63g+S/NJAfswGDl15oK0iXgYM=';

这种方式的缺点是如果脚本的内容发生变化,生成的hash会不一样,就会触发违规。

选项 2 — 使用 Nonce

允许执行内联代码的第二种方法是使用 nonce。 这些是随机字符串,您可以使用它们来允许完整的代码块,而不管其内容如何。

下面是一个使用中的 nonce 值的示例:

script-src 'self' https://unpkg.com/vue@3.0.2/dist/ 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'

CSP 中的 nonce 值必须与脚本上的 nonce 属性匹配:

<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">
  // Some inline code
</script>

随机数必须是不可猜测的,并且每次加载页面时动态生成,这样攻击者就无法使用它们来执行恶意脚本。 如果你决定实现这个选项,你可以使用 crypto 包来生成一个随机数,如下所示:

const crypto = require('crypto');
let nonce = crypto.randomBytes(16).toString('base64');

我们将在本教程中选择散列方法,因为它对我们的用例更实用。

更新 CSP 标头中的 script-src 指令以包含唯一内联脚本的 SHA256 哈希,如下所示:

服务器.js

. . .
app.use(function (req, res, next) {
  res.setHeader(
    'Content-Security-Policy-Report-Only',
    "default-src 'self'; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://images.unsplash.com; script-src 'self' https://unpkg.com/vue@3.0.2/dist/ 'sha256-INJfZVfoUd61ITRFLf63g+S/NJAfswGDl15oK0iXgYM='; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css; frame-src 'self' https://www.youtube.com https://youtube.com;"
  );
  next();
});
. . .

这将删除内联脚本从控制台触发的最终 CSP 违规错误。

在下一节中,我们将监控 CSP 在生产环境中的影响。

第 5 步 - 监控违规行为

一旦你的 CSP 就位,有必要在使用后密切关注它的效果。 例如,如果您忘记在生产中允许合法来源,或者当攻击者试图利用 XSS 攻击向量(您需要立即识别并停止)。

如果没有某种形式的主动报告,就无法知道这些事件。 这就是存在 report-to 指令的原因。 它指定了浏览器应该将 JSON 格式的违规报告发布到的位置,以防它必须根据 CSP 采取行动。

要使用此指令,您需要添加一个额外的标头来指定 Reporting API 的端点:

Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"http://your_server_ip:5500/__cspreport__"}],"include_subdomains":true}

设置后,在 report-to 指令中指定组名,如下所示:

report-to csp-endpoint;

这是 server.js 文件的更新部分,其中包含更改。 请务必将 Report-To 标头中的 <your_server_ip> 占位符替换为您的实际服务器 IP 地址:

服务器.js

. . .
app.use(function (req, res, next) {
  res.setHeader(
    'Report-To',
    '{"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"https://<your_server_ip>:5500/__cspreport__"}],"include_subdomains":true}'
  );
  res.setHeader(
    'Content-Security-Policy-Report-Only',
    "default-src 'self'; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://images.unsplash.com; script-src 'self' https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/ 'sha256-INJfZVfoUd61ITRFLf63g+S/NJAfswGDl15oK0iXgYM='; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css; frame-src 'self' https://www.youtube.com https://youtube.com; report-to csp-endpoint;"
  );
  next();
});
. . .

report-to 指令旨在替换现已弃用的 report-uri 指令,但大多数浏览器还不支持它(截至 2020 年 11 月)。 因此,为了与当前浏览器兼容,同时确保与未来浏览器版本支持兼容,您应该在 CSP 中同时指定 report-urireport-to。 如果支持后者,它将忽略前者:

服务器.js

. . .
app.use(function (req, res, next) {
  res.setHeader(
    'Report-To',
    '{"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"https://your_server_ip:5500/__cspreport__"}],"include_subdomains":true}'
  );
  res.setHeader(
    'Content-Security-Policy-Report-Only',
    "default-src 'self'; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://images.unsplash.com; script-src 'self' https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/ 'sha256-INJfZVfoUd61ITRFLf63g+S/NJAfswGDl15oK0iXgYM='; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css; frame-src 'self' https://www.youtube.com https://youtube.com; report-to csp-endpoint; report-uri /__cspreport__;"
  );
  next();
});
. . .

/__cspreport__ 路由也需要存在于服务器上; 将此添加到您的文件中,如下所示:

服务器.js

. . .
app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname + '/index.html'));
});

app.post('/__cspreport__', (req, res) => {
  console.log(req.body);
});
. . .

一些浏览器将报告负载的 Content-Type 作为 application/csp-report 发送,而其他浏览器使用 application/json。 如果支持 report-to 指令,则 Content-Type 应为 application/reports+json

要考虑所有可能的 Content-Type 值,您必须在 express 服务器上设置一些配置:

服务器.js

. . .
app.use(
  bodyParser.json({
    type: ['application/json', 'application/csp-report', 'application/reports+json'],
  })
);
. . .

此时,任何 CSP 违规都将发送到 /__cspreport__ 路由并随后记录到终端。

您可以通过从不符合当前 CSP 的源中添加资源来进行尝试,或者修改 index.html 文件中的内联脚本,如下所示:

索引.html

. . .
<script>
  new Vue({
    el: '#vue',
    render(createElement) {
      return createElement('h1', 'Hello World!');
    },
  });
  console.log("Hello")
</script>
. . .

这将触发违规,因为脚本的哈希现在与您在 CSP 标头中包含的不同。

这是使用 report-uri 的浏览器的典型违规报告:

{
  'csp-report': {
    'document-uri': 'http://localhost:5500/',
    referrer: '',
    'violated-directive': 'script-src-elem',
    'effective-directive': 'script-src-elem',
    'original-policy': "default-src 'self'; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://images.unsplash.com; script-src 'self' https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/ 'sha256-INJfZVfoUd61ITRFLf63g+S/NJAfswGDl15oK0iXgYM='; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css; frame-src 'self' https://www.youtube.com https://youtube.com; report-uri /__cspreport__;",
    disposition: 'report',
    'blocked-uri': 'inline',
    'line-number': 58,
    'source-file': 'http://localhost:5500/',
    'status-code': 200,
    'script-sample': ''
  }
}

本报告的部分是:

  • document-uri:违规发生的页面。
  • referrer:页面的引用者。
  • blocked-uri:违反页面策略的资源(在本例中为 inline 脚本)。
  • line-number:内联代码开始的行号。
  • violated-directive:违反的特定指令。 (在这种情况下为 script-src-elem,它回退到 script-src。)
  • original-policy:页面的完整策略。

如果浏览器支持 report-to 指令,则有效负载应具有与以下内容类似的结构。 请注意它与 report-uri 有效载荷有何不同,同时仍携带相同的信息:

[{
    "age": 16796,
    "body": {
        "blocked-uri": "https://vimeo.com",
        "disposition": "enforce",
        "document-uri": "https://localhost:5500/",
        "effective-directive": "frame-src",
        "line-number": 58,
    'original-policy': "default-src 'self'; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://images.unsplash.com; script-src 'self' https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/ 'sha256-INJfZVfoUd61ITRFLf63g+S/NJAfswGDl15oK0iXgYM='; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css; frame-src 'self' https://www.youtube.com https://youtube.com; report-uri /__cspreport__;",
        "referrer": "",
        "script-sample": "",
        "sourceFile": "https://localhost:5500/",
        "violated-directive": "frame-src"
    },
    "type": "csp",
    "url": "https://localhost:5500/",
    "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36"
}]

注意report-to 指令仅在安全上下文中受支持,这意味着您需要使用有效的 HTTPS 证书设置您的 Express 服务器,否则您将无法测试或用它。


在本节中,我们成功地在我们的服务器上设置了 CSP 监控,以便我们可以快速检测和修复问题。 让我们继续通过在下一步中执行最终策略来完成本教程。

第 6 步 — 发布最终政策

一旦您确信您的 CSP 设置正确(理想情况下,在将其在仅报告模式下投入生产几天或几周后),您可以通过将 CSP 标头从 Content-Security-Policy-Report-Only 更改为 Content-Security-Policy

除了报告违规行为外,这还将阻止未经授权的资源在页面上执行,从而为访问者带来更安全的体验。

这是 server.js 文件的最终版本:

服务器.js

const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
const app = express();

app.use(function (req, res, next) {
  res.setHeader(
    'Report-To',
    '{"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"http://your_server_ip:5500/__cspreport__"}],"include_subdomains":true}'
  );
  res.setHeader(
    'Content-Security-Policy',
    "default-src 'self'; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://images.unsplash.com; script-src 'self' https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/ 'sha256-INJfZVfoUd61ITRFLf63g+S/NJAfswGDl15oK0iXgYM='; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css; frame-src 'self' https://www.youtube.com https://youtube.com; report-to csp-endpoint; report-uri /__cspreport__;"
  );
  next();
});

app.use(
  bodyParser.json({
    type: [
      'application/json',
      'application/csp-report',
      'application/reports+json',
    ],
  })
);
app.use(express.static(path.join(__dirname)));

app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname + '/index.html'));
});

app.post('/__cspreport__', (req, res) => {
  console.log(req.body);
});

const server = app.listen(process.env.PORT || 5500, () => {
  const { port } = server.address();
  console.log(`Server running on PORT ${port}`);
});

浏览器支持 所有浏览器 都支持CSP 标头,但Internet Explorer 除外,它使用非标准的X-Content-Security-Policy 标头代替。 如果您需要支持 IE,则必须在响应标头中发出两次 CSP。

最新版本的 CSP 规范(级别 3) 还引入了一些目前还没有得到很好支持的新指令。 示例包括 script-src-elemprefetch-src 指令。 确保在设置这些指令时使用适当的后备,以确保保护在尚未赶上的浏览器中保持活动状态。


结论

在本文中,您为 Node.js 应用程序设置了有效的内容安全策略并监控了违规行为。 如果您有一个较旧或更复杂的网站,则需要一个涵盖所有基础的更广泛的策略设置。 但是,设置一个彻底的策略是值得的,因为它使攻击者更难利用您的网站窃取用户数据。

您可以在 这个 GitHub 存储库 中找到本教程的最终代码。

如需更多与安全相关的文章,请查看我们的 安全主题页面 。 如果您想了解有关使用 Node.js 的更多信息,可以阅读我们的 如何在 Node.js 中编码系列