三种投递路径
反射型 XSS 把请求值回显到响应;存储型 XSS 持久化载荷并提供给后续用户;DOM 型 XSS 由客户端代码把不可信数据写入 innerHTML 等危险位置,服务端未参与。
在输出点防御
// ❌ HTML 注入
container.innerHTML = `<p>${userInput}</p>`;
// ✅ textContent 把值当作文本
const p = document.createElement('p');
p.textContent = userInput;
container.append(p);
如果确实需要 HTML,使用维护良好的白名单库清洗,并按具体上下文编码。给 href 或 src 赋值前校验协议,只允许 https:、mailto: 和明确批准的其他协议。
纵深防御
- 针对 HTML、属性、JavaScript、CSS 与 URL 上下文分别编码。
- 使用框架的安全 API,避免原始 HTML 写入点。
- 配置带 nonce 或哈希的严格 CSP。
- 会话 Cookie 设置
HttpOnly,使脚本无法读取。 - 使用经过审查的库清洗 Markdown 与富文本。
何时最重要
任何展示其他用户或外部系统内容的页面都是候选。与 CSRF 相比,XSS 攻击用户对页面本身的信任,并可以读取用户能看到的数据;CSRF 则在不读取响应的情况下触发动作。两者都需要上下文相关防御,而不是一个全局过滤器。
陷阱
只黑名单 <script> 会漏掉事件处理器、SVG 和编码后的载荷。先转义再解码会重新打开漏洞。存储未清洗的 HTML 并依赖客户端清理,会把漏洞转移给每一个消费方。