DOMPurify 是一款仅适用于 DOM、超快、超级容忍 XSS 的 HTML、MathML 和 SVG 清理程序。
它的使用和上手也非常简单。 DOMPurify 于 2014 年 2 月启动,同时版本已达到v3.2.1 。
DOMPurify 使用 JavaScript 编写,适用于所有现代浏览器(Safari (10+)、Opera (15+)、Edge、Firefox 和 Chrome - 以及几乎所有使用 Blink、Gecko 或 WebKit 的其他浏览器)。它不会在 MSIE 或其他旧版浏览器上中断。它根本什么也不做。
请注意,DOMPurify v2.5.7 是支持 MSIE 的最新版本。对于与 MSIE 兼容的重要安全更新,请使用 2.x 分支。
我们的自动化测试目前涵盖 24 种不同的浏览器,未来还会有更多浏览器。我们还介绍了在 jsdom 上运行 DOMPurify 的 Node.js v16.x、v17.x、v18.x 和 v19.x。众所周知,旧的 Node 版本也可以工作,但是嘿......不能保证。
DOMPurify 是由在 Web 攻击和 XSS 方面拥有丰富背景的安全人员编写的。不要害怕。有关更多详细信息,另请阅读我们的安全目标和威胁模型。请阅读它。就像,真的。
DOMPurify 可以净化 HTML 并防止 XSS 攻击。您可以向 DOMPurify 提供充满脏 HTML 的字符串,它将返回一个包含干净 HTML 的字符串(除非另有配置)。 DOMPurify 将删除所有包含危险 HTML 的内容,从而防止 XSS 攻击和其他恶意行为。这也太快了。我们使用浏览器提供的技术并将其变成 XSS 过滤器。您的浏览器速度越快,DOMPurify 的速度就越快。
这很容易。只需在您的网站上包含 DOMPurify 即可。
<script type="text/javascript" src="src/purify.js"></script>
<script type="text/javascript" src="dist/purify.min.js"></script>
之后,您可以通过执行以下代码来清理字符串:
const clean = DOMPurify.sanitize(dirty);
或者也许是这样,如果你喜欢使用 Angular 或类似的东西:
import DOMPurify from 'dompurify';const clean = DOMPurify.sanitize('<b>你好</b>');
可以使用innerHTML
将生成的HTML写入DOM元素,或者使用document.write()
将生成的HTML写入DOM。这完全取决于你。请注意,默认情况下,我们允许 HTML、SVG和MathML。如果您只需要 HTML(这可能是一个非常常见的用例),您也可以轻松进行设置:
const clean = DOMPurify.sanitize(dirty, { USE_PROFILES: { html: true } });
好吧,请注意,如果您先清理 HTML,然后再修改它,您可能很容易使清理效果失效。如果您在清理后将清理后的标记提供给另一个库,请确保该库不会自行处理 HTML。
清理标记后,您还可以查看属性DOMPurify.removed
并找出哪些元素和属性被丢弃。请不要使用此属性做出任何安全关键决策。这只是好奇心的一个小帮手。
DOMPurify 从技术上讲也可以在服务器端与 Node.js 一起使用。我们的支持努力遵循 Node.js 发布周期。
在服务器上运行 DOMPurify 需要存在 DOM,这可能并不奇怪。通常,jsdom 是首选工具,我们强烈建议使用最新版本的jsdom 。
为什么?因为众所周知,旧版本的jsdom存在问题,即使DOMPurify 100% 正确地完成所有操作,也会导致 XSS。例如, jsdom v19.0.0中存在已知的攻击向量,这些攻击向量已在jsdom v20.0.0中修复 - 因此,我们确实建议保持jsdom最新。
另请注意,像 happy-dom 这样的工具是存在的,但目前并不被认为是安全的。目前不建议将 DOMPurify 与happy-dom结合使用,这可能会导致 XSS。
除此之外,您可以在服务器上使用 DOMPurify。大概。这实际上取决于jsdom或您在服务器端使用的任何 DOM。如果您可以接受这一点,那么您可以通过以下方式让它发挥作用:
npm 安装 dompurify npm 安装 jsdom
对于jsdom (请使用最新版本),这应该可以解决问题:
const createDOMPurify = require('dompurify');const { JSDOM } = require('jsdom');const window = new JSDOM('').window;const DOMPurify = createDOMPurify(window);const clean = DOMPurify.sanitize(' <b>你好</b>');
或者甚至是这样,如果您更喜欢使用导入:
import { JSDOM } from 'jsdom';import DOMPurify from 'dompurify';const window = new JSDOM('').window;const purify = DOMPurify(window);const clean = purify.sanitize('<b>你好< /b>');
如果您在特定设置中遇到问题,请考虑查看令人惊叹的 isomorphic-dompurify 项目,它解决了人们可能遇到的许多问题。
npm 安装同构-dompurify
import DOMPurify from 'isomorphic-dompurify';const clean = DOMPurify.sanitize('<s>hello</s>');
当然有演示!玩 DOMPurify
首先,请立即通过电子邮件与我们联系,以便我们进行修复。 PGP密钥
此外,您可能有资格获得错误赏金! Fastmail 的优秀人员使用 DOMPurify 提供服务,并将我们的库添加到他们的错误赏金范围中。因此,如果您找到绕过或削弱 DOMPurify 的方法,请同时查看他们的网站和错误赏金信息。
纯化标记是什么样子的?好吧,演示展示了一大堆令人讨厌的元素。但我们也展示一些较小的例子!
DOMPurify.sanitize('<img src=x onerror=alert(1)//>'); // 变为 <img src="x">DOMPurify.sanitize('<svg><g/onload=alert(2)//<p>'); // 变为 <svg><g></g></svg>DOMPurify.sanitize('<p>abc<iframe//src=jAva	script:alert(3)>def</p>'); // 变为 <p>abc</p>DOMPurify.sanitize('<math><mi//xlink:href="data:x,<script>alert(4)</script>">'); // 变为 <math><mi></mi></math>DOMPurify.sanitize('<TABLE><tr><td>HELLO</tr></TABL>'); // 变为 <table><tbody><tr><td>HELLO</td></tr></tbody></table>DOMPurify.sanitize('<UL><li><A HREF=//google .com>点击</UL>'); // 变为 <ul><li><a href="//google.com">点击</a></li></ul>
DOMPurify 目前支持 HTML5、SVG 和 MathML。 DOMPurify 默认允许 CSS、HTML 自定义数据属性。 DOMPurify 还支持 Shadow DOM - 并递归地清理 DOM 模板。 DOMPurify 还允许您清理 HTML,以便与 jQuery $()
和elm.html()
API 一起使用,而不会出现任何已知问题。
DOMPurify 根本不执行任何操作。它只是返回您输入的字符串。 DOMPurify 公开了一个名为isSupported
属性,它告诉您它是否能够完成其工作,以便您可以制定自己的备份计划。
在 1.0.9 版本中,DOMPurify 添加了对可信类型 API 的支持。在版本 2.0.0 中,添加了一个配置标志来控制 DOMPurify 的相关行为。
当DOMPurify.sanitize
在可信类型 API 可用且RETURN_TRUSTED_TYPE
设置为true
环境中使用时,它会尝试返回TrustedHTML
值而不是字符串( RETURN_DOM
和RETURN_DOM_FRAGMENT
配置选项的行为不会更改)。
请注意,为了使用 DOMPurify 在trustedTypes
中创建策略,需要RETURN_TRUSTED_TYPE: false
,因为createHTML
需要普通字符串,而不是TrustedHTML
。下面的例子展示了这一点。
window.trustedTypes!.createPolicy('默认', { createHTML: (to_escape) =>DOMPurify.sanitize(to_escape, { RETURN_TRUSTED_TYPE: false }),});
是的。包含的默认配置值已经相当不错了 - 但您当然可以覆盖它们。查看/demos
文件夹,查看大量有关如何自定义 DOMPurify 的示例。
// 剥离 {{ ... }}、${ ... } 和 <% ... %> 以使输出对模板系统安全 // 请小心,不建议在生产环境中使用此模式。 // 允许根本不建议在用户控制的 HTML 中进行模板解析。// 仅在确实没有替代方案时才使用此模式。 const clean = DOMPurify.sanitize(dirty, {SAFE_FOR_TEMPLATES: true});// 更改包含风险的注释的方式HTML 字符// 请非常小心,如果您确实只处理 // HTML 而没有其他内容,没有 SVG、MathML 等,则此设置只应设置为“false”。 // 否则,从 `true` 更改为 `false` 将会以这种或其他方式导致 XSS。 const clean = DOMPurify.sanitize(dirty, {SAFE_FOR_XML: false});
// 只允许 <b> 元素,非常严格 const clean = DOMPurify.sanitize(dirty, {ALLOWED_TAGS: ['b']});// 只允许带有样式属性的 <b> 和 <q> const clean = DOMPurify.sanitize( dirty, {ALLOWED_TAGS: ['b', 'q'], ALLOWED_ATTR: ['style']});// 允许所有安全 HTML 元素,但两者都不允许SVG 或 MathML // 请注意,USE_PROFILES 设置将覆盖 ALLOWED_TAGS 设置 // 因此不要一起使用它们 const clean = DOMPurify.sanitize(dirty, {USE_PROFILES: {html: true}});// 允许所有安全的 SVG 元素和 SVG 过滤器,无 HTML 或 MathMLconst clean = DOMPurify.sanitize(dirty, {USE_PROFILES: {svg: true, svgFilters: true}});// 允许所有安全的 MathML 元素和 SVG,但不允许 SVG Filtersconst clean = DOMPurify.sanitize(dirty, {USE_PROFILES: {mathMl: true, svg: true}});// 更改默认命名空间从 HTML 到不同的东西 const clean = DOMPurify.sanitize(dirty, {NAMESPACE: 'http://www.w3.org/2000/svg'});// 保留所有安全 HTML 不变,并将 <style> 元素添加到块列表const clean = DOMPurify.sanitize(dirty, {FORBID_TAGS: [' style']});// 保留所有安全 HTML 不变,并将样式属性添加到块列表 const clean = DOMPurify.sanitize(dirty, {FORBID_ATTR: ['style']});// 扩展现有的允许标签数组并将 <my-tag> 添加到允许列表 const clean = DOMPurify.sanitize(dirty, {ADD_TAGS: ['my-tag']});/ / 扩展现有的允许属性数组并将 my-attr 添加到允许列表 const clean = DOMPurify.sanitize(dirty, {ADD_ATTR: ['my-attr']});// 禁止ARIA 属性,保留其他安全 HTML 不变(默认为 true)const clean = DOMPurify.sanitize(dirty, {ALLOW_ARIA_ATTR: false});// 禁止 HTML5 数据属性,保留其他安全 HTML 不变(默认为 true)const clean = DOMPurify.sanitize(脏, {ALLOW_DATA_ATTR: false});
// DOMPurify 允许定义自定义元素的规则。当使用 CUSTOM_ELEMENT_HANDLING// 文字时,可以准确定义您希望允许的元素(默认情况下,不允许任何元素)。//// 它们的属性也是如此。默认情况下,使用内置或配置的allow.list。////您可以使用RegExp文字来指定允许的内容或谓词,两者的示例如下所示。//默认值非常严格以防止意外的 XSS 绕过。小心处理!const clean = DOMPurify.sanitize('<foo-bar baz="foobar"禁忌="true"></foo-bar><div is="foo-baz"></div>', {CUSTOM_ELEMENT_HANDLING: {tagNameCheck: null, // 不允许自定义元素attributeNameCheck: null, // 使用默认/标准属性允许列表allowCustomizedBuiltInElements: false, // 不允许自定义内置元素允许},}); // <div is=""></div>const clean = DOMPurify.sanitize('<foo-bar baz="foobar"禁忌="true"></foo-bar><div is="foo-baz "></div>',{CUSTOM_ELEMENT_HANDLING: {tagNameCheck: /^foo-/, // 允许所有以“foo-”开头的标签 attributeNameCheck: /baz/, // 允许所有包含以下内容的属性"baz"allowCustomizedBuiltInElements: true, // 允许自定义内置},}); // <foo-bar baz="foobar"></foo-bar><div is="foo-baz"></div>const clean = DOMPurify.sanitize('<foo-bar baz="foobar" 禁止="true"></foo-bar><div is="foo-baz"></div>',{CUSTOM_ELEMENT_HANDLING: {tagNameCheck: (tagName) => tagName.match(/^foo-/), / / 允许所有以以下开头的标签"foo-"attributeNameCheck: (attr) => attr.match(/baz/), // 允许所有包含 "baz"allowCustomizedBuiltInElements: true, // 允许自定义内置},}); // <foo-bar baz="foobar"></foo-bar><div is="foo-baz"></div>
// 扩展可以使用 Data URIs 的现有元素数组 const clean = DOMPurify.sanitize(dirty, {ADD_DATA_URI_TAGS: ['a', 'area']});// 扩展对 URI 安全的现有元素数组-像值(小心,XSS 风险)const clean = DOMPurify.sanitize(dirty, {ADD_URI_SAFE_ATTR: ['我的属性']});
// 在 URL 属性中允许外部协议处理程序(默认为 false,小心,XSS 风险)// 默认情况下仅允许 http、https、ftp、ftps、tel、mailto、callto、sms、cid 和 xmpp。const clean = DOMPurify.sanitize(dirty, {ALLOW_UNKNOWN_PROTOCOLS: true});// 通过正则表达式允许 URL 属性中的特定协议处理程序(默认为 false,小心 XSS risk)// 默认情况下仅允许 http、https、ftp、ftps、tel、mailto、callto、sms、cid 和 xmpp。// 默认正则表达式: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^az]|[a-z+.-]+(?:[^ a-z+.-:]|$))/i;const clean = DOMPurify.sanitize(dirty, {ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|xxx):|[^az]|[a-z+.-]+(?: [^a-z+.-:]|$))/i});
// 返回 DOM HTMLBodyElement 而不是 HTML 字符串(默认为 false)const clean = DOMPurify.sanitize(dirty, {RETURN_DOM: true});// 返回 DOM DocumentFragment 而不是 HTML 字符串(默认为 false)const clean = DOMPurify.sanitize(dirty, {RETURN_DOM_FRAGMENT: true});// 使用 RETURN_TRUSTED_TYPE 标志打开可信类型支持 if availableconst clean = DOMPurify.sanitize(dirty, {RETURN_TRUSTED_TYPE: true}); // 如果可能,将返回 TrustedHTML 对象而不是字符串 // 使用提供的可信类型策略 const clean = DOMPurify.sanitize(dirty, {// 提供的策略必须定义 createHTML 和 createScriptURLTRUSTED_TYPES_POLICY: trustTypes.createPolicy({createHTML(s) {返回 s},createScriptURL(s) { 返回 s},}});
// 返回包含 <html> 标签的整个文档(默认为 false)const clean = DOMPurify.sanitize(dirty, {WHOLE_DOCUMENT: true});// 禁用输出上的 DOM Clobbering 保护(默认为 true,小心处理,轻微 XSS这里有风险)const clean = DOMPurify.sanitize(dirty, {SANITIZE_DOM: false});// 通过命名空间隔离强制执行严格的 DOM 破坏保护(默认为false)// 启用后,通过在 JS 变量前添加字符串 `user-content-` const clean = DOMPurify.sanitize(dirty) 来将命名属性的命名空间(即 `id` 和 `name` 属性)与 JS 变量隔离, {SANITIZE_NAMED_PROPS: true});// 删除元素时保留元素的内容(默认为 true)const clean = DOMPurify.sanitize(dirty, {KEEP_CONTENT: false});// 将样式、脚本或其他元素粘合到 document.body 并防止在几种边缘情况下不直观的浏览器行为(默认为 false)const clean = DOMPurify.sanitize(dirty, {FORCE_BODY: true} );// 删除已删除的 <p> 元素下的所有 <a> 元素 const clean = DOMPurify.sanitize(dirty, {FORBID_CONTENTS: ['a'], FORBID_TAGS: ['p']});// 更改解析器类型,以便将清理后的数据视为 XML 而不是 HTML,这是默认值 const clean = DOMPurify.sanitize(dirty, {PARSER_MEDIA_TYPE: '应用程序/xhtml+xml'});
// 使用 IN_PLACE 模式“就地”清理节点,这要快得多,具体取决于您如何使用 DOMPurifyconst dirty = document.createElement('a');dirty.setAttribute('href', 'javascript:alert(1) )');const clean = DOMPurify.sanitize(dirty, {IN_PLACE: true}); // 请参阅 https://github.com/cure53/DOMPurify/issues/288 了解更多信息
这里还有更多示例,展示如何运行、自定义和配置 DOMPurify 以满足您的需求。
您可以使用DOMPurify.setConfig
方法,而不是重复将相同的配置传递给DOMPurify.sanitize
。您的配置将持续存在,直到您下次调用DOMPurify.setConfig
或调用DOMPurify.clearConfig
来重置它为止。请记住,只有一个活动配置,这意味着一旦设置,传递给DOMPurify.sanitize
所有额外配置参数都将被忽略。
DOMPurify 允许您通过使用DOMPurify.addHook
方法将一个或多个函数附加到以下挂钩之一来增强其功能:
beforeSanitizeElements
uponSanitizeElement
(No 's' - 为每个元素调用)
afterSanitizeElements
beforeSanitizeAttributes
uponSanitizeAttribute
afterSanitizeAttributes
beforeSanitizeShadowDOM
uponSanitizeShadowNode
afterSanitizeShadowDOM
它将当前处理的 DOM 节点传递给回调,并在需要时传递带有已验证节点和属性数据的文字以及 DOMPurify 配置。查看 MentalJS hook 演示,了解如何很好地使用该 API。
例子:
DOMPurify.addHook( 'uponSanitizeAttribute', function (currentNode, hookEvent, config) {// 对当前节点执行某些操作 // 您还可以改变当前节点的 hookEvent(即设置 hookEvent.forceKeepAttr = true)// 对于除 'uponSanitizeAttribute' 以外的钩子类型 hookEvent 等于 null });
选项 | 自从 | 笔记 |
---|---|---|
SAFE_FOR_JQUERY | 2.1.0 | 无需更换。 |
我们目前正在将 Github Actions 与 BrowserStack 结合使用。这使我们能够确认每一次提交都在所有受支持的浏览器中按计划进行。在此处查看构建日志:https://github.com/cure53/DOMPurify/actions
您可以通过执行npm test
进一步运行本地测试。测试在 Node.js v0.6.2 和 [email protected] 上运行良好。
所有相关提交都将使用密钥0x24BB6BF4
进行签名,以提高安全性(自 2016 年 4 月 8 日起)。
npm i
)我们正式支持npm
。 GitHub Actions 工作流程配置为使用npm
安装依赖项。当使用已弃用的npm
版本时,我们无法完全确保已安装依赖项的版本,这可能会导致意外问题。
我们依靠 npm 运行脚本来与我们的工具基础设施集成。我们使用 ESLint 作为预提交挂钩来确保代码一致性。此外,为了简化格式化,我们在通过rollup
构建/dist
资产时使用 prettier 。
这些是我们的 npm 脚本:
npm run dev
开始构建,同时观察源代码的变化
npm run test
通过 jsdom 和 karma 运行我们的测试套件
test:jsdom
仅通过 jsdom 运行测试
test:karma
仅通过 karma 运行测试
npm run lint
使用 ESLint 对源代码进行 lint(通过 xo)
npm run format
使用 prettier 来格式化我们的源代码以轻松通过 ESLint
npm run build
将我们的分发资产构建为压缩和未压缩的 UMD 模块
npm run build:umd
仅构建未缩小的 UMD 模块
npm run build:umd:min
仅构建缩小的 UMD 模块
注意:所有运行脚本都是通过npm run <script>
触发的。
还有更多的 npm 脚本,但它们主要是为了与 CI 集成,或者是“私有的”,例如在每次提交时修改构建分发文件。
我们妈