Nginx安全头详解
为什么会有这两个警告?
| 指令 | 作用 | 风险 |
|---|---|---|
unsafe-inline | 允许页面内直接写 <script> 标签或 onclick 等事件 | 攻击者若找到 XSS 漏洞,可以直接注入恶意脚本执行 |
unsafe-eval | 允许使用 eval()、setTimeout(string) 等动态执行代码 | 攻击者可以混淆恶意代码绕过静态检测 |
理想的安全策略是完全禁止这两个指令,但现实是很多现有网站(尤其是使用 Vue/React 等框架或第三方 SDK 的)离不开它们。完全移除可能导致功能崩溃。
解决方案
根据你的实际情况,我提供三个阶段的方案,你可以根据需求选择。
方案一:快速过关(5 分钟,立即消除警告)
适用场景:急于通过安全扫描,网站功能复杂暂时无法重构。
做法:将 unsafe-inline 和 unsafe-eval 从 default-src 中移除,仅保留在 script-src 和 style-src 中。
add_header Content-Security-Policy "
default-src 'self' https: data: blob:;
script-src 'self' https: 'unsafe-inline' 'unsafe-eval';
style-src 'self' https: 'unsafe-inline';
img-src 'self' https: data: blob:;
font-src 'self' https: data:;
connect-src 'self' https:;
object-src 'none';
base-uri 'self';
upgrade-insecure-requests;
" always;效果:扫描工具不会再在 default-src 中看到 unsafe-*,警告消失。但注意,script-src 和 style-src 中仍然包含它们,实际安全防护仍有缺口。这是权宜之计。
方案二:彻底移除 unsafe-inline(进阶,推荐)
适用场景:希望真正提升安全性,愿意花时间改造。
核心思路:使用 Nonce(一次性随机数) 机制。服务器每次加载页面时生成一个随机 nonce 值,只有携带相同 nonce 的 <script> 标签才会被执行,攻击者注入的脚本因为没有正确的 nonce 而被拦截。
Nginx 配置(假设你使用 PHP/Node.js 等后端生成 nonce)
# 注意:这里的 {NONCE} 需要由后端动态替换
add_header Content-Security-Policy "
default-src 'self' https: data: blob:;
script-src 'nonce-{NONCE}' 'strict-dynamic' https:;
style-src 'self' https: 'unsafe-inline';
img-src 'self' https: data: blob:;
font-src 'self' https: data:;
connect-src 'self' https:;
object-src 'none';
base-uri 'self';
upgrade-insecure-requests;
" always;后端生成 nonce 示例(PHP)
<?php
// 在页面最顶部生成 nonce
$nonce = base64_encode(random_bytes(16));
header("Content-Security-Policy: default-src 'self' https: data: blob:; script-src 'nonce-$nonce' 'strict-dynamic' https:; ...");
?><!-- 页面中可信的脚本加上 nonce -->
<script nonce="<?= $nonce ?>">
// 你的业务代码
</script>关于 strict-dynamic
加上 'strict-dynamic' 后,被 nonce 信任的脚本可以动态创建并加载其他脚本(如通过 document.createElement('script')),无需再为每个子脚本单独加 nonce,极大简化了管理。
方案三:彻底移除 unsafe-eval
如果网站使用了 eval() 或类似动态执行,需要替换为安全的替代方案:
| 危险写法 | 安全替代 |
|---|---|
eval('alert(1)') | 直接写 alert(1) |
new Function('return 1')() | 改用普通函数 |
setTimeout('doSomething()', 1000) | 改为 setTimeout(doSomething, 1000) |
| JSON 解析 | 使用 JSON.parse()(它是安全的,不受限) |
如果依赖第三方库(如某些版本的 Vue、React 开发工具、谷歌地图),可能需要升级库版本或寻找替代方案。
? 上线前的安全验证(强烈推荐)
无论采用哪个方案,千万不要直接在生产环境改动!建议按以下步骤验证:
第 1 步:使用 Report-Only 模式测试
add_header Content-Security-Policy-Report-Only "
default-src 'self' https: data: blob:;
script-src 'nonce-{NONCE}' 'strict-dynamic' https:;
style-src 'self' https: 'unsafe-inline';
img-src 'self' https: data: blob:;
connect-src 'self' https:;
object-src 'none';
base-uri 'self';
upgrade-insecure-requests;
report-uri /csp-report; # 收集违规报告的接口
" always;这个头不会拦截任何内容,只会把违规行为上报到你指定的接口。部署后观察 1-2 天,如果违规报告很少或没有,说明新策略可以安全启用。
第 2 步:正式启用
将 Content-Security-Policy-Report-Only 改为 Content-Security-Policy,移除 report-uri(或保留用于监控)。
? 针对你当前情况的明确建议
| 你的需求 | 推荐方案 |
|---|---|
| 只想快速通过扫描,业务优先 | 采用 方案一,5 分钟搞定 |
| 希望真正提升安全等级,愿意花 1-2 天改造 | 采用 方案二(Nonce),这是行业最佳实践 |
| 网站依赖大量第三方库,难以改造 | 先用 方案一 过渡,同时逐步替换危险库 |
最终推荐配置(方案一,可直接复制使用)
add_header Content-Security-Policy "
default-src 'self' https: data: blob:;
script-src 'self' https: 'unsafe-inline' 'unsafe-eval';
style-src 'self' https: 'unsafe-inline';
img-src 'self' https: data: blob:;
font-src 'self' https: data:;
connect-src 'self' https:;
object-src 'none';
base-uri 'self';
upgrade-insecure-requests;
" always;替换后执行 nginx -t && systemctl reload nginx,然后清空浏览器缓存重新扫描,警告应该会消失。
如果你决定采用 方案二(Nonce),告诉我你的后端语言(PHP/Node.js/Python/Java 等),我可以给你提供完整的、可直接集成的代码示例。
最新评论 (0/10)