fix(platform-browser): prevent clobbered elements from freezing the browser
see
4f69d38f09
This commit is contained in:
parent
52bbc9baf4
commit
a4076c70cc
@ -61,6 +61,14 @@ const _chromeNumKeyPadMap = {
|
|||||||
'\x90': 'NumLock'
|
'\x90': 'NumLock'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let nodeContains: (a: any, b: any) => boolean;
|
||||||
|
|
||||||
|
if (global['Node']) {
|
||||||
|
nodeContains = global['Node'].prototype.contains || function(node) {
|
||||||
|
return !!(this.compareDocumentPosition(node) & 16);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A `DomAdapter` powered by full browser DOM APIs.
|
* A `DomAdapter` powered by full browser DOM APIs.
|
||||||
*
|
*
|
||||||
@ -107,6 +115,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
|||||||
|
|
||||||
get attrToPropMap(): any { return _attrToPropMap; }
|
get attrToPropMap(): any { return _attrToPropMap; }
|
||||||
|
|
||||||
|
contains(nodeA: any, nodeB: any): boolean { return nodeContains.call(nodeA, nodeB); }
|
||||||
querySelector(el: Element, selector: string): any { return el.querySelector(selector); }
|
querySelector(el: Element, selector: string): any { return el.querySelector(selector); }
|
||||||
querySelectorAll(el: any, selector: string): any[] { return el.querySelectorAll(selector); }
|
querySelectorAll(el: any, selector: string): any[] { return el.querySelectorAll(selector); }
|
||||||
on(el: Node, evt: any, listener: any) { el.addEventListener(evt, listener, false); }
|
on(el: Node, evt: any, listener: any) { el.addEventListener(evt, listener, false); }
|
||||||
|
@ -52,6 +52,7 @@ export abstract class DomAdapter {
|
|||||||
/** @internal */
|
/** @internal */
|
||||||
_attrToPropMap: {[key: string]: string};
|
_attrToPropMap: {[key: string]: string};
|
||||||
|
|
||||||
|
abstract contains(nodeA: any, nodeB: any): boolean;
|
||||||
abstract parse(templateHtml: string): any;
|
abstract parse(templateHtml: string): any;
|
||||||
abstract querySelector(el: any, selector: string): any;
|
abstract querySelector(el: any, selector: string): any;
|
||||||
abstract querySelectorAll(el: any, selector: string): any[];
|
abstract querySelectorAll(el: any, selector: string): any[];
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
import {isDevMode} from '@angular/core';
|
import {isDevMode} from '@angular/core';
|
||||||
|
|
||||||
import {DomAdapter, getDOM} from '../dom/dom_adapter';
|
import {DomAdapter, getDOM} from '../dom/dom_adapter';
|
||||||
import {DOCUMENT} from '../dom/dom_tokens';
|
|
||||||
|
|
||||||
import {sanitizeSrcset, sanitizeUrl} from './url_sanitizer';
|
import {sanitizeSrcset, sanitizeUrl} from './url_sanitizer';
|
||||||
|
|
||||||
@ -146,11 +145,15 @@ class SanitizingHtmlSerializer {
|
|||||||
if (DOM.isElementNode(current)) {
|
if (DOM.isElementNode(current)) {
|
||||||
this.endElement(current as Element);
|
this.endElement(current as Element);
|
||||||
}
|
}
|
||||||
if (DOM.nextSibling(current)) {
|
|
||||||
current = DOM.nextSibling(current);
|
let next = checkClobberedElement(current, DOM.nextSibling(current));
|
||||||
|
|
||||||
|
if (next) {
|
||||||
|
current = next;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
current = DOM.parentElement(current);
|
|
||||||
|
current = checkClobberedElement(current, DOM.parentElement(current));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.buf.join('');
|
return this.buf.join('');
|
||||||
@ -191,7 +194,15 @@ class SanitizingHtmlSerializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private chars(chars: any /** TODO #9100 */) { this.buf.push(encodeEntities(chars)); }
|
private chars(chars: string) { this.buf.push(encodeEntities(chars)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkClobberedElement(node: Node, nextNode: Node): Node {
|
||||||
|
if (nextNode && DOM.contains(node, nextNode)) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to sanitize html because the element is clobbered: ${DOM.getOuterHTML(node)}`);
|
||||||
|
}
|
||||||
|
return nextNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regular Expressions for parsing tags and attributes
|
// Regular Expressions for parsing tags and attributes
|
||||||
|
@ -112,6 +112,28 @@ export function main() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not enter an infinite loop on clobbered elements', () => {
|
||||||
|
// Some browsers are vulnerable to clobbered elements and will throw an expected exception
|
||||||
|
// IE and EDGE does not seems to be affected by those cases
|
||||||
|
// Anyway what we want to test is that browsers do not enter an infinite loop which would
|
||||||
|
// result in a timeout error for the test.
|
||||||
|
try {
|
||||||
|
sanitizeHtml(defaultDoc, '<form><input name="parentNode" /></form>');
|
||||||
|
} catch (e) {
|
||||||
|
// depending on the browser, we might ge an exception
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
sanitizeHtml(defaultDoc, '<form><input name="nextSibling" /></form>')
|
||||||
|
} catch (e) {
|
||||||
|
// depending on the browser, we might ge an exception
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
sanitizeHtml(defaultDoc, '<form><div><div><input name="nextSibling" /></div></div></form>');
|
||||||
|
} catch (e) {
|
||||||
|
// depending on the browser, we might ge an exception
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (browserDetection.isWebkit) {
|
if (browserDetection.isWebkit) {
|
||||||
it('should prevent mXSS attacks', function() {
|
it('should prevent mXSS attacks', function() {
|
||||||
expect(sanitizeHtml(defaultDoc, '<a href=" javascript:alert(1)">CLICKME</a>'))
|
expect(sanitizeHtml(defaultDoc, '<a href=" javascript:alert(1)">CLICKME</a>'))
|
||||||
|
@ -63,6 +63,15 @@ export class Parse5DomAdapter extends DomAdapter {
|
|||||||
setRootDomAdapter(new Parse5DomAdapter());
|
setRootDomAdapter(new Parse5DomAdapter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contains(nodeA: any, nodeB: any): boolean {
|
||||||
|
let inner = nodeB;
|
||||||
|
while (inner) {
|
||||||
|
if (inner === nodeA) return true;
|
||||||
|
inner = inner.parent;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
hasProperty(element: any, name: string): boolean {
|
hasProperty(element: any, name: string): boolean {
|
||||||
return _HTMLElementPropertyList.indexOf(name) > -1;
|
return _HTMLElementPropertyList.indexOf(name) > -1;
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ export class WorkerDomAdapter extends DomAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contains(nodeA: any, nodeB: any): boolean { throw 'not implemented'; }
|
||||||
hasProperty(element: any, name: string): boolean { throw 'not implemented'; }
|
hasProperty(element: any, name: string): boolean { throw 'not implemented'; }
|
||||||
setProperty(el: Element, name: string, value: any) { throw 'not implemented'; }
|
setProperty(el: Element, name: string, value: any) { throw 'not implemented'; }
|
||||||
getProperty(el: Element, name: string): any { throw 'not implemented'; }
|
getProperty(el: Element, name: string): any { throw 'not implemented'; }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user