From 465c5a0767aecbfc239b1b34e4f6c9d2cd53aad8 Mon Sep 17 00:00:00 2001 From: Gabriel Cangussu Date: Sun, 3 Jun 2018 19:36:44 -0300 Subject: [PATCH 1/2] Removed wrong encodings of URLs (fixes #611). --- src/js/pannellum.js | 2 +- src/standalone/standalone.js | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/js/pannellum.js b/src/js/pannellum.js index 9644c4b..6f75e1e 100644 --- a/src/js/pannellum.js +++ b/src/js/pannellum.js @@ -392,7 +392,7 @@ function init() { if (xhr.status != 200) { // Display error if image can't be loaded var a = document.createElement('a'); - a.href = encodeURI(p); + a.href = p; a.innerHTML = a.href; anError(config.strings.fileAccessError.replace('%s', a.outerHTML)); } diff --git a/src/standalone/standalone.js b/src/standalone/standalone.js index c5523eb..0974117 100644 --- a/src/standalone/standalone.js +++ b/src/standalone/standalone.js @@ -10,17 +10,16 @@ function parseURLParameters() { var URL; if (window.location.hash.length > 0) { // Prefered method since parameters aren't sent to server - URL = [window.location.hash.slice(1)]; + URL = window.location.hash.slice(1); } else { - URL = decodeURI(window.location.href).split('?'); - URL.shift(); + URL = window.location.search.slice(1); } - if (URL.length < 1) { + if (!URL) { // Display error if no configuration parameters are specified anError('No configuration options were specified.'); return; } - URL = URL[0].split('&'); + URL = URL.split('&'); var configFromURL = {}; for (var i = 0; i < URL.length; i++) { var option = URL[i].split('=')[0]; From 02a639f16629fa059475ed2d6109a7879272ac90 Mon Sep 17 00:00:00 2001 From: Gabriel Cangussu Date: Tue, 5 Jun 2018 00:38:33 -0300 Subject: [PATCH 2/2] Fix double encoding URLs and better protect against XSS atacks. --- src/js/pannellum.js | 55 ++++++++++++++++++++++++++++++++++++-------- src/standalone/standalone.js | 6 +++-- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/js/pannellum.js b/src/js/pannellum.js index 6f75e1e..f5aeb6c 100644 --- a/src/js/pannellum.js +++ b/src/js/pannellum.js @@ -352,7 +352,7 @@ function init() { var onError = function(e) { var a = document.createElement('a'); a.href = e.target.src; - a.innerHTML = a.href; + a.textContent = a.href; anError(config.strings.fileAccessError.replace('%s', a.outerHTML)); }; @@ -367,7 +367,7 @@ function init() { } panoImage[i].onload = onLoad; panoImage[i].onerror = onError; - panoImage[i].src = encodeURI(p); + panoImage[i].src = sanitizeURL(p); } } } else if (config.type == 'multires') { @@ -393,7 +393,7 @@ function init() { // Display error if image can't be loaded var a = document.createElement('a'); a.href = p; - a.innerHTML = a.href; + a.textContent = a.href; anError(config.strings.fileAccessError.replace('%s', a.outerHTML)); } var img = this.response; @@ -1684,7 +1684,7 @@ function createHotSpot(hs) { p = hs.video; if (config.basePath && !absoluteURL(p)) p = config.basePath + p; - video.src = encodeURI(p); + video.src = sanitizeURL(p); video.controls = true; video.style.width = hs.width + 'px'; renderContainer.appendChild(div); @@ -1694,11 +1694,11 @@ function createHotSpot(hs) { if (config.basePath && !absoluteURL(p)) p = config.basePath + p; a = document.createElement('a'); - a.href = encodeURI(hs.URL ? hs.URL : p); + a.href = sanitizeURL(hs.URL ? hs.URL : p); a.target = '_blank'; span.appendChild(a); var image = document.createElement('img'); - image.src = encodeURI(p); + image.src = sanitizeURL(p); image.style.width = hs.width + 'px'; image.style.paddingTop = '5px'; renderContainer.appendChild(div); @@ -1706,7 +1706,7 @@ function createHotSpot(hs) { span.style.maxWidth = 'initial'; } else if (hs.URL) { a = document.createElement('a'); - a.href = encodeURI(hs.URL); + a.href = sanitizeURL(hs.URL); a.target = '_blank'; renderContainer.appendChild(a); div.className += ' pnlm-pointer'; @@ -1930,7 +1930,7 @@ function processOptions(isPreview) { p = config.basePath + p; preview = document.createElement('div'); preview.className = 'pnlm-preview-img'; - preview.style.backgroundImage = "url('" + encodeURI(p) + "')"; + preview.style.backgroundImage = "url('" + sanitizeURLForCss(p) + "')"; renderContainer.appendChild(preview); } @@ -1971,7 +1971,16 @@ function processOptions(isPreview) { break; case 'fallback': - infoDisplay.errorMsg.innerHTML = '

Your browser does not support WebGL.
Click here to view this panorama in an alternative viewer.

'; + var link = document.createElement('a'); + link.href = sanitizeURL(config[key]); + link.target = '_blank'; + link.textContent = 'Click here to view this panorama in an alternative viewer.'; + var message = document.createElement('p'); + message.textContent = 'Your browser does not support WebGL.' + message.appendChild(document.createElement('br')); + message.appendChild(link); + infoDisplay.errorMsg.innerHTML = ''; // Removes all children nodes + infoDisplay.errorMsg.appendChild(message); break; case 'hfov': @@ -2312,6 +2321,34 @@ function escapeHTML(s) { } /** + * Removes possibility of XSS attacks with URLs. + * The URL cannot be of protocol 'javascript'. + * @private + * @param {string} url - URL to sanitize + * @returns {string} Sanitized URL + */ +function sanitizeURL(url) { + if (url.trim().toLowerCase().indexOf('javascript:') === 0) { + return 'about:blank'; + } + return url; +} + +/** + * Removes possibility of XSS atacks with URLs for CSS. + * The URL will be sanitized with `sanitizeURL()` and single quotes + * and double quotes escaped. + * @private + * @param {string} url - URL to sanitize + * @returns {string} Sanitized URL + */ +function sanitizeURLForCss(url) { + return sanitizeURL(url) + .replace(/"/g, '%22') + .replace(/'/g, '%27'); +} + +/** * Checks whether or not a panorama is loaded. * @memberof Viewer * @instance diff --git a/src/standalone/standalone.js b/src/standalone/standalone.js index 0974117..9188b4c 100644 --- a/src/standalone/standalone.js +++ b/src/standalone/standalone.js @@ -1,7 +1,9 @@ function anError(error) { var errorMsg = document.createElement('div'); errorMsg.className = 'pnlm-info-box'; - errorMsg.innerHTML = '

' + error + '

'; + var p = document.createElement('p'); + p.textContent = error; + errorMsg.appendChild(p); document.getElementById('container').appendChild(errorMsg); } @@ -56,7 +58,7 @@ function parseURLParameters() { // Display error if JSON can't be loaded var a = document.createElement('a'); a.href = configFromURL.config; - a.innerHTML = a.href; + a.textContent = a.href; anError('The file ' + a.outerHTML + ' could not be accessed.'); return; }