XSS中“闭合标签”是利用HTML解析器自动修复未闭合标签的容错机制实现注入,而非漏洞本身;浏览器自动补全DOM结构,使恶意脚本得以执行,真实防御需按输出上下文做针对性编码而非简单过滤。

XSS攻击中“闭合标签”不是漏洞本身,而是利用HTML解析器对标签嵌套和结束符的容错机制实现注入——浏览器不会因标签未闭合而停止渲染,反而会自动补全或重排DOM结构,这给了攻击者插入恶意内容的空间。
HTML解析器如何处理未闭合标签
浏览器的HTML解析器(如WebKit的HTMLTokenizer、Blink的HTMLParser)在遇到未闭合标签时,会基于HTML5规范中的“栈式开放元素”规则进行自动修复。例如:
-
<div><script>alert(1)不会报错,解析器会自动补上</script></div>,但中间插入的脚本仍可能执行 -
<input value="<img src=x onerror=eval(atob('YWxlcnQoMSk='))>">中,双引号提前闭合后,onerror属性被解析为合法事件处理器 - 多个嵌套未闭合的
<span><div><p>会被解析器按上下文“合理收尾”,但收尾位置可能恰好把攻击载荷暴露在可执行上下文中
为什么白名单过滤比黑名单更难绕过
黑名单依赖识别已知危险模式(如 <script>、onerror、javascript:),但HTML解析器允许大量变体:
- 大小写混合:
<ScRiPt>、<IMG SRC="x" OnErRoR="..."> - 空格/制表符/换行干扰:
<img src=x onerror=alert(1)> - 编码混淆:
<script></script>在某些上下文中会被二次解码后执行 - 非标准属性名:部分老版本IE支持
onclick外的onmouseover、onfocus、甚至自定义事件
白名单策略(如只允许 <p>、<strong>、href、src)直接切断所有未授权标签和属性的解析路径,不依赖“识别恶意”,而是“只放行已知安全”。
立即学习“前端免费学习笔记(深入)”;
DOM型XSS中闭合逻辑失效的真实原因
DOM型XSS不经过服务端,所以“闭合标签”本质是前端JS操作DOM时的上下文错位。典型场景:
-
document.getElementById("out").innerHTML = location.hash.slice(1);—— 输入#<img src=x onerror=alert(1)>,innerHTML直接触发解析,此时没有服务端过滤,闭合与否完全由JS赋值位置决定 -
el.setAttribute("src", userInput)—— 若用户输入"javascript:alert(1)",属性值被当作JS URI执行,与标签是否闭合无关 -
eval("var name = '" + userInput + "'")—— 单引号闭合失败直接导致代码注入,这里根本不存在HTML标签结构
这类问题无法靠HTML过滤解决,必须严格区分数据来源和输出上下文,对 innerHTML、eval、setAttribute 等高危API做语义级约束。
真实拦截点不在标签层,而在上下文编码
现代防御真正起效的位置,是把用户输入按其**最终渲染上下文**做针对性编码,而非笼统“过滤标签”:
- 输出到HTML文本节点 → 使用
textContent或 HTML实体编码(<→) - 输出到HTML属性值(
<div title="X">)→ 先用引号包裹,再对引号及特殊字符编码 - 输出到JS字符串字面量(
var msg = "X";)→ 使用JSON序列化或JS字符串转义('→\') - 输出到URL参数(
href="/search?q=X")→ 使用encodeURIComponent()
绕过常发生在开发者误判上下文:比如把应进JS上下文的数据直接拼进HTML属性,或对已编码数据重复编码导致解码失效。最隐蔽的坑是模板引擎(如Vue/React)的 v-html 或 dangerouslySetInnerHTML —— 它们跳过框架默认防护,等价于裸调 innerHTML。



















