CSP Nonce 落地:从报告模式到可维护的脚本边界

CSP Nonce 落地:从报告模式到可维护的脚本边界原创封面

CSP 的目标是限制脚本执行来源

内容安全策略不能修复把用户输入拼进 HTML 的漏洞,但能显著限制注入内容执行脚本、加载插件或连接任意端点。设计前盘点页面中的外部脚本、内联脚本、事件属性、动态代码生成、Worker、样式、图片和连接目标。若策略一开始就包含 script-src * 与 unsafe-inline,浏览器虽然收到 CSP 头,关键执行边界仍然开放。策略应从业务真正需要的能力推导,不从扫描工具“有一个头即可”推导。

script-src 以 self 和每响应 nonce 为核心,明确 object-src none、base-uri self 或 none,并按应用需求设置 frame-ancestors、connect-src 等指令。default-src 可以作为保守回退,但每类资源的实际行为要在浏览器验证。CSP 是响应级契约,错误页、重定向后的 HTML、预览页面和后台都要覆盖;只在首页设置会留下更敏感的管理界面没有保护。

策略还应配合 Trusted Types 等浏览器能力评估 DOM 注入汇点,但不能在未覆盖目标浏览器时把它当成唯一防线。先盘点 innerHTML、insertAdjacentHTML 和脚本 URL,封装少量经过审查的创建策略,调用点仍执行上下文编码与清洗。监控策略违规只记录汇点和 release,不采集用户正文;逐步减少例外后再强制,避免一个宽泛 default policy 恢复任意 HTML 注入。

Nonce 必须逐响应不可预测并只给受控脚本

服务端为每个 HTML 响应使用密码学安全随机源生成足够熵的 nonce,把编码后的值同时放入 CSP 响应头和确实需要执行的 script 标签。nonce 不是长期配置、构建哈希或用户会话 id,不能跨响应复用。模板只从服务器上下文读取 nonce,用户内容永远不能控制 nonce 属性;若攻击者能把自己的 script 标签注入并获得当前 nonce,策略就无法阻止它。

Nonce 只授权该标签内脚本执行,不代表脚本内容可信。受控脚本仍避免把不可信字符串传给 innerHTML、eval、Function 或脚本 URL。JSON 初始化数据使用安全序列化并处理结束标签,最好放在非执行类型标签或经过框架安全机制传递。不要把 nonce 暴露到普通日志和分析事件;浏览器 DOM 中本就能被同页脚本观察,因此它不是秘密凭据,但无理由扩大传播会增加误用。

浏览器支持矩阵与策略版本一同保存,便于解释旧客户端为何走不同回退,也防止策略升级在未支持 nonce 的环境里形成整页脚本故障。

SSR、缓存和流式响应共享同一个 nonce 上下文

SSR 框架可能生成引导脚本、模块预加载和错误处理脚本,所有执行标签都要获得当前响应 nonce,CSP 头也必须在响应提交前确定。若 HTML 开始流式发送后才发现需要修改策略,已经无法可靠补救。实现应在请求入口创建 nonce 并通过受控服务端上下文传到 head 渲染,不使用全局变量;并发请求若共享变量,会把头与标签配成不同值。

公共 HTML 缓存是常见陷阱:缓存页面包含旧 nonce,而边缘层为新请求生成新 CSP 头,页面全部脚本会被阻止;若头和页面整体缓存,则 nonce 被大量响应复用。可以避免缓存带 nonce 的动态 HTML,或让同一可信边缘层在每次响应一致地重写头与全部标签。缓存键加入 nonce 会彻底失去共享价值,通常不是好方案。上线必须分别验证命中、未命中和陈旧缓存路径。

清理内联事件和动态执行依赖

onclick、javascript: URL 和模板拼接的 script 不应通过不断放宽策略保留。把行为迁移到打包模块中的 addEventListener 或框架事件绑定,内联样式按独立 style-src 方案处理。第三方组件若要求 unsafe-eval,要先定位是开发构建、旧版本还是核心运行需求;生产为了一个组件全站开放动态代码执行,影响远大于该组件本身。不能迁移的能力应隔离到更低权限 origin。

清理过程先通过源码搜索和浏览器报告建立清单,每次移除一类依赖并做功能回归。哈希适合内容固定的少量静态内联脚本,nonce 适合服务器生成标签;二者可以组合,但不要维护无法追溯来源的巨大哈希列表。strict-dynamic 等行为需要结合目标浏览器和加载链仔细评估,不能复制策略片段后假设所有浏览器以相同方式信任动态子脚本。

第三方来源按最小能力和故障域管理

允许一个第三方脚本 origin,意味着该 origin 上符合路径匹配的脚本可能在站点权限内执行。应减少第三方数量,固定官方 HTTPS 来源,评估其更新与供应链流程;能通过服务器代理数据或无脚本方案实现的功能,不必授予执行能力。来源通配符、整个 CDN 域和用户可上传脚本的共享主机风险尤其高。子资源完整性可作为额外校验,但动态版本与 nonce 仍需一致管理。

connect-src、img-src、font-src 和 frame-src 也按数据流设置。分析 SDK 若可以向任意域发送请求,会放大 XSS 数据外传;图片 data: 与 blob: 是否必要要分别论证。frame-ancestors 用于限制页面被谁嵌入,不应误用 frame-src 替代。策略文档为每个非 self 来源写明组件、用途、负责人和复核日期,移除组件时同步收紧 CSP,而不是永久积累例外。

报告模式用于发现,不用于收集敏感正文

上线前使用 Content-Security-Policy-Report-Only 收集真实页面违反项,按 effective-directive、blocked-uri、页面路由和 release 聚合。报告可能包含完整文档 URL、行号和浏览器信息,查询参数中若有敏感值会被带入,因此接收端先限制正文大小、脱敏和短期保留。公开端点需要限流与批量处理,攻击者可以伪造大量报告,不能让它成为日志放大器。

报告出现并不自动意味着把 blocked-uri 加入允许列表。先确认是站点代码、扩展注入、恶意内容还是过时页面;浏览器扩展和本地工具产生的噪声要分类。每个合法违反项回到源码移除内联依赖或增加最窄来源,记录决策。报告模式不会阻止执行,达到可接受基线后必须切换执行策略,并在执行模式继续观察违反率和关键功能指标。

浏览器回归验证阻止与正常执行两面

自动化测试读取响应 CSP,提取 nonce 并断言页面受控 script 标签使用同值、不同请求值不同。构造不带 nonce 的内联脚本、事件属性和非允许源脚本,浏览器必须阻止;带当前 nonce 的应用引导正常启动。测试缓存命中、500 错误页、登录跳转、后台和预览路由,确认没有某条响应漏头或头标签不匹配。仅对字符串快照无法证明浏览器实际执行决定。

功能矩阵覆盖导航、表单、编辑器、代码高亮、Web Worker、第三方登录和文件预览。记录控制台 CSP 违反,但不能把所有控制台噪声笼统忽略。故意在可显示用户内容的位置注入 script、img onerror 和 javascript 链接,既验证输出编码,也验证 CSP 纵深保护。200% 缩放和无 JavaScript阅读用于确保策略故障时核心信息仍可访问,不以空白页作为安全成功。

灰度收紧并为紧急回退保留安全下限

策略先在内部和小比例流量执行,按 release 比较页面错误、转化和报告。每次只收紧可归因的一组指令,避免同时升级框架和全部 CSP 后无法定位。配置由代码审查管理,不允许通过运行时后台输入任意来源。紧急回退可以暂时恢复上一个已验证策略,但最低线仍禁止宽泛脚本与对象执行,不能直接删除 CSP 让页面恢复。

框架升级、接入新分析脚本、改变缓存或启用流式 SSR 都触发重新验证 nonce 链路。定期审查来源清单与零使用报告,删除已下线例外。运维手册说明如何确认响应头、匹配标签、查看脱敏报告和区分缓存问题。可靠的 CSP 不是一次写完的长字符串,而是与模板、资源加载和发布流程一起持续验证的执行边界。

样式、Worker 与富文本需要各自明确的策略

style-src 不应因为脚本策略收紧就永久保留宽泛 unsafe-inline。框架生成的动态样式可评估 nonce、外部样式表或受控 CSS 变量,用户输入绝不能进入原始 style 字符串。CSS 虽不能直接执行普通脚本,仍可能通过外部资源请求和界面覆盖参与攻击。字体与图片来源分别限制,data: 只在确有内联资源需求时开放。

Web Worker、Service Worker 和 blob URL 通过 worker-src 等指令单独验证。允许 blob: 会扩大同源脚本构造能力,启用前确认 Worker 内容由可信 bundle 生成。Service Worker 具有持久拦截能力,注册脚本路径、作用域、更新和注销都进入回归;错误 CSP 不能让旧 Worker 永久缓存一套与当前 nonce 不匹配的 HTML。

富文本文章先经过服务端允许列表清洗,禁止 script、事件属性、危险 URL 和主动嵌入,CSP 只作为第二道边界。代码示例中的尖括号必须编码,不能因为显示教程而真实生成标签。预览与正式页使用相同清洗和 CSP,否则攻击内容可在管理员预览权限中执行。编辑器自身需要的 iframe 或 blob 来源采用独立最小配置。

CSP 多个响应头会取交集,代理、应用和 CDN 分别添加策略时可能意外阻断,并不是后一条覆盖前一条。部署检查读取公网最终响应而不只检查应用本地端口,记录所有 CSP 与 Report-Only 头。紧急调整由单一配置所有者发布,避免不同层同时修补造成策略难以推理。

实现片段

Content-Security-Policy: script-src 'self' 'nonce-{random}'

一手参考资料

评论 · 0

还没有评论留下第一句经过思考的话。

游客评论需审核。注册后可直接公开,无需审核。

SHARE / 分享

分享这篇文章

WECHAT / 微信

用微信扫一扫

在手机微信中打开文章后,再从微信右上角分享给朋友或朋友圈。