diff --git a/packages/core/src/sanitization/html_sanitizer.ts b/packages/core/src/sanitization/html_sanitizer.ts
index 2c45e726935..6dd295f135f 100644
--- a/packages/core/src/sanitization/html_sanitizer.ts
+++ b/packages/core/src/sanitization/html_sanitizer.ts
@@ -98,16 +98,17 @@ class SanitizingHtmlSerializer {
// However this code never accesses properties off of `document` before deleting its contents
// again, so it shouldn't be vulnerable to DOM clobbering.
let current: Node = el.firstChild !;
+ let elementValid = true;
while (current) {
if (current.nodeType === Node.ELEMENT_NODE) {
- this.startElement(current as Element);
+ elementValid = this.startElement(current as Element);
} else if (current.nodeType === Node.TEXT_NODE) {
this.chars(current.nodeValue !);
} else {
// Strip non-element, non-text nodes.
this.sanitizedSomething = true;
}
- if (current.firstChild) {
+ if (elementValid && current.firstChild) {
current = current.firstChild !;
continue;
}
@@ -130,11 +131,19 @@ class SanitizingHtmlSerializer {
return this.buf.join('');
}
- private startElement(element: Element) {
+ /**
+ * Outputs only valid Elements.
+ *
+ * Invalid elements are skipped.
+ *
+ * @param element element to sanitize
+ * Returns true if the element is valid.
+ */
+ private startElement(element: Element): boolean {
const tagName = element.nodeName.toLowerCase();
if (!VALID_ELEMENTS.hasOwnProperty(tagName)) {
this.sanitizedSomething = true;
- return;
+ return false;
}
this.buf.push('<');
this.buf.push(tagName);
@@ -154,6 +163,7 @@ class SanitizingHtmlSerializer {
this.buf.push(' ', attrName, '="', encodeEntities(value), '"');
}
this.buf.push('>');
+ return true;
}
private endElement(current: Element) {
diff --git a/packages/core/test/linker/security_integration_spec.ts b/packages/core/test/linker/security_integration_spec.ts
index dede02e5a19..7c65aa88401 100644
--- a/packages/core/test/linker/security_integration_spec.ts
+++ b/packages/core/test/linker/security_integration_spec.ts
@@ -239,7 +239,7 @@ function declareTests({useJit}: {useJit: boolean}) {
ci.ctxProp = 'ha ';
fixture.detectChanges();
- expect(getDOM().getInnerHTML(e)).toEqual('ha evil()');
+ expect(getDOM().getInnerHTML(e)).toEqual('ha ');
ci.ctxProp = 'also evil';
fixture.detectChanges();
diff --git a/packages/core/test/sanitization/html_sanitizer_spec.ts b/packages/core/test/sanitization/html_sanitizer_spec.ts
index ab9cf053131..5f64b0abee9 100644
--- a/packages/core/test/sanitization/html_sanitizer_spec.ts
+++ b/packages/core/test/sanitization/html_sanitizer_spec.ts
@@ -36,8 +36,8 @@ import {_sanitizeHtml} from '../../src/sanitization/html_sanitizer';
.toEqual('
Hello
World
Hello
'); // NB: quote encoded as ASCII ". }); - describe('should strip dangerous elements', () => { + describe('should strip dangerous elements and its content', () => { const dangerousTags = [ - 'frameset', 'form', 'param', 'object', 'embed', 'textarea', 'input', 'button', 'option', - 'select', 'script', 'style', 'link', 'base', 'basefont' + 'form', + 'object', + 'textarea', + 'button', + 'option', + 'select', + 'script', + 'style', ]; for (const tag of dangerousTags) { it(`${tag}`, - () => { expect(_sanitizeHtml(defaultDoc, `<${tag}>evil!${tag}>`)).toEqual('evil!'); }); + () => { expect(_sanitizeHtml(defaultDoc, `<${tag}>evil!${tag}>`)).toEqual(''); }); + } + const dangerousSelfClosingTags = [ + 'frameset', + 'embed', + 'input', + 'param', + 'base', + 'basefont', + 'param', + 'link', + ]; + + for (const tag of dangerousSelfClosingTags) { + it(`${tag}`, () => { + expect(_sanitizeHtml(defaultDoc, `before<${tag}>After`)).toEqual('beforeAfter'); + }); } it(`swallows frame entirely`, () => { @@ -111,6 +133,14 @@ import {_sanitizeHtml} from '../../src/sanitization/html_sanitizer'; } }); + it('ignores content of style elements', () => { + expect(_sanitizeHtml(defaultDoc, '<img src="