本节摘要:内容安全策略通过声明"哪些来源的哪些资源可以加载执行",从源头收窄 XSS 的利用空间,是 Electron 安全基线之上的第二道锁。本节讲 CSP 指令的阅读方法、inline script 为什么是首要清理对象、开发与生产环境的策略差异、以及混合形态应用的落地策略,并用一次"从全放开到收紧"的改造案例展示完整过程。
CSP 的本质是一份写给浏览器的准入清单:脚本只能来自这些地址、样式只能来自那些地址、连接只能打向这些域名。它防的不是"漏洞被注入",而是"注入之后能不能跑起来"——即便攻击者把脚本塞进了你的页面,清单里没有他的服务器,脚本就被浏览器拒绝执行。对 Electron 而言,这是把 4.1 节基线(能力层)与内容层连接起来的关键一环。
Electron 里落地 CSP 的方式是在页面响应头或 HTML 元信息里声明策略(本地加载的页面用后者)。先学会读策略:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://api.example.com;
逐行翻译:默认一切资源只许同源;脚本只许同源;样式允许同源加内联(样式内联的风险远低于脚本内联,实践中常放行);图片允许同源加 data 协议(小图内嵌的常见需求);网络连接只许同源加业务后端。这份"五指令起步集"能覆盖大多数纯本地应用。
策略里最值得较真的是 script-src。注意上面起步集里它没有 'unsafe-inline'——这是刻意的:允许内联脚本等于给注入攻击开了正门,策略里其他所有限制都会被这一条打穿。清理内联脚本是接入 CSP 的第一场硬仗:
<!-- 违规写法:内联脚本,收紧策略后直接不执行 --> <button onclick="doExport()">导出</button> <script> function doExport() { /* ... */ } </script> <!-- 合规写法:行为绑定搬进外部脚本文件 --> <button id="btn-export">导出</button> <!-- 外部脚本(同源)里: document.getElementById('btn-export').addEventListener('click', doExport); -->
前端框架用户在这方面有先天优势:现代框架的构建产物默认就是外部脚本加事件委托,基本没有内联残留;需要警惕的反而是第三方统计组件、可视化库的"复制粘贴式接入代码",它们经常自带内联脚本,接入时要过一道打包器改造。
**开发与生产必须两套策略。**开发期热重载、开发服务器、source map、Vue 或 React 的调试工具,都依赖宽松的来源;生产期应收紧到最小集。策略文件按环境分离,生产构建里绝不混入开发放宽项——这本身也该是 4.4 节自查表的一行。
加载远程内容的应用(4.1 节加了导航管制的那类),CSP 的写法思路要变:不再围着一个域名画圈,而是按资源类型分级放行——业务脚本严格限定,静态资源适度放宽,接口域名白名单化。同时要把 Electron 特有的两个来源纳入考虑:file 协议(本地资源)与自定义协议(7.1 节会讲到的应用协议,若使用则需在 CSP 里放行)。
# 混合形态应用的生产策略示意 default-src 'none'; # 白纸开始:一律拒绝 script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline' https://cdn.example.com; img-src 'self' data: https://cdn.example.com; connect-src https://api.example.com wss://api.example.com; font-src 'self' data:;
从 default-src 'none' 起步逐项放行,比从全放开始步逐项收紧安全得多——前者漏写一项就暴露一个问题(立刻发现),后者漏收一项就留一个洞(很难发现)。这是安全配置的通用心法:默认拒绝,按需开口。
背景:一个内部管理工具的桌面版,历史上为了图省事用着 script-src 'unsafe-inline' 'unsafe-eval' 加通配域名——等于没装 CSP。安全整顿要求两周内收紧。
操作:分四步走。第一步盘点——用 CSP 报告模式(只上报不拦截)跑一周,收集真实页面会请求哪些资源来源,得到一份"实际使用清单";第二步清理——把页面里的内联脚本与内联事件全部外置,第三方组件改造为打包接入;第三步收紧——按实际清单写最小策略,先在测试环境拦截模式跑三天,修掉误伤;第四步上线并保留报告监控,误伤新增上报时按流程评估是否放行。
结果:最终策略里 script-src 只剩 self 与一个 CDN 域,eval 彻底移除。改造期间发现两个"没人知道为什么还在加载"的第三方脚本被顺带清除,页面还快了一点。
解读:报告模式先行是这次改造能平稳落地的关键——不摸清现状就收紧,要么误伤业务引发抵触,要么被迫放行过多形同虚设。变式:如果应用必须保留动态求值能力(比如教学类产品让用户输入表达式),正确做法是把求值放进 Web Worker 或独立沙箱 iframe,用 postMessage 通信,而不是在 CSP 上开 unsafe-eval 的口子。
内容来源管住了,下一节直面最凶险的攻击类型:远程代码执行与 XSS,拆解一条完整的攻击链。