安全响应头与 CSP:给站点加一层护栏

HTTPS 解决的是「传输过程不被偷看」,安全响应头解决的是「浏览器拿到内容之后别乱来」。几行配置,成本极低。

一、四个基础头,加上就有效

nginx
# MIME 嗅探防护:别让浏览器把 .txt 当 .js 执行
add_header X-Content-Type-Options "nosniff" always;

# 点击劫持防护:不允许被别的站点 iframe 嵌套
add_header X-Frame-Options "SAMEORIGIN" always;

# 跨站请求时带多少来源信息
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# 浏览器危险 API 权限(本站不用就全关)
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

X-Frame-Options 的三个值

效果
DENY完全禁止被嵌套
SAMEORIGIN只允许同源页面嵌套(推荐)
ALLOW-FROM uri允许指定来源(已废弃,用 CSP 的 frame-ancestors 代替)

Referrer-Policy 怎么选

跨站时发送
no-referrer什么都不发
strict-origin-when-cross-origin只发域名,且 HTTPS→HTTP 时不发(推荐)
unsafe-url发完整 URL(含路径参数,有泄露风险)

二、HSTS:强制走 HTTPS

nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

作用是告诉浏览器「接下来一年,这个域名只许用 HTTPS 访问」。能防住首次访问的降级攻击(SSL Strip)。

但它有反噬:加上之后,如果你证书出问题,用户连「忽略警告继续访问」的选项都没有。所以稳妥做法是逐步加:

nginx
# 第一步:先用短时长试跑,确认全站 HTTPS 都正常
add_header Strict-Transport-Security "max-age=300" always;

# 第二步:稳定一周后加长
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

# 第三步(可选):申请加入浏览器内置 HSTS 预载列表
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

preload 一旦提交到 Chrome 的预载列表,撤回极慢(几个月)。没把握别加。

三、CSP:内容安全策略

CSP 是最强也最容易把站点搞挂的一个。它告诉浏览器「只允许加载这些来源的资源」,能有效挡住 XSS 注入的外链脚本。

先给一条适合纯静态站的严格配置:

nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'" always;

逐条解释:

指令作用
default-src所有资源的默认策略
script-src允许哪些地方加载 JS
style-src允许哪些地方加载 CSS
img-src图片来源,data: 是内联 base64
connect-srcfetch / XHR / WebSocket 能连哪
frame-ancestors谁可以 iframe 嵌套我(替代 X-Frame-Options)
base-uri限制 <base> 标签,防相对路径劫持
form-action表单能提交到哪

常见来源值:

含义
'self'同源
'none'完全禁止
'unsafe-inline'允许内联 script/style(会降低安全性
'unsafe-eval'允许 eval(尽量避免)
https://cdn.x.com指定域名
data: / blob:协议白名单

四、CSP 从零上线的稳妥步骤

直接上严格策略,很可能把站点样式或脚本全挡了。正确做法是先观察:

第一步:用 Report-Only 模式跑一周

nginx
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; report-uri /csp-report" always;

这个头只报告不拦截。打开浏览器控制台,被拦的资源会以警告形式列出,但页面照常工作。

第二步:收集报告,把合法来源加进白名单

如果用了 Google Fonts、CDN 上的 JS、内联脚本,逐一补进去:

nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:;" always;

第三步:正式启用,去掉 Report-Only

五、内联脚本怎么办

严格 CSP 会禁止 onclick="..."<script>...</script> 内联写法。三种解法,按推荐度排序:

  1. 改成外部文件(最干净);
  2. 用 nonce:服务器每次生成一个随机值,只有带该值的脚本能执行;
  3. 用 hash:把脚本内容算 sha256 写进策略(内容一变就失效,适合固定片段)。
nginx
# nonce 方案:Nginx 用变量生成,每次请求都不同
add_header Content-Security-Policy "script-src 'self' 'nonce-$request_id'" always;
html
<script nonce="这里的值要由服务端模板输出">…</script>

纯静态站没有服务端渲染,最简单的办法就是:把内联脚本都挪到外部 JS 文件里。本站就是这么做的——所有 JS 在 assets/js/ 下,CSP 可以直接设得很严。

六、一份整合配置

直接粘到 server 块里:

nginx
server {
    listen 443 ssl http2;
    server_name www.bzii.cn;

    # 安全头
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # CSP(纯静态站版本)
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; frame-ancestors 'self'; base-uri 'self'" always;

    root /var/www/baize;
    index index.html;
    location / { try_files $uri $uri/ /index.html; }
}

always 参数很重要:不加的话,Nginx 只对 200/201/204/206/301/302/303/304/307/308 这些响应加头,4xx/5xx 响应就不会带。

七、验证

bash
# 看响应头
curl -sI https://www.bzii.cn | grep -iE "content-security|x-frame|x-content|strict-transport|referrer"

在线工具推荐:

  • securityheaders.com —— 打分并解释每一项;
  • Mozilla Observatory —— 更严格,会给出具体改进建议。

拿到 A 或 A+ 并不难,基本就是上面这几行的事。