levmyshkin/dom_purify

DOMPurify是一个仅使用DOM的、超级快速、极具容忍度的XSS清理器,适用于HTML、MathML和SVG

资助包维护!
cure53

安装次数: 1,061

依赖项: 0

建议者: 0

安全: 0

星标: 0

关注者: 1

分支: 701

语言:JavaScript

类型:drupal-library

2.4.1 2022-11-22 12:21 UTC

This package is not auto-updated.

Last update: 2024-09-25 18:44:46 UTC


README

npm version Build and Test Downloads minified size gzip size dependents

NPM

DOMPurify是一个仅使用DOM的、超级快速、极具容忍度的XSS清理器,适用于HTML、MathML和SVG。

它也非常简单易用和入门。DOMPurify始于2014年2月,同时,已达到版本2.4.1。

DOMPurify是用JavaScript编写的,并在所有现代浏览器中运行(Safari(10+)、Opera(15+)、Internet Explorer(10+)、Edge、Firefox和Chrome - 以及几乎所有使用Blink或WebKit的浏览器)。它不会在MSIE6或其他旧浏览器上中断。它要么使用回退,要么什么也不做。

我们的自动化测试覆盖了19种不同的浏览器,还有更多。我们还涵盖了Node.js v14.x、v16.x、v17.x和v18.x,在jsdom上运行DOMPurify。已知的旧Node版本也可以正常工作,但...没有保证。

DOMPurify是由有广泛网络攻击和XSS背景的安全人员编写的。不用担心。有关更多详情,请阅读我们的安全目标 & 威胁模型。请阅读它。真的。

它做什么?

DOMPurify清理HTML并防止XSS攻击。您可以将充满脏HTML的字符串输入DOMPurify,它将返回一个字符串(除非配置了其他方式),其中包含干净的HTML。DOMPurify将删除所有包含危险HTML的内容,从而防止XSS攻击和其他恶意行为。它也非常快。我们使用浏览器提供的功能,将其转换为XSS过滤器。您的浏览器越快,DOMPurify就越快。

如何使用它?

很简单。只需在您的网站上包含DOMPurify即可。

使用未压缩的开发版本

<script type="text/javascript" src="src/purify.js"></script>

使用压缩并经过测试的生产版本(提供source-map)

<script type="text/javascript" src="dist/purify.min.js"></script>

之后,您可以通过执行以下代码来清理字符串

let clean = DOMPurify.sanitize(dirty);

或者,如果您喜欢使用Angular等

import * as DOMPurify from 'dompurify';

let clean = DOMPurify.sanitize('<b>hello there</b>');

结果HTML可以使用innerHTML或DOM的document.write()写入。这完全取决于您。请注意,默认情况下,我们允许HTML、SVG MathML。如果您只需要HTML,这可能是一个非常常见的用例,您也可以轻松设置

let clean = DOMPurify.sanitize(dirty, { USE_PROFILES: { html: true } });

TypeScript类型定义在哪里?

它们可以在这里找到:@types/dompurify

是否存在任何潜在的危险?

请注意,如果您首先清理HTML,然后修改它,您可能会轻易地无效化清理效果。如果您在清理后将标记传递给另一个库,请确保该库不会自行更改HTML。

好吧,有道理,让我们继续。

在清理您的标记后,您还可以查看属性 DOMPurify.removed 并了解哪些元素和属性被排除。请不要使用此属性做出任何安全关键的决定。这只是一个为好奇心强的人提供的辅助工具。

在服务器上运行 DOMPurify

DOMPurify在技术上也可以与Node.js一起在服务器端运行。我们的支持努力遵循Node.js版本周期

在服务器上运行DOMPurify需要存在一个DOM,这可能是预料之中的。通常,jsdom是首选工具,我们强烈建议使用jsdom的最新版本。

为什么?因为已知较旧的jsdom版本存在一些可能导致XSS的问题,即使DOMPurify做得100%正确。例如,在jsdom v19.0.0中存在已知的攻击向量,这些攻击向量在jsdom v20.0.0中得到修复——因此我们真的建议为了这个原因而保持jsdom的最新状态。

除此之外,您可以在服务器上使用DOMPurify。可能吧。这实际上取决于您在服务器端使用的jsdom或任何DOM。如果您能接受这一点,以下是使其工作的方式

npm install dompurify
npm install jsdom

对于jsdom(请使用最新版本),这应该会有效

const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');

const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);
const clean = DOMPurify.sanitize('<b>hello there</b>');

或者如果您喜欢使用导入,甚至可以这样做

import { JSDOM } from 'jsdom';
import DOMPurify from 'dompurify';

const window = new JSDOM('').window;
const purify = DOMPurify(window);
const clean = purify.sanitize('<b>hello there</b>');

如果您在自己的特定设置中遇到问题,请考虑查看令人惊叹的isomorphic-dompurify项目,该项目解决了人们可能遇到的大量问题。

npm install isomorphic-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)//>'); // becomes <img src="x">
DOMPurify.sanitize('<svg><g/onload=alert(2)//<p>'); // becomes <svg><g></g></svg>
DOMPurify.sanitize('<p>abc<iframe//src=jAva&Tab;script:alert(3)>def</p>'); // becomes <p>abc</p>
DOMPurify.sanitize('<math><mi//xlink:href="data:x,<script>alert(4)</script>">'); // becomes <math><mi></mi></math>
DOMPurify.sanitize('<TABLE><tr><td>HELLO</tr></TABL>'); // becomes <table><tbody><tr><td>HELLO</td></tr></tbody></table>
DOMPurify.sanitize('<UL><li><A HREF=//google.com>click</UL>'); // becomes <ul><li><a href="//google.com">click</a></li></ul>

支持哪些内容?

DOMPurify目前支持HTML5、SVG和MathML。DOMPurify默认允许CSS、HTML自定义数据属性。DOMPurify还支持Shadow DOM——并递归地净化DOM模板。DOMPurify还允许您使用jQuery $()elm.html() API净化HTML,而不会出现任何已知问题。

那么关于较旧浏览器如MSIE8怎么办?

DOMPurify为较旧的MSIE浏览器提供了回退行为。它使用仅适用于MSIE的toStaticHTML功能进行净化。但是请注意,在此回退模式下,以下所示的几乎所有配置标志都没有任何效果。您需要自己处理。

如果连toStaticHTML也不支持,DOMPurify将什么也不做。它简单地返回您输入的原始字符串。

DOMPurify还公开了一个名为isSupported的属性,它告诉您DOMPurify是否能够完成其工作。

关于DOMPurify和Trusted Types怎么办?

在版本1.0.9中,DOMPurify添加了对Trusted Types API的支持。在版本2.0.0中,添加了一个配置标志来控制DOMPurify对此的行为。

当在支持 Trusted Types API 的环境中使用 DOMPurify.sanitize,并且将 RETURN_TRUSTED_TYPE 设置为 true 时,它会尝试返回一个 TrustedHTML 值而不是字符串(RETURN_DOMRETURN_DOM_FRAGMENT 配置选项的行为不会改变)。

我可以配置 DOMPurify 吗?

可以。内置的默认配置值已经相当不错了——但你当然可以覆盖它们。查看 /demos 文件夹,了解如何自定义 DOMPurify 的示例。

/**
 * General settings
 */

// strip {{ ... }}, ${ ... } and <% ... %> to make output safe for template systems
// be careful please, this mode is not recommended for production usage.
// allowing template parsing in user-controlled HTML is not advised at all.
// only use this mode if there is really no alternative.
var clean = DOMPurify.sanitize(dirty, {SAFE_FOR_TEMPLATES: true});

/**
 * Control our allow-lists and block-lists
 */
// allow only <b> elements, very strict
var clean = DOMPurify.sanitize(dirty, {ALLOWED_TAGS: ['b']});

// allow only <b> and <q> with style attributes
var clean = DOMPurify.sanitize(dirty, {ALLOWED_TAGS: ['b', 'q'], ALLOWED_ATTR: ['style']});

// allow all safe HTML elements but neither SVG nor MathML
// note that the USE_PROFILES setting will override the ALLOWED_TAGS setting
// so don't use them together
var clean = DOMPurify.sanitize(dirty, {USE_PROFILES: {html: true}});

// allow all safe SVG elements and SVG Filters, no HTML or MathML
var clean = DOMPurify.sanitize(dirty, {USE_PROFILES: {svg: true, svgFilters: true}});

// allow all safe MathML elements and SVG, but no SVG Filters
var clean = DOMPurify.sanitize(dirty, {USE_PROFILES: {mathMl: true, svg: true}});

// change the default namespace from HTML to something different
var clean = DOMPurify.sanitize(dirty, {NAMESPACE: 'http://www.w3.org/2000/svg'});

// leave all safe HTML as it is and add <style> elements to block-list
var clean = DOMPurify.sanitize(dirty, {FORBID_TAGS: ['style']});

// leave all safe HTML as it is and add style attributes to block-list
var clean = DOMPurify.sanitize(dirty, {FORBID_ATTR: ['style']});

// extend the existing array of allowed tags and add <my-tag> to allow-list
var clean = DOMPurify.sanitize(dirty, {ADD_TAGS: ['my-tag']});

// extend the existing array of allowed attributes and add my-attr to allow-list
var clean = DOMPurify.sanitize(dirty, {ADD_ATTR: ['my-attr']});

// prohibit ARIA attributes, leave other safe HTML as is (default is true)
var clean = DOMPurify.sanitize(dirty, {ALLOW_ARIA_ATTR: false});

// prohibit HTML5 data attributes, leave other safe HTML as is (default is true)
var clean = DOMPurify.sanitize(dirty, {ALLOW_DATA_ATTR: false});

/**
 * Control behavior relating to Custom Elements
 */

// DOMPurify allows to define rules for Custom Elements. When using the CUSTOM_ELEMENT_HANDLING
// literal, it is possible to define exactly what elements you wish to allow (by default, none are allowed).
//
// The same goes for their attributes. By default, the built-in or configured allow.list is used.
//
// You can use a RegExp literal to specify what is allowed or a predicate, examples for both can be seen below.
// The default values are very restrictive to prevent accidental XSS bypasses. Handle with great care!


var clean = DOMPurify.sanitize(
    '<foo-bar baz="foobar" forbidden="true"></foo-bar><div is="foo-baz"></div>',
    {
        CUSTOM_ELEMENT_HANDLING: {
            tagNameCheck: null, // no custom elements are allowed
            attributeNameCheck: null, // default / standard attribute allow-list is used
            allowCustomizedBuiltInElements: false, // no customized built-ins allowed
        },
    }
); // <div is=""></div>

var clean = DOMPurify.sanitize(
    '<foo-bar baz="foobar" forbidden="true"></foo-bar><div is="foo-baz"></div>',
    {
        CUSTOM_ELEMENT_HANDLING: {
            tagNameCheck: /^foo-/, // allow all tags starting with "foo-"
            attributeNameCheck: /baz/, // allow all attributes containing "baz"
            allowCustomizedBuiltInElements: true, // customized built-ins are allowed
        },
    }
); // <foo-bar baz="foobar"></foo-bar><div is=""></div>

var clean = DOMPurify.sanitize(
    '<foo-bar baz="foobar" forbidden="true"></foo-bar><div is="foo-baz"></div>',
    {
        CUSTOM_ELEMENT_HANDLING: {
            tagNameCheck: (tagName) => tagName.match(/^foo-/), // allow all tags starting with "foo-"
            attributeNameCheck: (attr) => attr.match(/baz/), // allow all containing "baz"
            allowCustomizedBuiltInElements: true, // allow customized built-ins
        },
    }
); // <foo-bar baz="foobar"></foo-bar><div is="foo-baz"></div>

/**
 * Control behavior relating to URI values
 */
// extend the existing array of elements that can use Data URIs
var clean = DOMPurify.sanitize(dirty, {ADD_DATA_URI_TAGS: ['a', 'area']});

// extend the existing array of elements that are safe for URI-like values (be careful, XSS risk)
var clean = DOMPurify.sanitize(dirty, {ADD_URI_SAFE_ATTR: ['my-attr']});

/**
 * Control permitted attribute values
 */
// allow external protocol handlers in URL attributes (default is false, be careful, XSS risk)
// by default only http, https, ftp, ftps, tel, mailto, callto, cid and xmpp are allowed.
var clean = DOMPurify.sanitize(dirty, {ALLOW_UNKNOWN_PROTOCOLS: true});

// allow specific protocols handlers in URL attributes via regex (default is false, be careful, XSS risk)
// by default only http, https, ftp, ftps, tel, mailto, callto, cid and xmpp are allowed.
// Default RegExp: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i;
var clean = DOMPurify.sanitize(dirty, {ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|xxx):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i;});

/**
 * Influence the return-type
 */
// return a DOM HTMLBodyElement instead of an HTML string (default is false)
var clean = DOMPurify.sanitize(dirty, {RETURN_DOM: true});

// return a DOM DocumentFragment instead of an HTML string (default is false)
var clean = DOMPurify.sanitize(dirty, {RETURN_DOM_FRAGMENT: true});

// use the RETURN_TRUSTED_TYPE flag to turn on Trusted Types support if available
var clean = DOMPurify.sanitize(dirty, {RETURN_TRUSTED_TYPE: true}); // will return a TrustedHTML object instead of a string if possible

/**
 * Influence how we sanitize
 */
// return entire document including <html> tags (default is false)
var clean = DOMPurify.sanitize(dirty, {WHOLE_DOCUMENT: true});

// disable DOM Clobbering protection on output (default is true, handle with care, minor XSS risks here)
var clean = DOMPurify.sanitize(dirty, {SANITIZE_DOM: false});

// enforce strict DOM Clobbering protection via namespace isolation (default is false)
// when enabled, isolates the namespace of named properties (i.e., `id` and `name` attributes) 
// from JS variables by prefixing them with the string `user-content-` 
var clean = DOMPurify.sanitize(dirty, {SANITIZE_NAMED_PROPS: true});

// keep an element's content when the element is removed (default is true)
var clean = DOMPurify.sanitize(dirty, {KEEP_CONTENT: false});

// glue elements like style, script or others to document.body and prevent unintuitive browser behavior in several edge-cases (default is false)
var clean = DOMPurify.sanitize(dirty, {FORCE_BODY: true});

// remove all <a> elements under <p> elements that are removed
var clean = DOMPurify.sanitize(dirty, {FORBID_CONTENTS: ['a'], FORBID_TAGS: ['p']});

// change the parser type so sanitized data is treated as XML and not as HTML, which is the default
var clean = DOMPurify.sanitize(dirty, {PARSER_MEDIA_TYPE: 'application/xhtml+xml'});

/**
 * Influence where we sanitize
 */
// use the IN_PLACE mode to sanitize a node "in place", which is much faster depending on how you use DOMPurify
var dirty = document.createElement('a');
dirty.setAttribute('href', 'javascript:alert(1)');
var clean = DOMPurify.sanitize(dirty, {IN_PLACE: true}); // see https://github.com/cure53/DOMPurify/issues/288 for more info

这里还有更多示例,展示了如何运行、自定义和配置 DOMPurify 以满足您的需求。

持久配置

您可以使用 DOMPurify.setConfig 方法,而不是反复将相同的配置传递给 DOMPurify.sanitize。您的配置将一直持续到您下一次调用 DOMPurify.setConfig,或者您调用 DOMPurify.clearConfig 来重置它。请记住,只有一个活动配置,这意味着一旦设置,传递给 DOMPurify.sanitize 的所有额外配置参数都将被忽略。

钩子

DOMPurify 允许您通过使用 DOMPurify.addHook 方法将一个或多个函数附加到以下钩子之一来扩展其功能

  • beforeSanitizeElements
  • uponSanitizeElement(没有 's' - 对每个元素调用)
  • afterSanitizeElements
  • beforeSanitizeAttributes
  • uponSanitizeAttribute
  • afterSanitizeAttributes
  • beforeSanitizeShadowDOM
  • uponSanitizeShadowNode
  • afterSanitizeShadowDOM

当需要时,它将当前处理的 DOM 节点、带有验证节点和属性数据的文本和 DOMPurify 配置传递给回调。查看 MentalJS 钩子演示,了解如何优雅地使用 API。

示例:

DOMPurify.addHook(
  'beforeSanitizeElements',
  function (currentNode, hookEvent, config) {
    // Do something with the current node and return it
    // You can also mutate hookEvent (i.e. set hookEvent.forceKeepAttr = true)
    return currentNode;
  }
);

持续集成

我们目前正在使用 Github Actions 与 BrowserStack 结合使用。这使我们能够在所有支持的浏览器中确认每个提交都按照计划进行。查看构建日志:https://github.com/cure53/DOMPurify/actions

您还可以通过执行 npm test 来运行本地测试。这些测试与 Node.js v0.6.2 和 jsdom@8.5.0 一起工作得很好。

所有相关的提交都将使用 0x24BB6BF4 密钥签名,以提供额外的安全性(自 2016 年 4 月 8 日起)。

开发和贡献

安装(《npm i》)

我们官方支持 npm。GitHub Actions 工作流程配置为使用 npm 安装依赖项。当使用过时的 npm 版本时,我们无法完全确保已安装依赖项的版本,这可能导致不可预见的问题。

脚本

我们依赖于 npm run-scripts 来与我们的工具基础设施集成。我们使用 ESLint 作为预提交钩子以确保代码一致性。此外,为了简化格式化,我们在构建 /dist 资产时使用 prettier,而构建资产是通过 rollup 进行的。

以下是我们的一些 npm 脚本

  • npm run dev 以在监视源更改的同时启动构建
  • npm run test 通过 jsdom 和 karma 运行我们的测试套件
    • test:jsdom 仅通过 jsdom 运行测试
    • test:karma 仅通过 karma 运行测试
  • npm run lint 使用 ESLint(通过 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)集成,或者是“私有”的,例如,每次提交时都要修改构建分发文件。

安全邮件列表

我们维护一个邮件列表,每当发布 DOMPurify 的安全关键版本时,都会通知列表。这意味着,如果有人发现了一个绕过方法,并且我们通过一个版本修复了它(这在发现绕过方法时总是会发生),那么就会向该列表发送邮件。这通常发生在几分钟或几小时后得知绕过方法。您可以通过以下链接订阅此列表

https://lists.ruhr-uni-bochum.de/mailman/listinfo/dompurify-security

特性版本发布将不会在此列表中宣布。

谁做出了贡献?

许多人帮助并正在帮助 DOMPurify 变成它现在所是的样子,需要在此处给予认可!

JGraph 💸, GitHub 💸, CynegeticIO 💸, Sentry 💸, jarrodldavis 💸, GrantGryczan, Lowdefy 💸, granlem , oreoshake , dcramer 💸,tdeekens ❤️, peernohell ❤️, is2ei, SoheilKhodayari, franktopel, NateScarlet, neilj, fhemberger, Joris-van-der-Wel, ydaniv, terjanq, filedescriptor, ConradIrwin, gibson042, choumx, 0xSobky, styfle, koto, tlau88, strugee, oparoz, mathiasbynens, edg2s, dnkolegov, dhardtke, wirehead, thorn0, styu, mozfreddyb, mikesamuel, jorangreef, jimmyhchan, jameydeorio, jameskraus, hyderali, hansottowirtz, hackvertor, freddyb, flavorjones, djfarrelly, devd, camerondunford, buu700, buildog, alabiaga, Vector919, Robbert, GreLI, FuzzySockets, ArtemBernatskyy, @garethheyes, @shafigullin, @mmrupp, @irsdl,ShikariSenpai, ansjdnakjdnajkd, @asutherland, @mathias, @cgvwzq, @robbertatwork, @giutro, @CmdEngineer_, @avr4mit and especially @securitymb ❤️ & @masatokinugawa ❤️

测试由以下提供支持


最后但同样重要的是,感谢BrowserStack 开源计划为我们免费提供他们的服务,并在此之上提供卓越、专业和非常专业的支持。