动态渲染漏洞本质是数据与代码边界失控,关键在前端将未净化的用户数据(如URL参数、API响应)直接通过innerHTML、document.write等危险API插入DOM,而非后端过滤缺失;需定位调用点并配合DOMPurify白名单净化。

动态渲染漏洞不是“有没有”,而是“在哪一环失控”——绝大多数问题出在前端用 innerHTML、document.write 或模板引擎(如 art-template、FreeMarker)把未净化的用户数据直接拼进 DOM,且跳过了服务端转义或客户端上下文隔离。
查 innerHTML 和 outerHTML 的实际调用点
浏览器不会告诉你哪行 JS 触发了 DOM 型 XSS,得自己顺藤摸瓜。打开 DevTools → Sources 面板,全局搜索 innerHTML、outerHTML、insertAdjacentHTML,重点关注参数是否来自 location.search、location.hash、localStorage 或 AJAX 响应体。
- 常见错误模式:
el.innerHTML = `<div>${data.title}</div>`,其中data.title来自后端 JSON,但服务端没做 HTML 转义 - 容易被忽略的场景:富文本编辑器回显内容时,直接赋值给
innerHTML,而没走DOMPurify.sanitize() - 注意大小写和字符串拼接:
el['inner'+'HTML']或el.setAttribute('innerHTML', ...)也会绕过简单 grep
抓原始响应体,别信 Elements 面板显示
Elements 面板里看到的 DOM 是浏览器 Tokenizer 容错修正后的结果,灰色斜体标签就是警告:服务端返回的 HTML 已被重写。真正的问题可能藏在原始响应里——比如缺失 </div>、<script> 标签没闭合、或 UTF-8 BOM 导致 Tokenizer 解析错位。
- 必须用 Network 面板选中对应请求 → Response 标签 → 查看原始字节,而非 Preview 或 HTML 标签
- 检查 HTTP 响应头
Content-Type中的charset是否与<meta charset>一致;不一致会触发乱码解析,导致<script>被切碎成普通文本再被误识别 - 如果响应是 JSON,但前端用
JSON.parse(...).html后直接塞进innerHTML,那所有服务端转义都白费——JSON 里的<script></script>在 JS 字符串里仍是纯文本,innerHTML会把它当真实标签执行
复现时优先用非脚本 payload 绕过 CSP 和基础过滤
别一上来就试 <script>alert(1)</script>。现代站点大多有 CSP 或服务端 script 过滤,但 onerror、javascript:、SVG 事件、data: URI 这些常被漏掉。
立即学习“前端免费学习笔记(深入)”;
- 快速验证入口点是否可控:在输入框或 URL 参数里填
<img src=x onerror=alert(1)>,提交后看是否弹窗;若没弹,检查控制台是否有报错,或 Elements 面板里是否生成了该标签 - 测试富文本场景:插入
<svg/onload=alert(1)>或<a href="javascript:alert(1)">click</a>,有些编辑器允许 SVG 但禁 script - 若页面启用了 CSP,尝试
<img src=x onerror="fetch('/api/log?x='+document.cookie)">,绕过script-src限制,只依赖connect-src
模板引擎注入要盯死“模板内容来源”
模板注入和 DOM XSS 不同:它不是执行 JS,而是让攻击者控制模板语法本身。关键判断标准只有一个——用户输入是否参与了模板字符串的拼接,而不是作为变量传入。
- 危险调用示例:
template.compile(userInput)、new Template('name', new StringReader(userInput))、res.render('index', { html: userInput })且模板中写了${html}(未转义) - FreeMarker 必查:
?eval、?interpret、#include路径是否含用户参数;art-template 必查{{@data}}和{{= data}}是否用于不可信内容 - 配置项比代码更关键:FreeMarker 的
setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER)、art-template 的cache = true(防止重复编译恶意模板)必须启用
最易被忽略的是“多层转义失效”:比如服务端对 <script> 做了 HTML 实体编码,但前端 JS 又用 decodeURIComponent() 解了一次,再塞进 innerHTML——两层处理看似严谨,实则归零。查漏洞,本质是查数据流里哪一环擅自解码、拼接或信任了不该信任的来源。



















