Kodokon kodokon.com

HTML 层面的安全:XSS、sandbox、noopener 与 CSP

识别标记特有的 XSS 攻击向量,并部署 HTML 的原生防御手段:sandbox、noopener 和内容安全策略(CSP)。

11 分钟 · 3 题

在 Kodokon 中打开本课

转义的上下文决定了一切:一个在文本节点中无害的值,在属性中却会变得可执行。未加引号的属性是最糟糕的情况:注入数据中的一个空格就足以创建一个新属性,例如一个 onmouseover 处理器。即便正确地加了引号,一个值在敏感的汇聚点(sinks)中仍然危险:hrefsrc 接受 javascript: 协议,formaction 会劫持表单的提交,而 srcdoc 在其实体被解码后会把它们当作 HTML 来解释。

HTML
<!-- userName = x onmouseover=alert(1) -->
<img src="avatar.png" alt=x onmouseover=alert(1)>
<!-- The unquoted attribute lets a handler in -->
未转义的数据在未加引号的属性中会变成代码。

一个 iframe 的 sandbox 属性会剥夺所有权限,然后你再逐个把它们授予回来:allow-scriptsallow-formsallow-popups……如果没有 allow-same-origin,被嵌入的文档会获得一个不透明来源(opaque origin):没有 cookie、没有存储、无法访问父页面的 DOM —— 即便它是从你自己的域名提供的。

HTML
<iframe
  src="https://widget.example.com"
  sandbox="allow-scripts allow-forms"
  referrerpolicy="no-referrer">
</iframe>
一个可以运行脚本、却被限制在不透明来源中的第三方小部件。

一个 target=_blank 链接在历史上会给被打开的页面一个指向你标签页的 window.opener 引用:它可以把你的页面重定向到一个钓鱼克隆站,这种攻击被称为标签劫持(tabnabbing)。现代浏览器如今会在 target=_blank 上隐式地应用 noopener,但明确写出它对于 JavaScript 中的 window.open 以及表明意图仍然至关重要;再加上 noreferrer 还能同时抑制 Referer 头。

HTML
<a href="https://external.example"
   target="_blank"
   rel="noopener noreferrer">
  View the external resource
</a>
被打开的页面会得到 window.opener === null。

内容安全策略(Content Security Policy)会限制可执行代码的来源,并中和大多数 XSS,即便注入已经成功。通过 <meta http-equiv> 标签声明时,它仍有盲点:frame-ancestorsreport-urisandbox 指令在那里会被忽略,需要 HTTP 头。相比于域名白名单,更应优先使用每次响应都重新生成的 nonce,何况 strict-dynamic 指令本就使白名单变得过时。

HTML
<meta http-equiv="Content-Security-Policy"
      content="default-src 'self';
               script-src 'self' 'nonce-r4nd0m'">
只有携带服务器 nonce 的脚本才会运行。

知识检测

确认你已牢记本课的重点内容。

  1. 为什么在同源 iframe 上组合 allow-scriptsallow-same-origin 会使 sandbox 作废?
    • 被嵌入的文档既可运行脚本又未被隔离,能触及父页面的 DOM 并自行移除 sandbox 属性
    • 这两个 token 不兼容,浏览器会忽略整个属性
    • allow-same-origin 会自动禁用所有其他 token
  2. rel=noopener 防范的是哪种攻击?
    • 被打开的页面窃取会话 cookie
    • 向被打开的页面注入脚本
    • 标签劫持(tabnabbing):被打开的页面利用 window.opener 把你的标签页重定向到一个钓鱼站点
  3. 当 CSP 通过 <meta http-equiv> 声明时,哪个指令会被忽略?
    • script-src
    • frame-ancestors
    • default-src
    • img-src