Skip to content

Commit

Permalink
fix($sanitize): remove browser bug detections from inert strategy sel…
Browse files Browse the repository at this point in the history
…ection

Default to using DOMParser if it is available and fall back to
createHTMLDocument if needed. This is the approach suggested in the
related pull request angular#17013 and used by DOMPurify too. It also safely
avoids using an inline style tag that causes CSP violation errors if inline
CSS is prohibited.

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 angular#16463.
  • Loading branch information
peruukki committed Apr 16, 2020
1 parent 418355f commit 7f57c68
Showing 1 changed file with 13 additions and 35 deletions.
48 changes: 13 additions & 35 deletions src/ngSanitize/sanitize.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
throw $sanitizeMinErr('noinert', 'Can\'t create an inert html document');
if (isDOMParserAvailable()) {
return getInertBodyElement_DOMParser;
}
var inertBodyElement = (inertDocument.documentElement || inertDocument.getDocumentElement()).querySelector('body');

// 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;
}
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;

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) {
Expand Down

0 comments on commit 7f57c68

Please sign in to comment.