If [innerHTML] is used in a component and a Content-Security-Policy is set that does not allow inline styles then Firefox and Chrome show the following message: > Content Security Policy: The page’s settings observed the loading of a resource at self (“default-src”). A CSP report is being sent. This message is caused because Angular is creating an inline style tag to test for a browser bug that we use to decide what sanitization strategy to use, which causes CSP violation errors if inline CSS is prohibited. This test is no longer necessary, since the `DOMParser` is now safe to use and the `style` based check is redundant. In this fix, we default to using `DOMParser` if it is available and fall back to `createHTMLDocument()` if needed. This is the approach used by DOMPurify too. The related unit tests in `html_sanitizer_spec.ts`, "should not allow JavaScript execution when creating inert document" and "should not allow JavaScript hidden in badly formed HTML to get through sanitization (Firefox bug)", are left untouched to assert that the behavior hasn't changed in those scenarios. Fixes #25214. PR Close #36578
This commit is contained in:
parent
ae5257cda6
commit
b950d4675f
|
@ -9,49 +9,26 @@
|
||||||
/**
|
/**
|
||||||
* This helper class is used to get hold of an inert tree of DOM elements containing dirty HTML
|
* This helper class is used to get hold of an inert tree of DOM elements containing dirty HTML
|
||||||
* that needs sanitizing.
|
* that needs sanitizing.
|
||||||
* Depending upon browser support we must use one of three strategies for doing this.
|
* Depending upon browser support we use one of two strategies for doing this.
|
||||||
* Support: Safari 10.x -> XHR strategy
|
* Default: DomParser strategy
|
||||||
* Support: Firefox -> DomParser strategy
|
* Fallback: InertDocument strategy
|
||||||
* Default: InertDocument strategy
|
|
||||||
*/
|
*/
|
||||||
export class InertBodyHelper {
|
export class InertBodyHelper {
|
||||||
private inertDocument: Document;
|
private inertDocument: Document;
|
||||||
|
|
||||||
constructor(private defaultDoc: Document) {
|
constructor(private defaultDoc: Document) {
|
||||||
this.inertDocument = this.defaultDoc.implementation.createHTMLDocument('sanitization-inert');
|
this.inertDocument = this.defaultDoc.implementation.createHTMLDocument('sanitization-inert');
|
||||||
let inertBodyElement = this.inertDocument.body;
|
if (this.inertDocument.body == null) {
|
||||||
|
|
||||||
if (inertBodyElement == null) {
|
|
||||||
// usually there should be only one body element in the document, but IE doesn't have any, so
|
// usually there should be only one body element in the document, but IE doesn't have any, so
|
||||||
// we need to create one.
|
// we need to create one.
|
||||||
const inertHtml = this.inertDocument.createElement('html');
|
const inertHtml = this.inertDocument.createElement('html');
|
||||||
this.inertDocument.appendChild(inertHtml);
|
this.inertDocument.appendChild(inertHtml);
|
||||||
inertBodyElement = this.inertDocument.createElement('body');
|
const inertBodyElement = this.inertDocument.createElement('body');
|
||||||
inertHtml.appendChild(inertBodyElement);
|
inertHtml.appendChild(inertBodyElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
inertBodyElement.innerHTML = '<svg><g onload="this.parentNode.remove()"></g></svg>';
|
this.getInertBodyElement = isDOMParserAvailable() ? this.getInertBodyElement_DOMParser :
|
||||||
if (inertBodyElement.querySelector && !inertBodyElement.querySelector('svg')) {
|
this.getInertBodyElement_InertDocument;
|
||||||
// We just hit the Safari 10.1 bug - which allows JS to run inside the SVG G element
|
|
||||||
// so use the XHR strategy.
|
|
||||||
this.getInertBodyElement = this.getInertBodyElement_XHR;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
inertBodyElement.innerHTML = '<svg><p><style><img src="</style><img src=x onerror=alert(1)//">';
|
|
||||||
if (inertBodyElement.querySelector && inertBodyElement.querySelector('svg img')) {
|
|
||||||
// We just hit the Firefox bug - which prevents the inner img JS from being sanitized
|
|
||||||
// so use the DOMParser strategy, if it is available.
|
|
||||||
// If the DOMParser is not available then we are not in Firefox (Server/WebWorker?) so we
|
|
||||||
// fall through to the default strategy below.
|
|
||||||
if (isDOMParserAvailable()) {
|
|
||||||
this.getInertBodyElement = this.getInertBodyElement_DOMParser;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// None of the bugs were hit so it is safe for us to use the default InertDocument strategy
|
|
||||||
this.getInertBodyElement = this.getInertBodyElement_InertDocument;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,33 +38,7 @@ export class InertBodyHelper {
|
||||||
getInertBodyElement: (html: string) => HTMLElement | null;
|
getInertBodyElement: (html: string) => HTMLElement | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use XHR to create and fill an inert body element (on Safari 10.1)
|
* Use DOMParser to create and fill an inert body element in browsers that support it.
|
||||||
* See
|
|
||||||
* https://github.com/cure53/DOMPurify/blob/a992d3a75031cb8bb032e5ea8399ba972bdf9a65/src/purify.js#L439-L449
|
|
||||||
*/
|
|
||||||
private getInertBodyElement_XHR(html: string) {
|
|
||||||
// We add these extra elements to ensure that the rest of the content is parsed as expected
|
|
||||||
// e.g. leading whitespace is maintained and tags like `<meta>` do not get hoisted to the
|
|
||||||
// `<head>` tag.
|
|
||||||
html = '<body><remove></remove>' + html + '</body>';
|
|
||||||
try {
|
|
||||||
html = encodeURI(html);
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
xhr.responseType = 'document';
|
|
||||||
xhr.open('GET', 'data:text/html;charset=utf-8,' + html, false);
|
|
||||||
xhr.send(undefined);
|
|
||||||
const body: HTMLBodyElement = xhr.response.body;
|
|
||||||
body.removeChild(body.firstChild!);
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use DOMParser to create and fill an inert body element (on Firefox)
|
|
||||||
* See https://github.com/cure53/DOMPurify/releases/tag/0.6.7
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
private getInertBodyElement_DOMParser(html: string) {
|
private getInertBodyElement_DOMParser(html: string) {
|
||||||
// We add these extra elements to ensure that the rest of the content is parsed as expected
|
// We add these extra elements to ensure that the rest of the content is parsed as expected
|
||||||
|
@ -107,8 +58,7 @@ export class InertBodyHelper {
|
||||||
/**
|
/**
|
||||||
* Use an HTML5 `template` element, if supported, or an inert body element created via
|
* Use an HTML5 `template` element, if supported, or an inert body element created via
|
||||||
* `createHtmlDocument` to create and fill an inert DOM element.
|
* `createHtmlDocument` to create and fill an inert DOM element.
|
||||||
* This is the default sane strategy to use if the browser does not require one of the specialised
|
* This is the fallback strategy if the browser does not support DOMParser.
|
||||||
* strategies above.
|
|
||||||
*/
|
*/
|
||||||
private getInertBodyElement_InertDocument(html: string) {
|
private getInertBodyElement_InertDocument(html: string) {
|
||||||
// Prefer using <template> element if supported.
|
// Prefer using <template> element if supported.
|
||||||
|
|
Loading…
Reference in New Issue