HTML中利用hash实现CSP内联脚本白名单

作者:袖梨 2026-06-13
CSP识别的hash值需对内联脚本内容(不含<script>标签)做UTF-8编码后计算SHA-256,再base64编码;例如alert(1)生成sha256-qznLcsROx4GAYnkPvH3tkvhUL6Vf9o1IvEOw1mTHlJk=,填入script-src 'sha256-...'即可生效。

hash值怎么生成才能被CSP识别

CSP的script-src支持'sha256-xxx'这类哈希白名单,但必须基于脚本内容(不含标签)做SHA-256计算,且编码为base64(无换行、无空格)。常见错误是直接对<script>alert(1)</script>整个字符串哈希——这会导致不匹配。

正确做法:只取alert(1)这部分内容(去除首尾<script></script>),UTF-8编码后计算SHA-256,再base64编码。例如:

echo -n "alert(1)" | shasum -a 256 | xxd -r -p | base64

输出类似sha256-qznLcsROx4GAYnkPvH3tkvhUL6Vf9o1IvEOw1mTHlJk=,这个值才能放进script-src 'sha256-qznLcsROx4GAYnkPvH3tkvhUL6Vf9o1IvEOw1mTHlJk='生效。

内联脚本里有变量或模板语法怎么办

只要脚本内容在HTML渲染前就完全确定(即服务端静态生成、非客户端JS拼接),就可以生成哈希。但如果含服务端变量(如alert(<%= user.name %>)),哈希必须按最终渲染出的实际JS字符串计算——不同用户、不同上下文产生的哈希可能不同,不能复用。

立即学习“前端免费学习笔记(深入)”;

容易踩的坑:

  • 前端模板(如${name})未被服务端解析,而是留到浏览器执行,此时哈希失效——CSP校验的是原始HTML里的文本,不是运行时结果
  • 自动格式化工具(Prettier、IDE保存时)给内联脚本加了空格或换行,导致哈希不匹配
  • 使用document.writeeval动态注入的脚本,无法用hash白名单,CSP根本不检查那段内容

多个内联脚本如何管理hash白名单

每个<script>块需独立计算哈希,CSP策略中用空格分隔多个hash值。例如:

Content-Security-Policy: script-src 'sha256-A1B2...' 'sha256-C3D4...' 'self'

但要注意:

  • 浏览器对单条CSP头长度有限制(通常约1000字符),太多hash会截断,建议合并逻辑或改用nonce
  • 构建流程中若脚本内容变更,必须重新生成对应hash,否则页面JS全部被阻断,且控制台只报Refused to execute inline script,不提示具体哪条没匹配
  • 开发环境可临时加'unsafe-inline'调试,但上线前必须移除——它会使所有hash规则失效

为什么有时候hash写了还是被拦截

最常忽略的是HTML解析细节:

  • <script>console.log(1)</script><script type="text/javascript">console.log(1)</script>算作不同内容,后者多出type="text/javascript"字符串,哈希必然不同
  • 脚本前后有不可见Unicode字符(如零宽空格、BOM)、Windows换行符(rn)而Linux工具默认用n,也会导致哈希错位
  • HTTP响应头中的CSP未覆盖全部资源(比如通过<meta http-equiv="Content-Security-Policy">声明,但该标签位置在<script>之后,部分浏览器会忽略)

真要靠hash白名单,就得把内联脚本当作“编译产物”来对待:内容锁定、生成自动化、上线前验证。稍有松动,就只剩报错和空白控制台。

相关文章

精彩推荐