在 Web 前端开发中,模板字符串因为支持多行和变量插值,常被用来组装 HTML 片段。但当插值内容来自用户输入框、URL 参数或第三方接口时,如果不加处理直接写入页面,就会形成反射型或存储型 XSS 漏洞。模板字符串本身只是 ECMAScript 2015 引入的语法糖,它让字符串拼接更直观,却并没有提供任何自动转义能力,最终生成的仍然是一个普通字符串,浏览器在解析这个字符串为 DOM 时不会区分代码与数据。

为什么模板字符串直接嵌入用户输入会触发 XSS
很多开发者误以为模板字符串比传统加号拼接更安全,其实两者在安全性上没有任何区别。假设页面上有一个评论框,用户输入 <img src=x onerror=alert(1)>,后端原样返回,前端用模板字符串组装列表:这时浏览器拿到的是一段包含标签结构的 HTML 文本,一旦通过 innerHTML 插入,onerror 中的脚本就会执行。模板字符串的 ${} 仅仅是把变量值转换为字符串并连接,完全不会分析内容是否含有尖括号或事件属性。
从解析原理看,HTML 文档的词法分析器在接收到 innerHTML 赋值时,会按标签、属性、文本节点规则重新构建 DOM 树。用户输入中的 <script> 或 <img onerror> 会被识别为元素而非文本,因此脚本逻辑被激活。以下代码展示了一个危险写法:
const userInput = '<img src=x onerror=alert(document.cookie)>';
const html = `<div class="comment">${userInput}</div>`;
document.getElementById('list').innerHTML = html;
上述代码中,userInput 来自不可信源,却未做处理。即便使用 textContent 之外的任何 HTML 写入接口,都会造成脚本执行。要注意,即便把输入放进 value 属性或 href 属性中,也可能通过 javascript: 协议触发脚本,因此不能只转义尖括号。
使用原生转义与文本接口阻断注入路径
最基础也最有效的防护方式,是区分“写文本”和“写结构”。如果目的只是展示用户提交的内容,应当使用 textContent 或 innerText,这两个接口会把赋值内容作为纯文本节点处理,浏览器不会对文本中的尖括号发起标签解析。改写前面的例子,只需创建元素并赋值文本:
const userInput = '<img src=x onerror=alert(1)>';
const div = document.createElement('div');
div.className = 'comment';
div.textContent = userInput;
document.getElementById('list').appendChild(div);
在这种方式下,页面上看到的是字面意义上的 <img src=x onerror=alert(1)>,而不会发起任何请求或弹窗。如果确实需要在模板字符串中生成 HTML,则必须手动转义关键字符:将 & 转成 &、< 转成 <、> 转成 >、双引号转成 "、单引号转成 '。一个简单的转义函数如下:
function escapeHtml(str) {
return str.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
const safe = `<div>${escapeHtml(userInput)}</div>`;
这种转义方案能覆盖绝大多数场景,但缺点是需要开发者时刻记着调用,且对属性值中的 javascript: 伪协议无能为力。因此更推荐默认用 textContent,仅在可控结构中使用转义后的变量。
复杂富文本场景下的白名单过滤方案
当业务要求用户能提交带格式的富文本(例如论坛帖子、邮件模板),单纯转义会破坏合法标签,此时应使用基于白名单的 DOM 清洗库,例如 DOMPurify。它的原理是把字符串解析为 DOM 树,遍历每个节点和属性,移除不在白名单中的元素与事件处理器,再序列化回安全字符串。配合模板字符串时,先清洗再插入:
const dirty = '<p>hello</p><img src=x onerror=alert(1)>';
const clean = DOMPurify.sanitize(dirty);
const html = `<article>${clean}</article>`;
document.getElementById('content').innerHTML = html;
在上面的代码中,<p> 被保留,而 <img> 上的 onerror 被剥离,如果配置禁止 img 标签则整个标签消失。DOMPurify 支持自定义标签和属性白名单,也能处理 SVG、MathML 等复杂命名空间,比手写正则稳健得多。要注意,即便使用此类库,也应避免把清洗后的内容通过 eval 或 setTimeout 字符串形式执行,因为那已经脱离 HTML 解析范畴。
除了前端清洗,后端在存储和返回数据时也应做一道校验,例如限制字段长度、过滤控制字符,并在 HTTP 响应头加上 Content-Security-Policy 来禁止内联脚本。前后端协同才能把 XSS 风险降到最低。模板字符串作为书写便利工具,只要牢记“插值即数据、写入需转义、富文本靠白名单”的原则,就能在保持代码清晰的同时保障应用安全。