4、内容安全策略(CSP):CSP策略配置、常见CSP指令、绕过CSP的攻击手法
聊到 Electron 安全,CSP 绝对是个绕不开的话题。我个人习惯把它叫做「最后一道防线」—— 就算你的 XSS 防护没做好,CSP 还能帮你兜个底。说白了,CSP 就是告诉浏览器:「哪些资源可以加载,哪些脚本可以执行,其他的统统给我拦下来。」
但现实是,很多 Electron 应用要么没配 CSP,要么配了个寂寞。我在审计项目时见过不少「*」满天飞的策略,那基本等于没穿防弹衣上战场。今天咱们就好好聊聊,CSP 到底该怎么配,以及攻击者是怎么绕过它的。
4.1 CSP 策略配置基础
CSP 的配置方式有两种:HTTP 响应头 和 HTML 中的 <meta> 标签。在 Electron 里,我更推荐用响应头的方式,因为 <meta> 在某些场景下会有兼容性问题。
先看一个最基础的配置:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'
这个策略的意思是:所有资源默认只允许从同源加载,脚本和样式也一样。嗯,这算是个起步配置,但实际项目中远远不够。
在 Electron 的主进程中,你可以通过 session.defaultSession.webRequest.onHeadersReceived 来注入 CSP:
const { session } = require('electron')
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': [
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
]
}
})
})
webPreferences 中设置 sandbox: true,这样 CSP 的效果会更好。沙箱和 CSP 是黄金搭档,一个管权限,一个管资源。
4.2 常见 CSP 指令详解
CSP 指令其实不少,但日常开发中常用的就那么几个。我整理了一张表,方便你对照着看:
| 指令 | 作用 | 常用值示例 |
|---|---|---|
default-src |
所有资源的默认策略 | 'self' |
script-src |
控制 JavaScript 加载来源 | 'self' 'nonce-abc123' |
style-src |
控制 CSS 加载来源 | 'self' 'unsafe-inline' |
img-src |
控制图片加载来源 | 'self' https://cdn.example.com |
connect-src |
控制 AJAX、WebSocket 等连接 | 'self' https://api.example.com |
frame-src |
控制 iframe 嵌入来源 | 'self' |
object-src |
控制 <object>、<embed> 等插件 |
'none' |
base-uri |
控制 <base> 标签的 URL |
'self' |
这里有个坑,我必须要提醒你:'unsafe-inline' 这个值,能不用就别用。它允许内联脚本执行,这恰恰是 XSS 攻击最喜欢利用的点。你想想看,如果攻击者注入了一段 <script>alert(1)</script>,而你的策略允许了 'unsafe-inline',那 CSP 就形同虚设了。
script-src 'self' 'unsafe-inline' 'unsafe-eval'。结果安全审计一查,全是高危漏洞。说白了,'unsafe-eval' 允许 eval()、setTimeout(string) 等动态执行,攻击者可以轻松绕过。
4.3 绕过 CSP 的攻击手法
配了 CSP 就万事大吉了吗?当然不是。攻击者有的是办法绕过它。我见过不少「看似安全」的 CSP 策略,实际上漏洞百出。
4.3.1 JSONP 绕过
如果你的策略里允许了某个第三方域名的脚本,而那个域名恰好提供了 JSONP 接口,攻击者就能利用它来执行任意代码。
举个例子:
Content-Security-Policy: script-src 'self' https://cdn.example.com
如果 cdn.example.com 上有个 JSONP 接口:
https://cdn.example.com/jsonp?callback=alert(1)
攻击者就能这样注入:
<script src="https://cdn.example.com/jsonp?callback=alert(document.cookie)"></script>
嗯,这就是为什么我建议你尽量少用第三方 CDN 的脚本。如果非要用,一定要确认对方没有 JSONP 接口,或者用 SRI(Subresource Integrity)来锁定文件内容。
4.3.2 基于 <base> 标签的绕过
这个手法比较冷门,但确实有效。如果 CSP 没有限制 base-uri,攻击者可以插入一个 <base> 标签,改变页面的基础 URL。这样一来,所有相对路径的脚本都会加载攻击者控制的资源。
<base href="https://evil.com/">
<script src="/js/app.js"></script>
上面的 /js/app.js 实际上会加载 https://evil.com/js/app.js。所以,记得在 CSP 里加上 base-uri 'self'。
4.3.3 利用 script-src 中的 'strict-dynamic'
'strict-dynamic' 是 CSP Level 3 引入的机制,它允许由合法脚本动态加载的其他脚本也获得信任。听起来很方便,对吧?但这也带来了风险。
假设你的策略是:
script-src 'strict-dynamic' 'nonce-abc123'
攻击者如果找到了一个带有正确 nonce 的脚本,而这个脚本又动态创建了 <script> 标签,那么攻击者就能通过这个脚本加载任意代码。
'strict-dynamic' 时,一定要确保你的脚本库本身是安全的。最好配合 require-trusted-types-for 'script' 一起使用,这样能进一步限制动态脚本的创建。
4.3.4 通过 iframe 绕过
如果 CSP 允许了 frame-src 或 child-src,攻击者可以嵌入一个恶意页面,然后通过 postMessage 或 window.parent 来与主页面通信。虽然不能直接执行脚本,但可以窃取数据。
我记得有个案例,攻击者利用 frame-src * 嵌入了自己的钓鱼页面,然后通过 postMessage 骗取了用户的登录凭证。所以,frame-src 一定要限制死,最好只允许 'self'。
4.4 实战中的 CSP 配置建议
说了这么多,到底该怎么配?我给出一个相对安全的模板,你可以根据自己的项目调整:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{random}' 'strict-dynamic';
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
connect-src 'self' https://api.yourdomain.com;
frame-src 'none';
object-src 'none';
base-uri 'self';
form-action 'self';
require-trusted-types-for 'script'
Content-Security-Policy-Report-Only 来测试策略,它不会真的拦截资源,只会报告违规情况。等确认无误了,再换成正式的 Content-Security-Policy。
最后说一句,CSP 不是银弹。它需要和其他安全措施配合使用,比如输入验证、输出编码、沙箱隔离等。你想想看,如果攻击者已经拿到了你的服务器权限,CSP 再强也拦不住他直接改策略。所以,安全是一个体系,别指望一招鲜吃遍天。
好了,这一章的内容就到这里。下一章咱们聊聊 Electron 中的 Node.js 集成风险,那可是个重头戏。