JS `CSP` `report-uri` / `report-to` 端点接收与分析违规报告

各位观众,咳咳,老规矩,先测试一下麦克风… 喂喂喂,听得到吗?好嘞!今天咱们来聊聊一个听起来有点高大上,但其实挺有意思的话题:CSP (Content Security Policy) 的 report-urireport-to 端点,以及如何接收和分析违规报告。

说白了,CSP 就是一个安全策略,用来告诉浏览器哪些资源(比如脚本、样式、图片)可以加载,哪些不能加载。如果浏览器发现有东西违背了这个策略,就会生成一个违规报告,然后发给咱们的服务器。而 report-urireport-to 就是用来指定报告发到哪里的。

那么,为什么我们要关注这些报告呢?因为它们能帮我们:

  • 发现潜在的安全漏洞: 如果有人试图注入恶意脚本,CSP 会阻止它,并告诉我们。
  • 调试 CSP 配置: 配置 CSP 是一件很烦人的事情,很容易出错。违规报告可以帮助我们找到错误配置。
  • 了解用户体验: 如果 CSP 阻止了某些资源加载,可能会影响用户体验。违规报告可以帮助我们了解这些影响。

好了,废话不多说,咱们直接上干货。

1. report-uri:老朋友,但有点过时

report-uri 是 CSP 规范的早期版本中使用的指令。它允许你指定一个或多个 URL,浏览器会将违规报告以 JSON 格式 POST 到这些 URL。

怎么用呢?

在你的 HTTP 响应头中添加 Content-Security-Policy 头部,并包含 report-uri 指令:

Content-Security-Policy: default-src 'self'; script-src 'self' https://example.com; report-uri /csp-report-endpoint;

这里,report-uri /csp-report-endpoint 告诉浏览器,如果发生违规,就把报告发到 /csp-report-endpoint 这个 URL。

服务器端代码 (Node.js + Express):

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

const app = express();
const port = 3000;

app.use(bodyParser.json({ type: 'application/csp-report' })); // 重要:指定 Content-Type

app.post('/csp-report-endpoint', (req, res) => {
  console.log('CSP Violation Report:', req.body);
  // TODO:  存储、分析报告
  res.status(204).end(); // 返回 204 No Content,表示成功接收
});

app.get('/', (req, res) => {
  res.send(`
    <!DOCTYPE html>
    <html>
    <head>
      <title>CSP Test</title>
      <script>
        // 故意违反 CSP
        eval("alert('Hello from inline script!')");
      </script>
    </head>
    <body>
      <h1>CSP Test Page</h1>
      <img src="https://insecurewebsite.com/image.jpg">
    </body>
    </html>
  `);
});

app.listen(port, () => {
  console.log(`Server listening at http://localhost:${port}`);
});

重要事项:

  • Content-Type: body-parser.json({ type: 'application/csp-report' }) 这行代码至关重要! 浏览器发送的 CSP 报告的 Content-Typeapplication/csp-report。 如果你不指定这个类型,body-parser 可能无法正确解析 JSON 数据。
  • 状态码: 必须返回 204 No Content200 OK 等成功状态码,告诉浏览器你已经成功接收了报告。否则,浏览器可能会重试发送报告。
  • 安全性: /csp-report-endpoint 这个 URL 应该只接受 POST 请求。 你应该对这个端点进行保护,防止恶意攻击者发送大量的垃圾报告。
  • 数据格式: req.body 包含一个 JSON 对象,包含了违规报告的详细信息。 稍后我们将详细讨论报告的格式。

优点:

  • 简单易用。

缺点:

  • 过时: report-uri 已经被标记为过时,推荐使用 report-to
  • 只支持 POST 请求: 只能通过 POST 请求发送报告。
  • 缺乏灵活性: 配置选项较少。

2. report-to:新秀,更灵活

report-to 是 CSP Level 3 规范引入的指令,它提供了一种更灵活、更强大的方式来配置违规报告。 它允许你定义一个或多个 "reporting group",每个 group 都有自己的配置,包括报告发送的 URL、报告的优先级、以及是否缓存报告。

怎么用呢?

首先,你需要定义一个 "reporting group"。 这可以通过 Report-To HTTP 响应头来实现。

Report-To: {
  "group": "csp-endpoint",
  "max_age": 31536000,
  "endpoints": [
    {
      "url": "/csp-report-endpoint"
    }
  ],
  "include_subdomains": true
}

这个 Report-To 头部定义了一个名为 csp-endpoint 的 reporting group。

  • group: 组的名称,在 CSP 指令中引用。
  • max_age: 浏览器缓存这个组配置的时间 (秒)。
  • endpoints: 报告发送到的 URL 列表。 可以配置多个 endpoint,浏览器会根据优先级选择一个。
  • include_subdomains: 是否包含子域名。

然后,在你的 CSP 策略中使用 report-to 指令,引用这个 reporting group:

Content-Security-Policy: default-src 'self'; script-src 'self' https://example.com; report-to csp-endpoint;

这里,report-to csp-endpoint 告诉浏览器,如果发生违规,就把报告发送到 csp-endpoint 这个 reporting group 中定义的 URL。

服务器端代码 (Node.js + Express):

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

const app = express();
const port = 3000;

app.use(bodyParser.json({ type: 'application/csp-report' })); // 重要:指定 Content-Type

app.post('/csp-report-endpoint', (req, res) => {
  console.log('CSP Violation Report:', req.body);
  // TODO:  存储、分析报告
  res.status(204).end(); // 返回 204 No Content,表示成功接收
});

app.get('/', (req, res) => {
  res.setHeader('Report-To', JSON.stringify({
    "group": "csp-endpoint",
    "max_age": 31536000,
    "endpoints": [
      {
        "url": "/csp-report-endpoint"
      }
    ],
    "include_subdomains": true
  }));
  res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self' https://example.com; report-to csp-endpoint;");

  res.send(`
    <!DOCTYPE html>
    <html>
    <head>
      <title>CSP Test</title>
      <script>
        // 故意违反 CSP
        eval("alert('Hello from inline script!')");
      </script>
    </head>
    <body>
      <h1>CSP Test Page</h1>
      <img src="https://insecurewebsite.com/image.jpg">
    </body>
    </html>
  `);
});

app.listen(port, () => {
  console.log(`Server listening at http://localhost:${port}`);
});

重要事项:

  • Report-To 头部: 必须在 HTTP 响应头中发送 Report-To 头部,定义 reporting group。
  • JSON 字符串化: Report-To 头部的值必须是 JSON 字符串。 使用 JSON.stringify() 进行转换。
  • CSP 策略: 在 CSP 策略中使用 report-to 指令,引用 reporting group 的名称。
  • 其他注意事项:report-uri 相同,Content-Type、状态码、安全性等问题也需要考虑。

优点:

  • 更灵活: 可以定义多个 reporting group,每个 group 都有自己的配置。
  • 支持优先级: 可以配置多个 endpoint,浏览器会根据优先级选择一个。
  • 支持缓存: 浏览器会缓存 reporting group 的配置,减少网络请求。
  • 是未来趋势: report-to 是 CSP 的推荐方式。

缺点:

  • 配置更复杂: 需要同时配置 Report-To 头部和 CSP 策略。
  • 兼容性问题: 某些旧版本的浏览器可能不支持 report-to

3. 违规报告的格式

无论是 report-uri 还是 report-to,浏览器发送的违规报告的格式都是一样的,是一个 JSON 对象。

{
  "csp-report": {
    "document-uri": "http://localhost:3000/",
    "referrer": "",
    "violated-directive": "script-src",
    "effective-directive": "script-src",
    "original-policy": "default-src 'self'; script-src 'self' https://example.com; report-to csp-endpoint;",
    "disposition": "enforce",
    "blocked-uri": "unsafe-inline",
    "status-code": 200,
    "script-sample": "alert('Hello from inline script!')"
  }
}

字段解释:

字段名 描述
document-uri 发生违规的页面的 URL。
referrer 导致页面加载的引用页面的 URL。
violated-directive 导致违规的 CSP 指令的名称。
effective-directive 实际生效的 CSP 指令的名称。在某些情况下,violated-directive 可能是一个更通用的指令,而 effective-directive 是更具体的指令。
original-policy 完整的 CSP 策略。
disposition 策略的处置方式,可以是 "enforce" (强制执行) 或 "report" (仅报告)。
blocked-uri 被阻止的资源的 URL。 如果违规不是由 URL 引起的,则可能是 "inline""eval""wasm"
status-code HTTP 状态码,与被阻止的资源相关。
script-sample 如果违规是由脚本引起的,则包含脚本的前 40 个字符。

4. 如何处理违规报告

接收到违规报告后,你需要做以下事情:

  1. 存储报告: 将报告存储到数据库或日志文件中,以便后续分析。
  2. 分析报告: 分析报告,找出违规的原因。 可以根据 violated-directiveblocked-uri 等字段进行过滤和排序。
  3. 修复问题: 根据分析结果,修复 CSP 配置或代码中的问题。
  4. 监控报告: 定期监控违规报告,及时发现新的问题。

示例代码 (Node.js + Express):

const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs'); // 用于文件存储

const app = express();
const port = 3000;

app.use(bodyParser.json({ type: 'application/csp-report' }));

app.post('/csp-report-endpoint', (req, res) => {
  console.log('CSP Violation Report:', req.body);

  // 1. 存储报告到文件
  const report = JSON.stringify(req.body, null, 2); // 格式化 JSON
  fs.appendFile('csp-reports.log', report + 'n', (err) => {
    if (err) {
      console.error('Failed to write to log file:', err);
    }
  });

  // TODO:  2. 分析报告 (例如,使用正则表达式或专门的 CSP 分析库)
  // TODO:  3. 修复问题
  // TODO:  4. 监控报告

  res.status(204).end();
});

// ... (其他代码与之前相同)

一些有用的工具和资源:

5. 最佳实践

  • 从宽松的策略开始: 不要一开始就使用非常严格的 CSP 策略,否则可能会导致很多问题。 从一个宽松的策略开始,逐步增加限制。
  • 使用 report-urireport-to 一定要配置违规报告端点,以便及时发现问题。
  • 监控违规报告: 定期监控违规报告,及时发现新的问题。
  • 使用 CSP Evaluator: 使用 CSP Evaluator 评估你的 CSP 策略,确保它是有效的。
  • 考虑兼容性: report-to 的兼容性不如 report-uri,需要根据你的用户群体选择合适的方案。
  • 在开发环境中测试: 在生产环境中使用 CSP 之前,一定要在开发环境中进行充分的测试。

总结

CSP 是一个强大的安全工具,可以帮助你保护你的网站免受各种攻击。 report-urireport-to 是 CSP 的重要组成部分,可以帮助你发现和修复 CSP 配置中的问题。 虽然配置 CSP 可能会有些复杂,但它是值得的。

特性 report-uri report-to
规范 CSP Level 1/2 CSP Level 3
推荐程度 已过时,不推荐 推荐
配置 直接在 CSP 头部指定 URL 需要配置 Report-To 头部和 CSP 头部
灵活性 较低 较高,支持多个 endpoint 和优先级
兼容性 较好 较差,旧版本浏览器可能不支持
报告方式 POST 请求 POST 请求
缓存 不支持 支持,浏览器会缓存 Report-To 头部的内容

希望今天的讲解对大家有所帮助。 如果有什么问题,欢迎随时提问。 下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注