Vulnerability: Pannellum Cross-Site Scripting due to data not being sanitized for URIs or vbscript Versions of `pannellum` prior to 2.5.6 are vulnerable to Cross-Site Scripting (XSS). The package fails to sanitize URLs for data URIs, which may allow attackers to execute arbitrary code in a victim's browser. 


## Recommendation

Upgrade to version 2.5.6 or later.
Vulnerability ID: GHSA-m52x-29pq-w3vv
CWE: CWE-79
Severity score: 4.8
Project: https://github.com/mpetroff/pannellum
Published: 2019-11-22
File: src/js/pannellum.js
Commit message: Merge pull request from GHSA-m52x-29pq-w3vv

Fix potential XSS vulnerability
Commit author: Matthew Petroff

Vulnerable version (before fix):
 function createHotSpot(hs) {         if (config.basePath && !absoluteURL(imgp))             imgp = config.basePath + imgp;         a = document.createElement('a');         a.href = sanitizeURL(hs.URL ? hs.URL : imgp);         a.target = '_blank';         span.appendChild(a);         var image = document.createElement('img'); function createHotSpot(hs) {         span.style.maxWidth = 'initial';     } else if (hs.URL) {         a = document.createElement('a');         a.href = sanitizeURL(hs.URL);         if (hs.attributes) {             for (var key in hs.attributes) {                 a.setAttribute(key, hs.attributes[key]); function processOptions(isPreview) {                 var authorText = escapeHTML(config[key]);                 if (config.authorURL) {                     var authorLink = document.createElement('a');                     authorLink.href = sanitizeURL(config['authorURL']);                     authorLink.target = '_blank';                     authorLink.innerHTML = escapeHTML(config[key]);                     authorText = authorLink.outerHTML; function processOptions(isPreview) {                          case 'fallback':                 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'); function escapeHTML(s) {  * 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;

Fixed version (after fix):
 function createHotSpot(hs) {         if (config.basePath && !absoluteURL(imgp))             imgp = config.basePath + imgp;         a = document.createElement('a');         a.href = sanitizeURL(hs.URL ? hs.URL : imgp, true);         a.target = '_blank';         span.appendChild(a);         var image = document.createElement('img'); function createHotSpot(hs) {         span.style.maxWidth = 'initial';     } else if (hs.URL) {         a = document.createElement('a');         a.href = sanitizeURL(hs.URL, true);         if (hs.attributes) {             for (var key in hs.attributes) {                 a.setAttribute(key, hs.attributes[key]); function processOptions(isPreview) {                 var authorText = escapeHTML(config[key]);                 if (config.authorURL) {                     var authorLink = document.createElement('a');                     authorLink.href = sanitizeURL(config['authorURL'], true);                     authorLink.target = '_blank';                     authorLink.innerHTML = escapeHTML(config[key]);                     authorText = authorLink.outerHTML; function processOptions(isPreview) {                          case 'fallback':                 var link = document.createElement('a');                 link.href = sanitizeURL(config[key], true);                 link.target = '_blank';                 link.textContent = 'Click here to view this panorama in an alternative viewer.';                 var message = document.createElement('p'); function escapeHTML(s) {  * The URL cannot be of protocol 'javascript'.  * @private  * @param {string} url - URL to sanitize  * @param {boolean} href - True if URL is for link (blocks data URIs)  * @returns {string} Sanitized URL  */ function sanitizeURL(url, href) {     if (url.trim().toLowerCase().indexOf('javascript:') === 0 ||         url.trim().toLowerCase().indexOf('vbscript:') === 0) {         console.log('Script URL removed.');         return 'about:blank';     }     if (href && url.trim().toLowerCase().indexOf('data:') === 0) {         console.log('Data URI removed from link.');         return 'about:blank';     }     return url;

Patch:
patch: @@ -1719,7 +1719,7 @@ function createHotSpot(hs) {
         if (config.basePath && !absoluteURL(imgp))
             imgp = config.basePath + imgp;
         a = document.createElement('a');
-        a.href = sanitizeURL(hs.URL ? hs.URL : imgp);
+        a.href = sanitizeURL(hs.URL ? hs.URL : imgp, true);
         a.target = '_blank';
         span.appendChild(a);
         var image = document.createElement('img');
@@ -1731,7 +1731,7 @@ function createHotSpot(hs) {
         span.style.maxWidth = 'initial';
     } else if (hs.URL) {
         a = document.createElement('a');
-        a.href = sanitizeURL(hs.URL);
+        a.href = sanitizeURL(hs.URL, true);
         if (hs.attributes) {
             for (var key in hs.attributes) {
                 a.setAttribute(key, hs.attributes[key]);
@@ -2005,7 +2005,7 @@ function processOptions(isPreview) {
                 var authorText = escapeHTML(config[key]);
                 if (config.authorURL) {
                     var authorLink = document.createElement('a');
-                    authorLink.href = sanitizeURL(config['authorURL']);
+                    authorLink.href = sanitizeURL(config['authorURL'], true);
                     authorLink.target = '_blank';
                     authorLink.innerHTML = escapeHTML(config[key]);
                     authorText = authorLink.outerHTML;
@@ -2016,7 +2016,7 @@ function processOptions(isPreview) {
             
             case 'fallback':
                 var link = document.createElement('a');
-                link.href = sanitizeURL(config[key]);
+                link.href = sanitizeURL(config[key], true);
                 link.target = '_blank';
                 link.textContent = 'Click here to view this panorama in an alternative viewer.';
                 var message = document.createElement('p');
@@ -2378,10 +2378,17 @@ function escapeHTML(s) {
  * The URL cannot be of protocol 'javascript'.
  * @private
  * @param {string} url - URL to sanitize
+ * @param {boolean} href - True if URL is for link (blocks data URIs)
  * @returns {string} Sanitized URL
  */
-function sanitizeURL(url) {
-    if (url.trim().toLowerCase().indexOf('javascript:') === 0) {
+function sanitizeURL(url, href) {
+    if (url.trim().toLowerCase().indexOf('javascript:') === 0 ||
+        url.trim().toLowerCase().indexOf('vbscript:') === 0) {
+        console.log('Script URL removed.');
+        return 'about:blank';
+    }
+    if (href && url.trim().toLowerCase().indexOf('data:') === 0) {
+        console.log('Data URI removed from link.');
         return 'about:blank';
     }
     return url;
path: src/js/pannellum.js