CVE-2021-23414: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: This affects the package video.js before 7.14.3. The src attribute of track tag allows to bypass HTML escaping and execute arbitrary code.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2021-07-28T08:15Z
Repository: https://github.com/videojs/video.js
Language: JavaScript
Files changed: src/js/utils/url.js, test/unit/utils/url.test.js
Commit date: 2021-07-22 13:14:26 -0400
Commit message: fix: remove IE8 url parsing workaround (#7334)


Diff: diff --git a/src/js/utils/url.js b/src/js/utils/url.js
index 78f30e89..06d9a400 100644
--- a/src/js/utils/url.js
+++ b/src/js/utils/url.js
@@ -39,42 +39,29 @@ import window from 'global/window';
  *
  * @return   {url:URLObject}
  *           An object of url details
  */
 export const parseUrl = function(url) {
+  // This entire method can be replace with URL once we are able to drop IE11
+
   const props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host'];
 
   // add the url to an anchor and let the browser parse the URL
-  let a = document.createElement('a');
+  const a = document.createElement('a');
 
   a.href = url;
 
-  // IE8 (and 9?) Fix
-  // ie8 doesn't parse the URL correctly until the anchor is actually
-  // added to the body, and an innerHTML is needed to trigger the parsing
-  const addToBody = (a.host === '' && a.protocol !== 'file:');
-  let div;
-
-  if (addToBody) {
-    div = document.createElement('div');
-    div.innerHTML = `<a href="${url}"></a>`;
-    a = div.firstChild;
-    // prevent the div from affecting layout
-    div.setAttribute('style', 'display:none; position:absolute;');
-    document.body.appendChild(div);
-  }
-
   // Copy the specific URL properties to a new object
-  // This is also needed for IE8 because the anchor loses its
+  // This is also needed for IE because the anchor loses its
   // properties when it's removed from the dom
   const details = {};
 
   for (let i = 0; i < props.length; i++) {
     details[props[i]] = a[props[i]];
   }
 
-  // IE9 adds the port to the host property unlike everyone else. If
+  // IE adds the port to the host property unlike everyone else. If
   // a port identifier is added for standard ports, strip it.
   if (details.protocol === 'http:') {
     details.host = details.host.replace(/:80$/, '');
   }
 
@@ -84,12 +71,13 @@ export const parseUrl = function(url) {
 
   if (!details.protocol) {
     details.protocol = window.location.protocol;
   }
 
-  if (addToBody) {
-    document.body.removeChild(div);
+  /* istanbul ignore if */
+  if (!details.host) {
+    details.host = window.location.host;
   }
 
   return details;
 };
 
diff --git a/test/unit/utils/url.test.js b/test/unit/utils/url.test.js
index 13cf5b15..e9ea0689 100644
--- a/test/unit/utils/url.test.js
+++ b/test/unit/utils/url.test.js
@@ -3,16 +3,13 @@ import document from 'global/document';
 import window from 'global/window';
 import * as Url from '../../../src/js/utils/url.js';
 
 QUnit.module('url');
 QUnit.test('should parse the details of a url correctly', function(assert) {
-  assert.equal(
-    Url.parseUrl('#').protocol,
-    window.location.protocol,
-    'parsed relative url protocol'
-  );
+  assert.equal(Url.parseUrl('#').protocol, window.location.protocol, 'parsed relative url protocol');
   assert.equal(Url.parseUrl('#').host, window.location.host, 'parsed relative url host');
+  assert.equal(Url.parseUrl('#foo').hash, '#foo', 'parsed relative url hash');
 
   assert.equal(Url.parseUrl('http://example.com').protocol, 'http:', 'parsed example url protocol');
   assert.equal(Url.parseUrl('http://example.com').hostname, 'example.com', 'parsed example url hostname');
 
   assert.equal(Url.parseUrl('http://example.com:1234').port, '1234', 'parsed example url port');

Vulnerable code:   let a = document.createElement('a');
  // IE8 (and 9?) Fix
  // ie8 doesn't parse the URL correctly until the anchor is actually
  // added to the body, and an innerHTML is needed to trigger the parsing
  const addToBody = (a.host === '' && a.protocol !== 'file:');
  let div;

  if (addToBody) {
    div = document.createElement('div');
    div.innerHTML = `<a href="${url}"></a>`;
    a = div.firstChild;
    // prevent the div from affecting layout
    div.setAttribute('style', 'display:none; position:absolute;');
    document.body.appendChild(div);
  }

  // This is also needed for IE8 because the anchor loses its
  // IE9 adds the port to the host property unlike everyone else. If
  if (addToBody) {
    document.body.removeChild(div);
  assert.equal(
    Url.parseUrl('#').protocol,
    window.location.protocol,
    'parsed relative url protocol'
  );
Fixed code:   // This entire method can be replace with URL once we are able to drop IE11

  const a = document.createElement('a');
  // This is also needed for IE because the anchor loses its
  // IE adds the port to the host property unlike everyone else. If
  /* istanbul ignore if */
  if (!details.host) {
    details.host = window.location.host;
  assert.equal(Url.parseUrl('#').protocol, window.location.protocol, 'parsed relative url protocol');
  assert.equal(Url.parseUrl('#foo').hash, '#foo', 'parsed relative url hash');