CVE-2021-3664 (JavaScript, published 2021-07-26T12:15Z)
Description: value: url-parse is vulnerable to URL Redirection to Untrusted Site
Severity: MEDIUM (CVSS v3 base score: 5.3)
Weakness: CWE-601 \u2014 URL Redirection to Untrusted Site ('Open Redirect')
A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This simplifies phishing attacks.
Repository: https://github.com/unshiftio/url-parse
Files changed:
index.js
test/test.js
Commit message: [fix] Ignore slashes after the protocol for special URLs

Fixes #205
Fixes #206


Vulnerable code:
function extractProtocol(address) {
  var match = protocolre.exec(address)
    , protocol = match[1] ? match[1].toLowerCase() : ''
    , slashes = !!(match[2] && match[2].length >= 2)
    , rest =  match[2] && match[2].length === 1 ? '/' + match[3] : match[3];
  extracted = extractProtocol(address || '');
  if (!extracted.slashes || url.protocol === 'file:') {
        rest: 'foo/bar'
  it('does not see a slash after the protocol as path', function () {
      var slashes = ['', '/', '//', '///', '////', '/////'];

Fixed code:
/**
 * Check whether a protocol scheme is special.
 *
 * @param {String} The protocol scheme of the URL
 * @return {Boolean} `true` if the protocol scheme is special, else `false`
 * @private
 */
function isSpecial(scheme) {
  return (
    scheme === 'file:' ||
    scheme === 'ftp:' ||
    scheme === 'http:' ||
    scheme === 'https:' ||
    scheme === 'ws:' ||
    scheme === 'wss:'
  );
}

 * @param {Object} location
function extractProtocol(address, location) {
  location = location || {};
  var match = protocolre.exec(address);
  var protocol = match[1] ? match[1].toLowerCase() : '';
  var rest = match[2] ? match[2] + match[3] : match[3];
  var slashes = !!(match[2] && match[2].length >= 2);

  if (protocol === 'file:') {
    if (slashes) {
      rest = rest.slice(2);
    }
  } else if (isSpecial(protocol)) {
    rest = match[3];
  } else if (protocol) {
    if (rest.indexOf('//') === 0) {
      rest = rest.slice(2);
    }
  } else if (slashes && location.hostname) {
    rest = match[3];
  }
  extracted = extractProtocol(address || '', location);
  if (
    url.protocol === 'file:' ||
    (!extracted.slashes && !isSpecial(extracted.protocol))
  ) {
        rest: '//foo/bar'
  it('ignores slashes after the protocol for special URLs', function () {
    parsed = parse(url);

    url = 'https:/github.com/foo/bar';
    parsed = parse(url);
    assume(parsed.host).equals('github.com');
    assume(parsed.pathname).equals('/foo/bar');

    url = 'https:\\github.com/foo/bar';
    parsed = parse(url);
    assume(parsed.host).equals('github.com');
    assume(parsed.pathname).equals('/foo/bar');

    url = 'https:github.com/foo/bar';
    parsed = parse(url);
    assume(parsed.host).equals('github.com');
    assume(parsed.pathname).equals('/foo/bar');

    url = 'https:github.com/foo/bar';
    parsed = parse(url);
    assume(parsed.host).equals('github.com');
    assume(parsed.pathname).equals('/foo/bar');
  it('handles slashes after the protocol for non special URLs', function () {
    var url = 'foo:example.com'
      , parsed = parse(url);

    assume(parsed.hostname).equals('');
    assume(parsed.pathname).equals('example.com');
    assume(parsed.href).equals('foo:example.com');

    url = 'foo:/example.com';
    parsed = parse(url);
    assume(parsed.hostname).equals('');
    assume(parsed.pathname).equals('/example.com');
    assume(parsed.href).equals('foo:/example.com');

    url = 'foo://example.com';
    parsed = parse(url);
    assume(parsed.hostname).equals('example.com');
    assume(parsed.pathname).equals('/');
    assume(parsed.href).equals('foo://example.com/');

    url = 'foo:///example.com';
    parsed = parse(url);
    assume(parsed.hostname).equals('');
    assume(parsed.pathname).equals('/example.com');
    assume(parsed.href).equals('foo:///example.com');
  })

      var slashes = ['', '/', '//', '///'];
      url = 'file:////';
      data = parse(url);
      assume(data.protocol).equals('file:');
      assume(data.pathname).equals('//');
      assume(data.href).equals(url);

      url = 'file://///';
      data = parse(url);
      assume(data.protocol).equals('file:');
      assume(data.pathname).equals('///');
      assume(data.href).equals(url);