大家好!欢迎来到今天的“JS Subresource Integrity (SRI) 的 Content-Security-Policy 集成与自动化”讲座。
我是你们今天的导游,负责带大家一起探索如何让我们的网站更安全,更可靠。准备好了吗?Let’s roll!
第一幕:故事的开始 – 为什么我们需要 SRI 和 CSP?
想象一下,你辛辛苦苦搭建了一个完美的网站,代码写得像诗一样优美。突然有一天,你发现你的网站开始弹广告,或者更糟糕,用户的信用卡信息被盗了!原因可能就是因为你引用的第三方库被人篡改了。
这就是为什么我们需要 Subresource Integrity (SRI) 和 Content-Security-Policy (CSP)。它们就像我们网站的保安,负责检查进出我们网站的每个“人”(资源),确保它们都是好人。
- SRI (Subresource Integrity): 验证从 CDN 或其他源加载的资源是否未被篡改。简单来说,就是给每个资源加上一个“指纹”,浏览器加载时会检查这个指纹是否匹配。如果指纹不一样,说明资源被人动过手脚,浏览器就会拒绝加载。
- CSP (Content-Security-Policy): 定义浏览器可以加载哪些来源的资源。相当于给浏览器列了一个“白名单”,只有名单上的“好人”才能进入我们的网站。
第二幕:SRI – 给你的资源加上“指纹”
SRI 的核心在于生成资源的哈希值,并将其添加到 <script>
或 <link>
标签中。
1. 如何生成哈希值?
可以使用 openssl
命令,或者任何在线 SRI 哈希生成器。
-
使用
openssl
命令:openssl dgst -sha384 -binary your_script.js | openssl base64 -A
这个命令会生成
your_script.js
文件的 SHA-384 哈希值。你可以根据需要选择不同的哈希算法,例如sha256
或sha512
。注意: 推荐使用
sha384
或sha512
,因为它们更安全。 -
在线 SRI 哈希生成器: 有很多在线工具可以帮你生成 SRI 哈希值。只需要上传你的文件,或者输入 URL,就可以得到哈希值。
2. 如何将哈希值添加到 HTML 标签?
将生成的哈希值添加到 <script>
或 <link>
标签的 integrity
属性中。
<script
src="https://cdn.example.com/your_script.js"
integrity="sha384-YOUR_HASH_VALUE"
crossorigin="anonymous"
></script>
src
: 资源的 URL。integrity
: 资源的哈希值。crossorigin="anonymous"
: 告诉浏览器使用匿名模式加载资源。这是为了避免 CORS 问题,因为 SRI 会检查响应头的Access-Control-Allow-Origin
字段。如果你的 CDN 配置了正确的 CORS 头,也可以使用crossorigin="use-credentials"
。
重要提示:
crossorigin
属性是必需的,否则 SRI 无法正常工作。- 确保哈希值与资源的实际内容匹配。 否则,浏览器会拒绝加载资源。
一个完整的例子:
假设我们有一个名为 app.js
的文件,内容如下:
console.log("Hello from app.js!");
我们使用 openssl
命令生成它的 SHA-384 哈希值:
openssl dgst -sha384 -binary app.js | openssl base64 -A
假设生成的哈希值是:jZ4jH4U+E6w9o/1+2Q+l0B6jW0jH4U+E6w9o/1+2Q+l0B6jW0jH4U+E6w9o/1+2Q+l0B6jW0jH4U+E6w9o/1+2Q+l0B6jW0jH4U=
(这是一个假的例子,不要直接使用)
那么,HTML 代码应该如下所示:
<script
src="app.js"
integrity="sha384-jZ4jH4U+E6w9o/1+2Q+l0B6jW0jH4U+E6w9o/1+2Q+l0B6jW0jH4U+E6w9o/1+2Q+l0B6jW0jH4U+E6w9o/1+2Q+l0B6jW0jH4U="
crossorigin="anonymous"
></script>
现在,浏览器在加载 app.js
时,会验证它的哈希值是否与 integrity
属性中指定的哈希值匹配。如果匹配,说明资源没有被篡改,浏览器会正常加载。如果不匹配,浏览器会拒绝加载资源,并在控制台中显示错误信息。
第三幕:CSP – 给你的网站设置“门卫”
CSP 通过 HTTP 响应头或 HTML <meta>
标签来定义。
1. HTTP 响应头:
这是推荐的方式,因为它更安全。
Content-Security-Policy: policy-directive; policy-directive
2. HTML <meta>
标签:
<meta
http-equiv="Content-Security-Policy"
content="policy-directive; policy-directive"
/>
policy-directive
是 CSP 的核心,它定义了允许加载哪些来源的资源。
一些常用的 policy-directive
:
指令 | 描述 | 例子 |
---|---|---|
default-src |
定义所有资源的默认来源。 | default-src 'self' (只允许同源资源) |
script-src |
定义 JavaScript 资源的来源。 | script-src 'self' https://cdn.example.com (允许同源资源和 cdn.example.com 的资源) |
style-src |
定义 CSS 资源的来源。 | style-src 'self' 'unsafe-inline' (允许同源资源和内联样式) |
img-src |
定义图片资源的来源。 | img-src 'self' data: (允许同源资源和 data URI) |
connect-src |
定义可以进行网络请求的来源。 | connect-src 'self' wss://example.com (允许同源请求和 WebSocket 连接到 example.com ) |
font-src |
定义字体资源的来源。 | font-src 'self' https://fonts.example.com (允许同源资源和 fonts.example.com 的字体) |
media-src |
定义音视频资源的来源。 | media-src 'self' (只允许同源音视频资源) |
object-src |
定义 <object> , <embed> 和 <applet> 元素的来源。 |
object-src 'none' (不允许加载任何插件) |
frame-src |
定义 <frame> 和 <iframe> 元素的来源。 |
frame-src 'self' https://youtube.com (允许同源 frame 和来自 youtube.com 的 frame) |
base-uri |
定义可以出现在 <base> 元素中的 URL。 |
base-uri 'self' (只允许同源 URL 作为 base URI) |
form-action |
定义表单可以提交到哪些 URL。 | form-action 'self' https://secure.example.com (允许提交到同源 URL 和 secure.example.com ) |
frame-ancestors |
定义当前页面可以被嵌入到哪些来源的 frame 中。 | frame-ancestors 'none' (不允许被任何 frame 嵌入), frame-ancestors 'self' (只允许被同源 frame 嵌入) |
report-uri |
定义浏览器在违反 CSP 策略时,将报告发送到哪个 URL。 | report-uri /csp-report (将报告发送到 /csp-report 路径) |
report-to |
定义浏览器在违反 CSP 策略时,将报告发送到哪个命名终端点。这个指令和 report-uri 配合使用,可以提供更详细的报告信息,并支持浏览器发送 JSON 格式的报告。 |
report-to "default" (将报告发送到名为 "default" 的终端点) |
一些常用的来源值:
'self'
: 允许同源资源。'none'
: 不允许任何来源的资源。'unsafe-inline'
: 允许内联 JavaScript 和 CSS。(不推荐使用,因为它会降低安全性)'unsafe-eval'
: 允许使用eval()
和Function()
。(不推荐使用,因为它会降低安全性)'strict-dynamic'
: 允许通过已经信任的脚本加载的脚本执行。需要配合 nonce 或 hash 使用。'unsafe-hashes'
: 允许特定的内联事件处理程序(例如onclick
)使用哈希值。通常不推荐使用。data:
: 允许 data URI。https://example.com
: 允许来自example.com
的资源。*.example.com
: 允许来自example.com
及其所有子域的资源。
一个例子:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:
这个 CSP 策略的含义是:
default-src 'self'
: 默认情况下,只允许加载同源资源。script-src 'self' https://cdn.example.com
: 允许加载同源的 JavaScript 资源和来自cdn.example.com
的 JavaScript 资源。style-src 'self' 'unsafe-inline'
: 允许加载同源的 CSS 资源和内联样式。img-src 'self' data:
: 允许加载同源的图片资源和 data URI。
重要提示:
'unsafe-inline'
和'unsafe-eval'
会降低安全性,应该尽量避免使用。- CSP 策略应该根据你的网站的需求进行定制。 不要盲目复制别人的策略。
- 可以使用
report-uri
或report-to
指令来收集 CSP 违规报告。 这可以帮助你发现并修复潜在的安全问题。
使用 report-uri
或 report-to
的例子:
Content-Security-Policy: default-src 'self'; report-uri /csp-report; report-to "default";
// 后端处理 CSP 违规报告的示例代码 (Node.js)
app.post('/csp-report', (req, res) => {
console.log('CSP Violation Report:', req.body);
// 可以将报告保存到数据库,或者发送到监控系统
res.sendStatus(204); // 必须返回 204 No Content
});
report-to
指令需要配置一个终端点:
Content-Security-Policy: default-src 'self'; report-to "default";
Report-To: {"group":"default","max_age":31536000,"endpoints":[{"url":"/csp-endpoint"}]}
在这个例子中,Report-To
头定义了一个名为 "default" 的终端点,它会将报告发送到 /csp-endpoint
。 max_age
指定了浏览器缓存这个终端点配置的时间(单位是秒)。
CSP 的 nonce
和 hash
:
为了更安全地使用内联脚本和样式,可以使用 nonce
或 hash
。
-
nonce
(Number used Once): 是一个每次页面加载时都会变化的随机字符串。<script nonce="YOUR_RANDOM_STRING"> console.log("Hello from inline script!"); </script>
Content-Security-Policy: script-src 'nonce-YOUR_RANDOM_STRING'
-
hash
: 是内联脚本或样式的哈希值。<script> console.log("Hello from inline script!"); </script>
Content-Security-Policy: script-src 'sha256-YOUR_HASH_VALUE'
第四幕:SRI 和 CSP 的集成
SRI 和 CSP 可以一起使用,以提供更强大的安全保护。
1. 使用 SRI 确保第三方库的完整性。
2. 使用 CSP 限制可以加载资源的来源。
一个例子:
<script
src="https://cdn.example.com/your_script.js"
integrity="sha384-YOUR_HASH_VALUE"
crossorigin="anonymous"
></script>
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com
在这个例子中,我们使用 SRI 确保 your_script.js
文件的完整性,并使用 CSP 限制只能从 cdn.example.com
加载 JavaScript 资源。
第五幕:自动化 – 让你的生活更轻松
手动管理 SRI 和 CSP 策略非常繁琐,容易出错。因此,我们需要自动化工具来帮助我们。
1. 构建工具插件 (Webpack, Parcel, Rollup):
这些工具可以自动生成 SRI 哈希值,并将其添加到 HTML 标签中。它们还可以帮助你生成 CSP 策略。
- Webpack: 可以使用
webpack-subresource-integrity
插件。 - Parcel: 内置了 SRI 支持。
- Rollup: 可以使用
rollup-plugin-sri-hash
插件。
2. 后端框架 (Node.js, Python, Ruby):
这些框架可以自动生成 CSP 响应头。
- Node.js: 可以使用
helmet
中间件。 - Python: 可以使用
Flask-CSP
扩展。 - Ruby on Rails: 可以使用
secure_headers
gem。
3. CI/CD 集成:
可以将 SRI 和 CSP 集成到你的 CI/CD 流程中,以便在每次部署时自动生成和验证策略。
一个使用 Webpack 和 webpack-subresource-integrity
插件的例子:
-
安装插件:
npm install webpack-subresource-integrity --save-dev
-
配置
webpack.config.js
:const SriPlugin = require('webpack-subresource-integrity'); module.exports = { // ... plugins: [ new SriPlugin({ hashFuncNames: ['sha384'], // 可以选择不同的哈希算法 enabled: process.env.NODE_ENV === 'production', // 只在生产环境启用 }), ], };
-
构建你的项目:
webpack
Webpack 会自动生成 SRI 哈希值,并将其添加到 HTML 标签中。
一个使用 Node.js 和 helmet
中间件的例子:
-
安装
helmet
:npm install helmet --save
-
配置你的 Express 应用:
const express = require('express'); const helmet = require('helmet'); const app = express(); app.use( helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", 'https://cdn.example.com'], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", 'data:'], }, }, }) ); // ...
helmet
会自动生成 CSP 响应头,并将其添加到每个响应中。
第六幕:最佳实践
- 从宽松的 CSP 策略开始,然后逐渐收紧。 这可以避免因为 CSP 策略过于严格而导致网站无法正常工作。
- 使用 CSP 违规报告来监控你的网站。 这可以帮助你发现并修复潜在的安全问题。
- 定期更新你的第三方库。 这可以确保你使用的是最新版本,并且已经修复了已知的安全漏洞。
- 使用 SRI 来验证第三方库的完整性。 这可以防止你的网站受到恶意代码的攻击。
- 自动化你的 SRI 和 CSP 策略。 这可以减少手动操作的错误,并提高效率。
- 在所有环境中(开发、测试、生产)启用 SRI 和 CSP。 这可以确保你的网站在所有环境中都受到保护。
- 测试你的 CSP 策略。 可以使用在线 CSP 评估工具或浏览器扩展来测试你的 CSP 策略。
- 了解你的 CDN 的 CORS 配置。 确保你的 CDN 配置了正确的 CORS 头,以便 SRI 可以正常工作。
总结
SRI 和 CSP 是保护你的网站免受恶意攻击的重要工具。通过合理地使用它们,你可以大大提高你的网站的安全性。记住,安全是一个持续的过程,需要不断地学习和改进。
希望今天的讲座对你有所帮助。感谢大家的参与!
最后,送大家一句安全箴言: "Better safe than sorry!" (小心驶得万年船!)