什么是 Content Security Policy (CSP)?它在 JavaScript 安全中有什么作用?

各位听众,早上好! 今天咱们聊聊一个听起来有点高冷,但实际上非常实用的东西:Content Security Policy,简称CSP。你可以把它想象成你家大门的保安,专门负责检查进出你家(网页)的人(资源)是不是可信的。

一、 CSP:网页安全的“白名单”卫士

在没有CSP的日子里,网页就像不设防的城市,谁都能随便进出。黑客们利用XSS(跨站脚本攻击)漏洞,往你的网页里注入恶意脚本,偷取用户信息,篡改页面内容,简直防不胜防。

CSP的出现,改变了这一切。它本质上是一个HTTP响应头,告诉浏览器哪些来源的资源是允许加载的。也就是说,你可以在服务器端设置一个规则,比如只允许加载来自你自己的服务器的脚本,拒绝所有其他来源的脚本。这样,即使黑客成功注入了恶意脚本,浏览器也会拒绝执行,从而保护你的网页安全。

CSP的核心思想是“白名单”。你明确告诉浏览器哪些资源是可信的,浏览器只信任这些资源,其他一概拒绝。这就像给浏览器装上了一双火眼金睛,能识别出哪些是妖魔鬼怪。

二、 CSP语法:像写菜谱一样简单

CSP的语法其实很简单,就像写菜谱一样,告诉浏览器你想允许哪些资源,不允许哪些资源。

最基本的CSP策略长这样:

Content-Security-Policy: default-src 'self';

这句话的意思是:

  • Content-Security-Policy: 告诉浏览器这是一个CSP策略。
  • default-src: 指定所有类型资源的默认来源。
  • 'self': 表示只允许来自当前域名(协议和端口)的资源。

是不是很简单? 咱们再来几个例子:

Content-Security-Policy: script-src 'self' https://example.com;

这个策略允许加载来自当前域名和https://example.com的脚本。

Content-Security-Policy: img-src 'self' data:;

这个策略允许加载来自当前域名的图片,以及使用data: URI(可以将图片直接嵌入到HTML中)的图片。

Content-Security-Policy: style-src 'self' 'unsafe-inline';

这个策略允许加载来自当前域名的样式表,以及允许内联样式(直接写在HTML标签里的样式)。 注意 'unsafe-inline' 这个关键词,它表示允许内联样式,但同时也降低了安全性, 除非必要, 尽量避免使用。

CSP策略可以同时指定多种资源类型的来源,用分号分隔:

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; img-src 'self' data:;

这个策略规定了:

  • 所有资源的默认来源是当前域名。
  • 脚本的来源可以是当前域名或https://cdn.example.com
  • 图片的来源可以是当前域名或data: URI。

三、 CSP指令:你的资源“通行证”

CSP有很多指令,每条指令都控制着一种资源类型的加载。 常见的指令如下表所示:

指令 描述
default-src 所有未明确指定的资源类型的默认来源。
script-src 脚本的来源。
style-src 样式表的来源。
img-src 图片的来源。
font-src 字体文件的来源。
connect-src 允许使用XMLHttpRequestWebSocketEventSource等接口连接的URL。
media-src 音视频文件的来源。
object-src <object><embed><applet>等标签的来源。
frame-src <iframe>标签的来源。
base-uri 允许<base> 标签设置的URL。
form-action 允许提交表单的URL。
upgrade-insecure-requests 指示浏览器将页面上所有不安全的HTTP请求升级为HTTPS请求。
block-all-mixed-content 阻止页面加载任何通过HTTP加载的资源,当页面是通过HTTPS加载的时候。
plugin-types 限制可以加载的插件类型,比如application/x-shockwave-flash
sandbox <iframe>标签启用沙箱模式,限制其权限。

四、 CSP来源值:资源的“身份证”

每个指令后面都可以跟一个或多个来源值,用来指定允许加载的资源来源。 常见的来源值如下表所示:

来源值 描述
'self' 当前域名,包括协议和端口。
'none' 不允许加载任何资源。
https://example.com 精确匹配的URL。
*.example.com 通配符,匹配example.com的所有子域名。
'unsafe-inline' 允许内联脚本和样式,不推荐使用,除非必要。
'unsafe-eval' 允许使用eval()函数,不推荐使用,除非必要。
'unsafe-hashes' 允许特定的内联事件处理程序,通过哈希值指定,这比'unsafe-inline'更安全。
'nonce-{随机字符串}' 允许带有特定nonce属性的脚本和样式,每次页面加载时nonce值都应该不同。
'sha256-{哈希值}' 允许哈希值匹配的脚本和样式。
data: 允许使用data: URI。
mediastream: 允许使用mediastream: URI,用于访问用户的摄像头和麦克风。
blob: 允许使用 blob: URI,表示从 Blob 对象创建的 URL。
filesystem: 允许使用 filesystem: URI,用于访问文件系统 API 创建的文件。

五、 如何设置CSP:服务器说了算

设置CSP的方式有两种:

  1. HTTP响应头: 这是最常用的方式,在服务器端设置Content-Security-Policy响应头。 比如,在Node.js中:
const http = require('http');

const server = http.createServer((req, res) => {
  res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self' https://cdn.example.com;");
  res.end('Hello, CSP!');
});

server.listen(3000, () => {
  console.log('Server listening on port 3000');
});

在Apache服务器中,可以在.htaccess文件中添加:

Header set Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com;"
  1. HTML <meta> 标签: 这种方式不太常用,因为它只能设置部分CSP指令,而且优先级较低。
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://cdn.example.com;">

六、 CSP报告:网页安全的“监控器”

CSP不仅能阻止恶意脚本,还能告诉你哪些资源被阻止了。 这要归功于report-urireport-to指令。

report-uri指令指定一个URL,浏览器会将违反CSP策略的报告发送到这个URL。

Content-Security-Policy: default-src 'self'; report-uri /csp-report;

report-to指令是report-uri的升级版,它允许你更灵活地配置报告的发送方式。 你需要先定义一个报告组,然后在CSP策略中使用report-to指令。

Content-Security-Policy: default-src 'self'; report-to csp-endpoint;
Content-Security-Policy-Report-Only: default-src 'self'; report-to csp-endpoint;

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

需要注意的是,Report-To头需要单独设置。

浏览器发送的报告通常是JSON格式,包含以下信息:

  • document-uri: 发生违规的文档的URL。
  • referrer: 引用文档的URL。
  • violated-directive: 导致违规的CSP指令。
  • effective-directive: 实际生效的CSP指令。
  • original-policy: 原始的CSP策略。
  • blocked-uri: 被阻止的资源的URL。
  • status-code: 被阻止资源的HTTP状态码。

你可以在服务器端接收这些报告,并进行分析和处理。 这就像给你的网页装上了一个监控器,随时监控安全状况。

七、 CSP部署:步步为营,稳扎稳打

部署CSP是一个渐进的过程,不能一蹴而就。 否则,可能会导致你的网页无法正常工作。

  1. Report-Only模式: 首先,使用Content-Security-Policy-Report-Only响应头,将CSP设置为Report-Only模式。 在这种模式下,CSP策略不会阻止任何资源,只会将违规报告发送到指定的URL。 你可以根据这些报告,逐步调整你的CSP策略。
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report;
  1. 逐步收紧策略: 根据Report-Only模式下的报告,逐步收紧你的CSP策略。 先从最宽松的策略开始,比如只允许加载来自当前域名的资源。 然后,逐步添加其他可信的来源。

  2. 监控和调整: 在部署CSP之后,要持续监控违规报告,并根据实际情况调整你的策略。 有时候,你可能会发现一些意想不到的资源被阻止了,需要及时添加白名单。

八、 CSP的局限性:并非万能药

CSP虽然能有效防御XSS攻击,但它并不是万能药。 它无法防御所有类型的攻击,比如:

  • 服务器端漏洞: CSP只能防御客户端的XSS攻击,无法防御服务器端的漏洞。
  • 点击劫持: CSP无法防御点击劫持攻击。
  • CSRF(跨站请求伪造): CSP无法防御CSRF攻击。

因此,在保护你的网页安全时,不能只依赖CSP,还需要采取其他安全措施。

九、 CSP最佳实践:让你的网页更安全

  • 使用HTTPS: HTTPS是安全的基础,所有的CSP策略都应该在HTTPS环境下部署。
  • 尽可能使用'strict-dynamic' 'strict-dynamic'指令允许通过信任的脚本动态加载的其他脚本也被信任。 这可以简化CSP策略,并提高性能。 但是,使用'strict-dynamic'需要谨慎,确保你的信任的脚本不会被篡改。
  • 避免使用'unsafe-inline''unsafe-eval' 除非必要,尽量避免使用这两个关键词,因为它们会降低安全性。
  • 使用noncehash 对于内联脚本和样式,可以使用noncehash来提高安全性。
  • 定期审查和更新CSP策略: 随着你的网页的演变,你的CSP策略也需要定期审查和更新。

十、 CSP示例:代码说话

假设你有一个网页,需要加载以下资源:

  • 来自当前域名的HTML、CSS和JavaScript文件。
  • 来自https://cdn.example.com的JavaScript库。
  • 来自https://fonts.googleapis.com的字体文件。
  • 来自https://images.example.com的图片。

你的CSP策略可以这样写:

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; font-src https://fonts.googleapis.com; img-src https://images.example.com data:;

这个策略规定了:

  • 所有资源的默认来源是当前域名。
  • 脚本的来源可以是当前域名或https://cdn.example.com
  • 样式表的来源是当前域名,并且允许内联样式。
  • 字体文件的来源是https://fonts.googleapis.com
  • 图片的来源是https://images.example.com,并且允许data: URI。

如果你的网页使用了内联脚本,你可以使用nonce来提高安全性:

<script nonce="r4nd0m">
  console.log('Hello, CSP!');
</script>

你的CSP策略需要包含'nonce-r4nd0m'

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0m';

每次页面加载时,nonce的值都应该不同。

十一、 总结:CSP,网页安全的“守护神”

CSP是一种强大的安全机制,可以有效防御XSS攻击。 它可以让你明确指定哪些来源的资源是允许加载的,从而保护你的网页安全。 虽然它不是万能药,但它是网页安全的重要组成部分。 掌握CSP,就像给你的网页装上了一个“守护神”,让你的用户可以放心地浏览你的网页。

希望今天的讲座对大家有所帮助! 祝大家编程愉快!

发表回复

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