fix($sanitize): do not trigger CSP alert/report in Firefox and Chrome

If `ngSanitize` is added as a module dependency 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 AngularJS 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 `sanitizeSpec.js`, "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 #16463.
This commit is contained in:
Harri Lehtola
2020-04-14 20:44:54 +03:00
committed by Pete Bacon Darwin
parent 72fbd48f2a
commit 2fab3d4e00
+13 -35
View File
@@ -421,50 +421,28 @@ function $SanitizeProvider() {
}
/**
* Create an inert document that contains the dirty HTML that needs sanitizing
* Depending upon browser support we use one of three strategies for doing this.
* Support: Safari 10.x -> XHR strategy
* Support: Firefox -> DomParser strategy
* Create an inert document that contains the dirty HTML that needs sanitizing.
* We use the DOMParser API by default and fall back to createHTMLDocument if DOMParser is not
* available.
*/
var getInertBodyElement /* function(html: string): HTMLBodyElement */ = (function(window, document) {
var inertDocument;
if (document && document.implementation) {
inertDocument = document.implementation.createHTMLDocument('inert');
} else {
if (isDOMParserAvailable()) {
return getInertBodyElement_DOMParser;
}
if (!document || !document.implementation) {
throw $sanitizeMinErr('noinert', 'Can\'t create an inert html document');
}
var inertDocument = document.implementation.createHTMLDocument('inert');
var inertBodyElement = (inertDocument.documentElement || inertDocument.getDocumentElement()).querySelector('body');
return getInertBodyElement_InertDocument;
// Check for the Safari 10.1 bug - which allows JS to run inside the SVG G element
inertBodyElement.innerHTML = '<svg><g onload="this.parentNode.remove()"></g></svg>';
if (!inertBodyElement.querySelector('svg')) {
return getInertBodyElement_XHR;
} else {
// Check for the Firefox bug - which prevents the inner img JS from being sanitized
inertBodyElement.innerHTML = '<svg><p><style><img src="</style><img src=x onerror=alert(1)//">';
if (inertBodyElement.querySelector('svg img')) {
return getInertBodyElement_DOMParser;
} else {
return getInertBodyElement_InertDocument;
}
}
function getInertBodyElement_XHR(html) {
// We add this dummy element 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 = '<remove></remove>' + html;
function isDOMParserAvailable() {
try {
html = encodeURI(html);
return !!getInertBodyElement_DOMParser('');
} catch (e) {
return undefined;
return false;
}
var xhr = new window.XMLHttpRequest();
xhr.responseType = 'document';
xhr.open('GET', 'data:text/html;charset=utf-8,' + html, false);
xhr.send(null);
var body = xhr.response.body;
body.firstChild.remove();
return body;
}
function getInertBodyElement_DOMParser(html) {