5月30日 22:56

DOM 型 XSS 为什么难发现?前端该如何检测和修复?

直接回答

DOM 型 XSS 是漏洞发生在浏览器端的一类 XSS:页面脚本从 locationpostMessagelocalStorage、URL 参数等来源读取不可信数据,再写入 innerHTMLdocument.writeevalsetTimeout(string) 等危险位置,最终让浏览器执行了攻击者控制的代码。它和反射型、存储型最大的区别是,恶意内容不一定经过服务器响应,服务端日志里可能什么都看不到。

检测 DOM 型 XSS 要抓两件事:source 和 sink。source 是不可信输入从哪里来,sink 是它被送到哪里执行或解析。修复时优先改 sink,例如能用 textContent 就别用 innerHTML;确实要渲染 HTML,就先清洗;涉及 URL 跳转、iframe、图片地址时,要做协议和域名白名单。DOM XSS 难发现,不是因为原理复杂,而是因为前端状态太多,数据在路由、组件、缓存和第三方库之间绕几圈后,没人记得它最初来自用户。

危险与修复示例

js
const keyword = new URLSearchParams(location.search).get('q') || ''; // 危险:把 URL 参数当 HTML result.innerHTML = `搜索:${keyword}`; // 安全:只按文本显示 result.textContent = `搜索:${keyword}`;

如果业务必须展示高亮 HTML,不要手写几个 replace 就收工。HTML 上下文、属性上下文、URL 上下文的编码规则不同,手写函数很容易漏掉实体编码、大小写、闭合标签和 SVG 事件。用成熟库清洗,再配合单元测试覆盖常见 payload,会比临时补丁可靠得多。

追问

DOM 型 XSS 和反射型 XSS 怎么区分?

反射型 XSS 通常是服务器把请求参数拼进响应,漏洞点在服务端输出。DOM 型 XSS 则是浏览器端脚本自己读取并写入 DOM,服务器可能只是返回同一个静态页面。两者有时会混在一起,例如 URL 参数先被服务端渲染,再被前端脚本二次处理。判断边界时可以看恶意 payload 是否出现在 HTTP 响应体里,如果响应体没有但页面执行了,往往就是 DOM 型。

常见 source 和 sink 有哪些?

常见 source 包括 location.hreflocation.searchlocation.hashdocument.referrerwindow.namepostMessagelocalStorage 和接口返回中的用户字段。常见 sink 包括 innerHTMLouterHTMLinsertAdjacentHTMLdocument.writeevalnew Function、字符串形式的 setTimeout。取舍点是有些 API 本身不是绝对禁用,比如 innerHTML 渲染可信模板可以接受。问题在于不可信数据流进这些位置时,必须有清洗、编码或白名单校验。

手动测试 DOM XSS 应该怎么做?

先找页面里会读取 URL、hash、消息事件和本地存储的代码,再看这些值是否进入危险 sink。测试 payload 不要只用 <script>alert(1)</script>,现代浏览器和插入方式下它经常不执行,可以补充 <img src=x onerror=alert(1)>、SVG、属性闭合等变体。测试时要覆盖路由切换、弹窗打开、搜索建议、富文本预览等交互路径。踩坑点是单页应用很多逻辑懒加载,页面刚打开没触发,不代表没有漏洞。

自动化工具能发现所有 DOM XSS 吗?

不能,但它们能显著降低漏检。静态扫描能找出 source 到 sink 的可疑数据流,动态扫描能在真实浏览器里观察 payload 是否执行。边界在于复杂框架、混淆代码、运行时拼接和第三方组件会让工具误报或漏报。比较稳的做法是把 Semgrep/ESLint 规则放进 CI,再对高风险页面做人工复核和浏览器动态测试。

防护 DOM XSS 的组合策略是什么?

第一层是安全 API:文本用 textContent,属性用框架绑定,不把字符串当代码执行。第二层是输入和 URL 白名单,特别是跳转、iframe、图片和下载链接。第三层是富文本清洗和 CSP,减少漏网内容执行脚本或外传数据。最后还要保护 Cookie,给会话 Cookie 加 HttpOnlySecureSameSite,这样即使某处出现脚本执行,攻击收益也会被压低。

标签:XSS