Skip to content

Commit

Permalink
Merge pull request #710 from SoheilKhodayari/main
Browse files Browse the repository at this point in the history
Extra DOM Clobbering protection via SANITIZE_NAMED_PROPS config
  • Loading branch information
cure53 committed Aug 17, 2022
2 parents fb58d2d + e242b43 commit b49ee90
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 10 deletions.
7 changes: 6 additions & 1 deletion README.md
Expand Up @@ -43,7 +43,7 @@ let clean = DOMPurify.sanitize(dirty);
Or maybe this, if you love working with Angular or alike:

```js
import * as DOMPurify from 'dompurify'
import * as DOMPurify from 'dompurify';

let clean = DOMPurify.sanitize('<b>hello there</b>');
```
Expand Down Expand Up @@ -298,6 +298,11 @@ 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});

Expand Down
34 changes: 33 additions & 1 deletion dist/purify.cjs.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/purify.cjs.js.map

Large diffs are not rendered by default.

34 changes: 33 additions & 1 deletion dist/purify.es.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/purify.es.js.map

Large diffs are not rendered by default.

34 changes: 33 additions & 1 deletion dist/purify.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/purify.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/purify.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/purify.min.js.map

Large diffs are not rendered by default.

32 changes: 31 additions & 1 deletion src/purify.js
Expand Up @@ -266,9 +266,27 @@ function createDOMPurify(window = getGlobal()) {
* case Trusted Types are not supported */
let RETURN_TRUSTED_TYPE = false;

/* Output should be free from DOM clobbering attacks? */
/* Output should be free from DOM clobbering attacks?
* This sanitizes markups named with colliding, clobberable built-in DOM APIs.
*/
let SANITIZE_DOM = true;

/* Achieve full DOM Clobbering protection by isolating the namespace of named
* properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
*
* HTML/DOM spec rules that enable DOM Clobbering:
* - Named Access on Window (§7.3.3)
* - DOM Tree Accessors (§3.1.5)
* - Form Element Parent-Child Relations (§4.10.3)
* - Iframe srcdoc / Nested WindowProxies (§4.8.5)
* - HTMLCollection (§4.2.10.2)
*
* Namespace isolation is implemented by prefixing `id` and `name` attributes
* with a constant string, i.e., `user-content-`
*/
let SANITIZE_NAMED_PROPS = false;
const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';

/* Keep element content when removing element? */
let KEEP_CONTENT = true;

Expand Down Expand Up @@ -443,6 +461,7 @@ function createDOMPurify(window = getGlobal()) {
RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
FORCE_BODY = cfg.FORCE_BODY || false; // Default false
SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
IN_PLACE = cfg.IN_PLACE || false; // Default false
IS_ALLOWED_URI = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
Expand Down Expand Up @@ -1205,6 +1224,17 @@ function createDOMPurify(window = getGlobal()) {
continue;
}

/* Full DOM Clobbering protection via namespace isolation,
* Prefix id and name attributes with `user-content-`
*/
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
// Remove the attribute with this value
_removeAttribute(name, currentNode);

// Prefix the value and later re-create the attribute with the sanitized value
value = SANITIZE_NAMED_PROPS_PREFIX + value;
}

/* Handle attributes that require Trusted Types */
if (
trustedTypesPolicy &&
Expand Down
21 changes: 21 additions & 0 deletions test/test-suite.js
Expand Up @@ -465,6 +465,27 @@
['', '<form><input name="attributes"></form>', '<form><input></form>']
);
});
QUnit.test('Config-Flag tests: SANITIZE_NAMED_PROPS', function (assert) {
// SANITIZE_NAMED_PROPS
assert.equal(
DOMPurify.sanitize('<a id="x"></a>', {
SANITIZE_NAMED_PROPS: true,
}),
'<a id="user-content-x"></a>'
);
assert.equal(
DOMPurify.sanitize('<form id="x"><input id="y"></form>', {
SANITIZE_NAMED_PROPS: true,
}),
'<form id="user-content-x"><input id="user-content-y"></form>'
);
assert.equal(
DOMPurify.sanitize('<a id="x"></a><a id="x"></a>', {
SANITIZE_NAMED_PROPS: true,
}),
'<a id="user-content-x"></a><a id="user-content-x"></a>'
);
});
QUnit.test('Config-Flag tests: WHOLE_DOCUMENT', function (assert) {
//WHOLE_DOCUMENT
assert.equal(DOMPurify.sanitize('123', { WHOLE_DOCUMENT: false }), '123');
Expand Down

0 comments on commit b49ee90

Please sign in to comment.