使用模板字面量渲染页面时如何防范HTML注入?
核心解决思路
这类问题属于典型的HTML注入(XSS)风险,本质是不可信的用户输入被传入了会解析HTML的API。不存在“使用模板字面量就必须搭配innerHTML”的说法,模板字面量只是ES标准的字符串语法,和内容插入的API没有绑定关系,风险和模板字面量本身无关,完全可以通过合理选择API规避,具体方案分场景选择:
1. 纯文本场景优先用安全DOM API,从根源规避解析
插入纯用户文本时,优先使用textContent属性赋值,浏览器会自动将所有内容作为纯文本处理,不会解析任何HTML标签,完全不存在注入风险。
对应问题中的代码,两种常见安全写法:
第一种是纯DOM创建,全程不拼接含用户输入的HTML字符串:
const userInput = prompt("Enter your name", "<b>foo</b>"); // 创建DOM节点 const li = document.createElement('li'); // 用户输入直接赋值给textContent,<b>标签会原样显示为文本,不会渲染粗体 li.textContent = userInput; const ul = document.createElement('ul'); ul.append(li); const container = document.createElement('div'); container.append(ul); document.body.append(container);
第二种是固定结构用innerHTML渲染,用户输入单独赋值到预留插槽:
const userInput = prompt("Enter your name", "<b>foo</b>"); // 先渲染不含用户输入的固定结构 document.body.innerHTML = ` <div> <ul> <li class="username-slot"></li> </ul> </div> `; // 单独给内容插槽赋值文本,用户输入不会接触HTML解析流程 document.querySelector('.username-slot').textContent = userInput;
2. 必须拼接HTML字符串时,先做严格转义
如果业务场景必须将用户内容拼接进HTML字符串再传入innerHTML,必须先对用户输入做HTML实体转义,将触发HTML解析的特殊字符替换为不会被解析的字符实体,需要转义的核心字符包括:
&→&<→<>→>"→"'→'
可以利用浏览器原生能力实现轻量转义,比手写正则更可靠,不会漏过特殊字符:
function escapeHTML(unsafeStr) { const tempEl = document.createElement('div'); tempEl.textContent = unsafeStr; return tempEl.innerHTML; } // 使用时先转义用户输入,再拼进模板 const userInput = prompt("Enter your name", "<b>foo</b>"); document.body.innerHTML = ` <div> <ul> <li>${escapeHTML(userInput)}</li> </ul> </div> `;
注意:上述转义仅适用于将用户内容插入到HTML标签的文本位置,如果要将用户输入插入到HTML属性、事件回调、script/style标签内,需要对应场景的额外转义规则,不能直接套用这个方法。
最佳实践提醒
- 永远不要把未转义的用户可控内容传入
innerHTML、outerHTML、document.write这类会解析HTML的API。 - 不要自己写正则过滤HTML标签,这类规则很容易被变形的注入payload绕过,纯文本场景优先用
textContent。 - 如果业务需要渲染用户提交的富文本,不要自己实现过滤逻辑,直接使用经过社区长期安全审计的HTML净化库,只保留白名单内的安全标签和属性。
内容的提问来源于stack exchange,提问作者Khaled
相关产品推荐
相关产品推荐

