浏览器 Web 安全基础系列

  1. 浏览器怎么判断是不是同一个网站

  2. Cookie 为什么会自动携带

  3. Session 登录状态为什么可能被劫持

  4. HTTPS 为什么仍然不够

  5. CORS 到底在限制什么

  6. CSRF 是怎么冒用登录状态的

  7. CSRF 如何防御

  8. XSS 为什么能在你的网站执行

  9. CSP 与浏览器安全响应头(当前篇)

  10. Clickjacking 点击劫持

  11. 第三方资源为什么危险

  12. 防盗链到底防什么

浏览器安全响应头不是“加得越多越安全”,而是服务端明确告诉浏览器:哪些脚本可执行、页面能否被嵌入、是否强制 HTTPS、Referer 发多少信息。配置正确可以显著缩小攻击面,配置错误也可能导致页面不可用。

CSP 防御层

CSP 解决什么问题

Content Security Policy 通过白名单或 nonce/hash 限制资源加载和脚本执行。一个基础策略:

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'

含义:

  • default-src 'self':未单独声明的资源默认只允许本站;

  • script-src 'self':脚本只从本站加载;

  • object-src 'none':禁止插件对象;

  • base-uri 'self':限制 <base>

  • frame-ancestors 'none':禁止页面被 iframe 嵌入。

为什么不建议 unsafe-inline

很多旧项目为了让内联脚本继续运行,会配置:

script-src 'self' 'unsafe-inline'

这会允许大量内联脚本执行,削弱 CSP 对 XSS 的防护。更推荐 nonce:

Content-Security-Policy: script-src 'self' 'nonce-r4nd0m'

页面中的合法脚本带相同 nonce:

<script nonce="r4nd0m">
  bootstrapApp();
</script>

nonce 应每个响应随机生成,不能写死在模板中。

Express 伪代码:

app.use((req, res, next) => {
  res.locals.nonce = crypto.randomBytes(16).toString('base64');
  res.setHeader('Content-Security-Policy',
    `default-src 'self'; script-src 'self' 'nonce-${res.locals.nonce}'; object-src 'none'`
  );
  next();
});

Report-Only 先观察

直接启用严格 CSP 可能阻断现有统计脚本、内联样式和第三方组件。可以先使用:

Content-Security-Policy-Report-Only: ...

浏览器记录违规但不阻断。根据报告清理依赖,再切换到正式 CSP。不要为了消除报告无限扩大白名单。

常用安全响应头

常用安全响应头

Strict-Transport-Security

强制后续访问使用 HTTPS:

Strict-Transport-Security: max-age=31536000; includeSubDomains

X-Content-Type-Options

X-Content-Type-Options: nosniff

要求浏览器尊重声明的 MIME 类型,降低把非脚本内容猜测为脚本执行的风险。服务端同时要正确返回 Content-Type

Referrer-Policy

Referrer-Policy: strict-origin-when-cross-origin

控制 Referer 发送范围:同源可发送更多信息,跨源通常只发送 Origin,HTTPS 降级到 HTTP 时不发送。

敏感 Token 不应放进 URL,即使配置 Referrer-Policy,也不该依赖它掩盖设计问题。

Permissions-Policy

限制页面和嵌入内容使用摄像头、麦克风、定位等功能:

Permissions-Policy: camera=(), microphone=(), geolocation=(self)

X-Frame-Options

旧式 iframe 防护:

X-Frame-Options: DENY

现代项目优先使用 CSP frame-ancestors,也可同时保留 X-Frame-Options 兼容旧客户端。

Helmet 是否一装就完事

Express 常用 Helmet 设置多种安全头:

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"]
    }
  }
}));

它能减少漏配,但 CSP 白名单、跨域资源、iframe 和业务需求仍要按项目设计。默认配置不是对所有系统都完美。

CSP 不能替代 XSS 修复

如果 CSP 允许大量第三方域名、unsafe-inline 或危险脚本端点,攻击者仍可能利用。即使严格 CSP 阻断某段脚本,漏洞数据还可能破坏页面结构或进行钓鱼。

正确顺序:

安全输出与清洗
→ 减少危险 API
→ CSP 限制脚本执行
→ 监控违规报告

常见误区

把所有 CDN 域名加入 script-src

第三方脚本一旦被篡改,仍拥有页面权限。应固定必要来源,并配合 SRI 或自托管。

用元标签配置所有安全头

部分策略可用 <meta>,但 HSTS、X-Frame-Options 等必须通过 HTTP 响应头;CSP 某些能力在 meta 中也有限制。

复制一份网上 CSP 直接上线

CSP 与资源加载结构高度相关,应通过 Report-Only 和浏览器开发者工具逐步收紧。

小结

安全响应头是浏览器端纵深防御:CSP 控制可执行内容,HSTS 强制 HTTPS,nosniff 限制 MIME 猜测,Referrer-Policy 减少来源泄露,Permissions-Policy 限制敏感能力。它们必须建立在正确业务代码之上。

参考资料