From 4f69d38f097fab76e683105d1c758706e6cbe1a9 Mon Sep 17 00:00:00 2001 From: Georgios Kalpakas Date: Mon, 13 Feb 2017 21:24:13 +0200 Subject: [PATCH] fix($sanitize): prevent clobbered elements from freezing the browser Closes #15699 --- docs/content/error/$sanitize/elclob.ngdoc | 11 +++++++++++ src/ngSanitize/sanitize.js | 23 +++++++++++++++++++---- test/ngSanitize/sanitizeSpec.js | 20 ++++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 docs/content/error/$sanitize/elclob.ngdoc diff --git a/docs/content/error/$sanitize/elclob.ngdoc b/docs/content/error/$sanitize/elclob.ngdoc new file mode 100644 index 000000000000..1e9e9db42725 --- /dev/null +++ b/docs/content/error/$sanitize/elclob.ngdoc @@ -0,0 +1,11 @@ +@ngdoc error +@name $sanitize:elclob +@fullName Failed to sanitize html because the element is clobbered +@description + +This error occurs when `$sanitize` sanitizer is unable to traverse the HTML because one or more of the elements in the +HTML have been "clobbered". This could be a sign that the payload contains code attempting to cause a DoS attack on the +browser. + +Typically clobbering breaks the `nextSibling` property on an element so that it points to one of its child nodes. This +makes it impossible to walk the HTML tree without getting stuck in an infinite loop, which causes the browser to freeze. \ No newline at end of file diff --git a/src/ngSanitize/sanitize.js b/src/ngSanitize/sanitize.js index f5f7490c2f67..73168716a5d2 100644 --- a/src/ngSanitize/sanitize.js +++ b/src/ngSanitize/sanitize.js @@ -18,6 +18,7 @@ var forEach; var isDefined; var lowercase; var noop; +var nodeContains; var htmlParser; var htmlSanitizeWriter; @@ -218,6 +219,11 @@ function $SanitizeProvider() { htmlParser = htmlParserImpl; htmlSanitizeWriter = htmlSanitizeWriterImpl; + nodeContains = window.Node.prototype.contains || /** @this */ function(arg) { + // eslint-disable-next-line no-bitwise + return !!(this.compareDocumentPosition(arg) & 16); + }; + // Regular Expressions for parsing tags and attributes var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, // Match everything outside of normal chars and " (quote character) @@ -381,12 +387,12 @@ function $SanitizeProvider() { if (node.nodeType === 1) { handler.end(node.nodeName.toLowerCase()); } - nextNode = node.nextSibling; + nextNode = getNonDescendant('nextSibling', node); if (!nextNode) { while (nextNode == null) { - node = node.parentNode; + node = getNonDescendant('parentNode', node); if (node === inertBodyElement) break; - nextNode = node.nextSibling; + nextNode = getNonDescendant('nextSibling', node); if (node.nodeType === 1) { handler.end(node.nodeName.toLowerCase()); } @@ -518,8 +524,17 @@ function $SanitizeProvider() { stripCustomNsAttrs(nextNode); } - node = node.nextSibling; + node = getNonDescendant('nextSibling', node); + } + } + + function getNonDescendant(propName, node) { + // An element is clobbered if its `propName` property points to one of its descendants + var nextNode = node[propName]; + if (nextNode && nodeContains.call(node, nextNode)) { + throw $sanitizeMinErr('elclob', 'Failed to sanitize html because the element is clobbered: {0}', node.outerHTML || node.outerText); } + return nextNode; } } diff --git a/test/ngSanitize/sanitizeSpec.js b/test/ngSanitize/sanitizeSpec.js index d7e3105f155b..dd8c2ba97d43 100644 --- a/test/ngSanitize/sanitizeSpec.js +++ b/test/ngSanitize/sanitizeSpec.js @@ -246,6 +246,26 @@ describe('HTML', function() { .toEqual('

text1text2

'); }); + it('should remove clobbered elements', function() { + inject(function($sanitize) { + expect(function() { + $sanitize('
'); + }).toThrowMinErr('$sanitize', 'elclob'); + + expect(function() { + $sanitize('
'); + }).toThrowMinErr('$sanitize', 'elclob'); + + expect(function() { + $sanitize('
'); + }).toThrowMinErr('$sanitize', 'elclob'); + + expect(function() { + $sanitize('
'); + }).toThrowMinErr('$sanitize', 'elclob'); + }); + }); + describe('SVG support', function() {