为什么 XSS 依然是前端安全的头号威胁
跨站脚本攻击(XSS)长期占据 OWASP Top 10 的显眼位置,原因很简单:只要页面会渲染用户提供的内容,就存在注入风险。评论区、富文本编辑器、Markdown 渲染、URL 参数回显——这些常见功能都是 XSS 的温床。
XSS 的常见方式主要有三类:
存储型 XSS 是最危险的一种。攻击者将恶意脚本提交到服务器(比如一条评论),服务器将其存入数据库,之后每个访问该页面的用户都会执行这段脚本。危害范围大、持续时间长。
反射型 XSS 通常通过精心构造的 URL 诱导用户点击,恶意脚本作为请求参数发送到服务器,服务器未经处理直接回显在页面中。常见于搜索框、错误提示页。
DOM 型 XSS 完全发生在浏览器端。前端 JavaScript 从 location.hash、document.referrer 等来源读取数据,并通过 innerHTML、document.write 等方式插入页面,整个过程不经过服务器。
传统的防御思路是“转义一切”,但现实远比这复杂。现代 Web 应用需要允许一部分 HTML 标签和属性通过(比如加粗、链接、图片),纯粹的转义会让富文本功能彻底失效。手工编写白名单过滤器又极易遗漏边缘情况——浏览器对畸形 HTML 的容错解析能力,远超大多数开发者的想象。
DOMPurify 的定位与核心优势
DOMPurify 是一个专注于 HTML、SVG 和 MathML 净化的 JavaScript 库。它的设计哲学非常明确:只做一件事,并且做到极致。
它的核心优势体现在几个方面:
- 基于浏览器原生解析器:DOMPurify 利用浏览器自身的 DOM 解析引擎来构建节点树,这意味着它能正确处理浏览器实际会执行的畸形 HTML,而不是按照规范文档去猜测。
- 默认安全:开箱即用的配置已经能抵御绝大多数 XSS 向量,包括
javascript:协议、onerror等事件属性、<script>标签、SVG 中的脚本等。 - 可配置的白名单机制:允许开发者精确控制哪些标签、属性、CSS 属性可以通过。
- 持续维护与广泛验证:被 Google、Microsoft、Mozilla 等团队使用,安全社区持续对其进行审计和绕过测试。
基础用法:从危险到安全
安装方式很简单:
npm install dompurify
最基本的净化操作:
import DOMPurify from 'dompurify';
const dirty = '<img src=x onerror=alert(1)>';
const clean = DOMPurify.sanitize(dirty);
// 输出: <img src="x">
onerror 属性被移除,alert(1) 不会执行。这就是 DOMPurify 的默认行为——移除所有可执行脚本和事件处理器。
对于需要渲染用户提交的 HTML 内容的场景:
const userContent = '<p>Hello <strong>World</strong></p><script>stealCookies()</script>';
const safeContent = DOMPurify.sanitize(userContent);
document.getElementById('output').innerHTML = safeContent;
<script> 标签被剥离,安全的标签和文本保留。
进阶配置:在安全与功能之间取得平衡
实际项目中,我们往往需要允许特定标签。比如一个博客系统需要支持图片、链接、代码块:
const config = {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'a', 'img', 'code', 'pre', 'blockquote', 'ul', 'ol', 'li'],
ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'class'],
ALLOW_DATA_ATTR: false,
ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i
};
const clean = DOMPurify.sanitize(userHtml, config);
这里有几个关键点:
ALLOWED_URI_REGEXP限制了链接和图片地址的协议,阻止javascript:和data:等危险协议。ALLOW_DATA_ATTR: false禁止data-*属性,防止某些框架通过 data 属性触发行为。- 显式列出允许的标签和属性,而不是依赖默认值。
如果需要更严格的控制,还可以使用钩子函数:
DOMPurify.addHook('uponSanitizeAttribute', (node, data) => {
if (data.attrName === 'href' && !data.attrValue.startsWith('https://')) {
data.attrValue = '';
}
});
与其他防御手段的配合
DOMPurify 是前端 XSS 防御的重要一环,但不是全部。完整的防御体系需要前后端协同:
前端层面,除了 DOMPurify,还应避免使用 eval()、new Function()、setTimeout(string) 等动态执行代码的方式。对于纯文本展示场景,优先使用 textContent 而非 innerHTML。
后端层面,MySQL 防止 SQL 注入的几种写法同样值得重视。最可靠的方式是使用参数化查询(预处理语句),例如在 Node.js 中:
// 正确:参数化查询
db.query('SELECT * FROM users WHERE email = ?', [email]);
// 错误:字符串拼接
db.query(`SELECT * FROM users WHERE email = '${email}'`);
在 PHP 中使用 PDO 的预处理语句,在 Java 中使用 PreparedStatement,原理相同。此外,对输入进行类型验证、最小化数据库账户权限、使用 ORM 框架的查询构造器,都是有效的辅助手段。
服务器层面,一份 Linux 服务器安全加固清单可以帮助缩小攻击面:及时更新系统和软件包、禁用不必要的服务、配置防火墙规则、使用 SSH 密钥认证并禁用密码登录、设置 fail2ban 防止暴力破解、定期审计日志和文件权限。
常见误区与注意事项
不要直接拼接后再净化。有些开发者会先把用户输入拼接到模板中,再对整体调用 sanitize。这可能导致净化器无法正确识别某些上下文。正确的做法是先净化用户输入,再插入到模板中。
服务端渲染场景。在 Node.js 中使用 DOMPurify 需要配合 jsdom,因为 DOMPurify 依赖 DOM 环境。配置如下:
import { JSDOM } from 'jsdom';
import createDOMPurify from 'dompurify';
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);
保持更新。XSS 绕过技术不断演进,DOMPurify 团队会持续发布安全补丁。将 DOMPurify 锁定在过旧版本,可能错过关键修复。
不要过度依赖单一防线。即使使用了 DOMPurify,也应该配置 Content-Security-Policy(CSP)响应头,限制脚本来源和 unsafe-inline 的使用。纵深防御永远优于单点依赖。
结语
XSS 防御没有银弹,但 DOMPurify 提供了一层经过实战检验的可靠屏障。它的价值在于将复杂的 HTML 净化逻辑封装成简单、安全的 API,让开发者不必成为浏览器解析器专家也能有效防御 XSS。结合后端参数化查询、服务器加固和 CSP 策略,可以构建起从浏览器到数据库的完整防御链。安全是一个持续的过程,选择正确的工具并保持警惕,才是最务实的策略。
未经允许不得转载:任鹏个人博客 » 用 DOMPurify 构建可靠的 XSS 防御层


朋友圈点赞图在线生成源码