一、内容安全策略(CSP)是什么

简单来说,CSP就像是给网站加了个保安队长,专门盯着哪些资源能加载、哪些行为被允许。想象一下,你开了一家店,CSP就是门口的安检仪,只有符合规定的货物才能进店。

这个保安队长通过HTTP响应头来工作,最常见的配置方式是这样的:

<!-- 示例1:基础CSP配置 -->
<!-- 技术栈:HTML/HTTP -->
<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; script-src 'self' https://trusted.cdn.com">

这个配置的意思是:

  1. default-src 'self':默认只允许加载同源的资源
  2. script-src额外允许加载来自trusted.cdn.com的脚本

二、为什么要用CSP

去年我帮一个电商网站做安全审计,发现他们的商品详情页被注入了挖矿脚本。攻击者利用评论区XSS漏洞,让每个访问页面的用户电脑都变成矿机。如果当时有CSP防护,这种攻击根本不会得逞。

CSP主要防范这些攻击:

  1. XSS攻击(各种类型的跨站脚本)
  2. 点击劫持
  3. 混合内容攻击
  4. 数据注入攻击

来看个实际案例:

<!-- 示例2:防御XSS的CSP配置 -->
<!-- 技术栈:HTML/HTTP -->
Content-Security-Policy: 
  default-src 'none';
  script-src 'self' 'unsafe-inline' 'unsafe-eval';
  style-src 'self' fonts.googleapis.com;
  img-src 'self' data:;
  font-src 'self' fonts.gstatic.com;
  connect-src 'self' api.example.com;
  frame-ancestors 'none';

这个配置:

  1. 默认禁止所有资源加载
  2. 只允许特定类型的资源从指定来源加载
  3. 完全禁止iframe嵌套(防点击劫持)

三、CSP配置详解

3.1 常用指令说明

我整理了个配置速查表:

<!-- 示例3:完整的CSP指令示例 -->
<!-- 技术栈:HTML/HTTP -->
Content-Security-Policy:
  default-src 'self';      # 默认策略
  script-src 'self'        # 脚本
    'sha256-abc123...'     # 允许特定哈希值的脚本
    'nonce-random123';     # 允许带特定nonce的脚本
  style-src 'self' 'unsafe-inline'; # 样式
  img-src *;               # 图片允许任何来源
  media-src media1.com media2.com; # 媒体文件
  frame-src 'self';        # iframe来源
  font-src fonts.com;      # 字体文件
  connect-src 'self'       # 连接限制
    api.example.com;
  report-uri /csp-report;  # 违规报告地址

3.2 特殊值的使用

新手常问:"'unsafe-inline'是不是很危险?" 确实,但有时不得不使用:

<!-- 示例4:必须使用内联脚本的场景 -->
<!-- 技术栈:HTML/HTTP -->
<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">
  // 必须内联执行的初始化代码
  initApp();
</script>

<!-- CSP需要这样配置 -->
Content-Security-Policy: 
  script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'

这种情况下:

  1. 为内联脚本添加随机nonce值
  2. CSP只允许带有正确nonce的脚本执行
  3. 比直接使用'unsafe-inline'安全得多

四、实战配置建议

4.1 渐进式部署策略

我建议分三个阶段部署CSP:

<!-- 示例5:分阶段部署的CSP -->
<!-- 技术栈:HTML/HTTP -->

<!-- 第一阶段:仅报告模式 -->
Content-Security-Policy-Report-Only: 
  default-src 'self';
  report-uri /csp-report;

<!-- 第二阶段:限制性策略+报告 -->
Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  report-uri /csp-report;

<!-- 第三阶段:严格策略 -->
Content-Security-Policy:
  default-src 'none';
  script-src 'self' 'nonce-...';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data:;

4.2 常见问题解决方案

问题1:第三方小工具加载失败

<!-- 示例6:第三方资源处理方案 -->
<!-- 技术栈:HTML/HTTP -->
Content-Security-Policy:
  script-src 'self'
    https://apis.google.com
    https://connect.facebook.net;
  frame-src
    https://youtube.com
    https://facebook.com;

问题2:WebSocket连接被阻止

<!-- 示例7:WebSocket处理方案 -->
<!-- 技术栈:HTML/HTTP -->
Content-Security-Policy:
  connect-src 'self'
    wss://api.example.com
    https://push.example.com;

五、高级防护技巧

5.1 哈希和Nonce的应用

<!-- 示例8:哈希值使用示例 -->
<!-- 技术栈:HTML/HTTP -->
<script>
// 这个脚本的SHA256哈希是:qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng=
function loadAnalytics() {
  // 关键初始化代码
}
</script>

<!-- CSP配置 -->
Content-Security-Policy:
  script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng=';

5.2 严格动态特性

<!-- 示例9:strict-dynamic用法 -->
<!-- 技术栈:HTML/HTTP -->
Content-Security-Policy:
  script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa' 'strict-dynamic';

这个配置:

  1. 信任带有正确nonce的脚本
  2. 允许这些脚本动态加载其他脚本
  3. 向后兼容性更好

六、注意事项与总结

6.1 常见陷阱

  1. 不要过度依赖'unsafe-inline'
  2. 避免使用'unsafe-eval'除非绝对必要
  3. 记得为所有资源类型设置策略
  4. 测试所有用户流程

6.2 最佳实践建议

根据我的经验,推荐这些配置原则:

  1. 从default-src 'none'开始
  2. 逐步添加必要的资源类型
  3. 使用nonce而不是unsafe-inline
  4. 实施报告机制
  5. 定期审查CSP报告

6.3 性能考量

虽然CSP会增加一些开发成本,但安全收益远大于开销。一个好的CSP策略:

  1. 平均增加约50ms的页面加载时间
  2. 但能阻止90%以上的XSS攻击
  3. 减少混合内容问题

最后提醒:CSP不是银弹,需要与其他安全措施(如输入验证、输出编码)配合使用才能发挥最大效果。