模板插值必须按上下文区分处理:文本用textcontent或he.escape(),script内用json.stringify(),url参数用encodeuricomponent(),style/class避免拼接而用css变量,template克隆需重置name/id/for并处理file输入限制,前端仍须校验服务端html,dom型xss需用dompurify并禁用eval等危险api。

模板插值必须区分上下文,不能统一用innerHTML
直接把变量塞进innerHTML是高频XSS入口。哪怕模板引擎声明“已转义”,只要遇到{{ safe }}、v-html或dangerouslySetInnerHTML这类绕过机制,风险立刻回归。更隐蔽的是:同一段用户输入可能同时用于textContent、data-id、href和style——此时只做一次HTML实体编码(如he.escape())完全不够。
- 文本内容:用
textContent或he.escape() - 插入
<script></script>内:必须用JSON.stringify()包裹后再拼接 - 作为URL参数:走
encodeURIComponent(),不是encodeURI() - 写入
style或class:避免动态拼接,改用CSS自定义属性 + JS控制
<template></template>克隆后必须重置name/id/for属性
多次克隆含<input name="email">的模板,会导致表单提交时仅保留最后一个同名字段的值——因为FormData只取同名input的末位。这不是浏览器bug,而是规范行为。
- 克隆后遍历所有表单控件,用
setAttribute('name', `email-${index}`)生成唯一name - 若需保持后端接收结构(如
user[emails][]),改用数组式命名,而非纯数字索引 -
id和for必须同步更新,否则<label for="email-0"></label>会指向失效节点 -
<input type="file">克隆后值始终为空,这是浏览器安全限制,无法绕过
服务端返回HTML片段时,前端仍需二次校验上下文
即使后端声称“已过滤XSS”,也不能跳过前端校验。DOM型XSS不发请求、不碰数据库,纯靠前端读取location.search或document.referrer后直接塞进innerHTML触发。CSP对它基本无效。
- 典型危险代码:
document.getElementById('box').innerHTML = new URLSearchParams(location.search).get('q') - 攻击者传
?q=<svg onload="fetch('/steal?c='+document.cookie)"></svg>即可窃取cookie - 正确做法:先过
DOMPurify.sanitize(),再用textContent输出;永远不用eval()、setTimeout(string) -
router.currentRoute.value.query不比location.search更安全,单页应用路由参数同样要对待
避免用document.write()和硬拼HTML字符串
document.write()在页面加载完成后调用会清空整个文档,而硬拼innerHTML字符串容易漏掉闭合标签、引号不匹配或属性未转义,导致DOM结构断裂或XSS漏洞。
- 优先用
insertAdjacentHTML()替代innerHTML,支持'beforeend'等精准位置且不破坏现有节点 - 确保目标元素已挂载——脚本放在
里而目标<div id="app">在<code>底部,就会报Cannot set property 'innerHTML' of null - 复杂交互式表单建议用
<template></template>+cloneNode(true),而非字符串模板拼接
模板插值的真正难点不在语法本身,而在判断数据最终落点——是纯文本、属性值、URL、JS字符串,还是CSS规则。同一个变量,在不同位置需要不同的处理方式,漏掉任何一个上下文,都可能让防护形同虚设。











