本文目录导读:

- 核心原则:永远不要信任不可控的输入
- 输入验证与净化(白名单优先)
- 输出编码(关键防御点)
- 使用安全的API和框架
- 内容安全策略(CSP,Content Security Policy)【纵深防御】
- 特定场景防护
- 禁止的危险模式(勿踩雷区)
- 一个可操作的检查清单
DOM型XSS(Cross-Site Scripting)是一种前端安全漏洞,攻击者通过操纵页面中的DOM元素(如URL参数、Fragment、本地存储、表单输入等),将恶意脚本注入到页面中并执行,要有效规避DOM型XSS,需要从输入来源、输出点和执行环境三个关键环节进行防护。
以下是具体的规避策略和最佳实践:
核心原则:永远不要信任不可控的输入
任何来自用户、URL、window.name、localStorage、sessionStorage、postMessage、Referer、Cookie 等客户端来源的数据,都应被视为不可信。
输入验证与净化(白名单优先)
- 白名单验证:只允许特定格式或字符,如果输入应该是数字或邮箱,就严格校验其格式。
- 黑名单过滤:剔除
<script>、onerror、javascript:等危险关键字,但黑名单容易被绕过(如大小写、编码、换行符等),所以不推荐作为唯一手段。
输出编码(关键防御点)
这是最直接的防护手段,根据数据将要插入的HTML上下文,使用不同的编码方式。
-
HTML上下文:插入到
<div>、<p>等标签内容中。 -
对
&,<,>, , 进行HTML实体编码。 -
示例:
textContent属性自动进行HTML编码,而innerHTML不会。优先使用textContent。 -
属性上下文:插入到HTML标签的属性中,如
<img src="[USER_INPUT]">。 -
除了对
&,<,>, , 编码外,还需要对属性分隔符(空格、 等)进行编码。 -
强烈建议:如果必须在属性中使用用户输入,最好使用
setAttribute()设置安全属性(如src、href),并确保其值不包含javascript:协议。避免使用innerHTML直接拼接属性。 -
URL上下文:插入到
<a href="...">或window.location中。 -
禁止使用
javascript:协议,如果允许用户输入URL,必须验证协议只能是http:或https:。 -
对URL参数值进行百分比编码(
encodeURIComponent)。 -
使用
URL构造函数或正则表达式解析并验证协议。 -
JavaScript上下文:插入到
<script>标签或事件处理器(如onclick)中。 -
禁止直接拼接:永远不要像
"<script>var x = '" + userInput + "';</script>"这样写。 -
序列化:如果必须将数据传递给JS,使用
JSON.parse()和JSON.stringify()处理对象,或使用encodeURIComponent()处理简单字符串。
使用安全的API和框架
-
避免使用
innerHTML、outerHTML、document.write()、document.writeln(),这些方法直接解析HTML字符串。 -
替代方案:使用
textContent(纯文本)、createElement+appendChild(动态创建节点)或模板引擎(如Handlebars、Vue、React,但需确保它们默认开启了XSS防护)。 -
对于动态执行:避免使用
eval()、setTimeout(string)、Function()、script.src动态加载不可信代码。 -
现代框架的防护:React、Vue 3、Angular 等框架默认会对模板中的变量进行HTML实体编码(除非你使用
v-html、dangerouslySetInnerHTML等特殊指令),要避免使用这些不安全的绑定,除非你手动进行了严格净化。
内容安全策略(CSP,Content Security Policy)【纵深防御】
CSP是浏览器层面的最后一道防线,即使代码中存在缺陷,CSP也能阻止恶意脚本的执行(如禁止内联脚本、限制可加载的脚本源)。
- 推荐策略:
default-src 'self'; script-src 'self' 'nonce-随机值';(禁止内联脚本,允许同源脚本,并配合Nonce机制)。 - 如果必须允许内联脚本(如第三方统计代码),可使用
'strict-dynamic'或'unsafe-hash',但不推荐'unsafe-inline'。
特定场景防护
-
URL Fragment/Query参数:
-
不要从URL中提取数据后直接插入到
document.write或innerHTML。 -
使用
URLSearchParams或window.location.hash获取参数后,必须先执行输出编码再使用。 -
postMessage事件: -
在
message事件处理函数中,必须验证event.origin是否为可信任域名。 -
对接收到的
event.data进行严格类型检查(例如期望是JSON对象,就先用JSON.parse尝试解析,并捕获异常)。 -
jsonp回调: -
回调函数名称应严格限定为白名单中的合法函数名,不要直接拼接用户输入的参数作为回调名。
-
存储型XSS的DOM变体:
-
从
localStorage或sessionStorage中读取数据时,同样视为不安全的输入,输出时仍需编码。
禁止的危险模式(勿踩雷区)
- 通过字符串拼接生成HTML代码:
element.innerHTML = '<img src="' + userInput + '">'。 - 通过字符串拼接生成JS代码并执行:
eval('var x = ' + userInput)。 - 直接使用
setTimeout、setInterval或Function构造函数执行用户字符串。 - 使用
location.href跳转到用户可控的URL(需验证协议)。
一个可操作的检查清单
- 识别所有输入来源:URL参数、hash、
window.name、localStorage、postMessage、表单输入、Cookie等。 - 选择输出上下文:数据要写入 HTML内容 / HTML属性 / URL / CSS / JS代码?
- 执行编码:
- HTML内容 →
textContent或 HTML实体编码器。 - 属性值 → 属性编码 + 协议验证。
- URL → 协议白名单 +
encodeURIComponent。 - JS代码 → 禁止拼接,使用
JSON.stringify或encodeURIComponent后再传递。
- HTML内容 →
- 使用安全API:
textContent优先于innerHTML;动态创建节点而非字符串拼接。 - 启用CSP:配置严格CSP,阻止内联脚本(除非使用Nonce)。
- 定期扫描:使用静态分析工具(如ESLint的
eslint-plugin-no-unsanitized插件)或自动化安全扫描工具检测DOM XSS。
一句话原则:绝不要让用户输入的数据在未经严格编码或净化的情况下,被浏览器解析为可执行的HTML代码或JavaScript代码。