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 就形同虚设了。

避坑指南: 我曾经在一个 Electron 项目中看到团队为了省事,直接配了 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 集成风险,那可是个重头戏。