必须对 innerhtml 赋值前进行转义,否则直接使用 element.innerhtml = userinput 会引发 xss 漏洞,哪怕用户仅输入一个双引号也可能触发攻击。

innerHTML 赋值前必须转义,否则等于开后门
直接用 element.innerHTML = userInput 是最常见也最危险的 XSS 入口。哪怕用户只输入一个 " 或 ,都可能闭合属性或开启标签,触发 <code><img src="https://img.php.cn/?x-oss-process=image/resize,p_40" alt="index.html的安全性配置:HTML防止XSS攻击的最佳实践"> 这类 payload。
实操建议:
- 纯文本显示一律改用
element.textContent = userInput—— 完全绕过 HTML 解析器,零风险 - 若必须渲染富文本(如评论带链接、加粗),先调用
DOMPurify.sanitize(dirtyHtml),再赋给innerHTML;别自己写正则删<script></script>标签,绕过方式太多 - 服务端返回的 JSON 字段如果已做 HTML 转义(例如
"<div>hello</div>"),前端就不要再二次转义,否则会显示为字面量而非结构
HTML 属性值里插用户数据,引号和事件要分开防
在 <div title="{{ userInput }}"> 或 <code><a href="https://www.php.cn/link/18c934d7919291237857dfcc1e597c39"> </a> 这类场景中,只转义 和 <code>& 不够——攻击者输入 " onclick="alert(1),结果变成 <div title="" onclick="alert(1)"> ,事件照样执行。<p>实操建议:</p>
<ul>
<li>双引号包围的属性(<code>title="...")必须转义 " → ";单引号包围(title='...')则转义 ' → '
href、src)不能只做 HTML 转义,得先 encodeURIComponent() 编码,再拼入,否则 javascript:alert(1) 仍可执行<button onclick="doSomething('{{ userInput }}')"></button> 这种写法——事件处理器内嵌用户数据,无解服务端模板变量插值,确认是否默认转义
EJS、Jinja2、Thymeleaf 等模板引擎里写 {{ userInput }} 却没开启自动转义,等同于裸奔。比如 EJS 默认不转义, 直接输出原始字符串;而 才安全。
实操建议:
- PHP 用
htmlspecialchars($str, ENT_QUOTES, 'UTF-8') - Java JSTL 用
<out value="${userInput}"></out> - Jinja2 默认转义,但若用了
{{ userInput | safe }}就绕过了——每个| safe都必须有对应净化逻辑(如 DOMPurify 处理过的 HTML)
CSP 是兜底策略,但不能替代输出编码
加了 Content-Security-Policy: script-src 'self' 并不意味着 XSS 没事了。攻击者可以用 <img src="x" onerror="eval(atob('YWxlcnQoMSk='))"> 绕过,因为这是浏览器原生解析执行,不涉及外部脚本加载。
实操建议:
- CSP 仅作为最后一道防线,必须配合上下文感知的输出编码(HTML、JS、URL、CSS 各自不同)
- 启用
report-uri或report-to上报违规行为,用于发现漏掉的注入点 - 禁用
unsafe-inline和unsafe-eval,但别因此放松对innerHTML和属性拼接的审查
真正难防的不是 <script></script> 标签,而是那些看起来“只是个图片”或“只是个链接”的属性和上下文。每个插入点都要问一句:这里的数据最终会落到哪个解析器手里?是 HTML 解析器、JS 引擎,还是 URL 解析器?答案不同,处理方式就完全不同。











