CVE-2022-0613: Authorization Bypass Through User-Controlled Key (CWE-639)
value: Authorization Bypass Through User-Controlled Key in NPM urijs prior to 1.19.8.
CWE description: The system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying the key value identifying the data.
CVSS v3 base score: 6.5
Published: 2022-02-16T09:15Z
Repository: https://github.com/medialize/uri.js
Language: JavaScript
Files changed: src/URI.js, src/URI.min.js, test/urls.js
Commit date: 2022-02-15 09:19:40 +0100
Commit message: fix(parse): case insensitive scheme - #412

Diff: diff --git a/src/URI.js b/src/URI.js
index 6505698..b6cdaf9 100644
--- a/src/URI.js
+++ b/src/URI.js
@@ -511,11 +511,11 @@
       parts.query = string.substring(pos + 1) || null;
       string = string.substring(0, pos);
     }
 
     // slashes and backslashes have lost all meaning for the web protocols (https, http, wss, ws)
-    string = string.replace(/^(https?|ftp|wss?)?:[/\\]*/, '$1://');
+    string = string.replace(/^(https?|ftp|wss?)?:[/\\]*/i, '$1://');
 
     // extract protocol
     if (string.substring(0, 2) === '//') {
       // relative-scheme
       parts.protocol = null;
diff --git a/src/URI.min.js b/src/URI.min.js
index 4a63660..e1ae1c4 100644
--- a/src/URI.min.js
+++ b/src/URI.min.js
@@ -33,11 +33,11 @@ d.find_uri_expression=/\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-
 gopher:"70",ws:"80",wss:"443"};d.hostProtocols=["http","https"];d.invalid_hostname_characters=/[^a-zA-Z0-9\.\-:_]/;d.domAttributes={a:"href",blockquote:"cite",link:"href",base:"href",script:"src",form:"action",img:"src",area:"href",iframe:"src",embed:"src",source:"src",track:"src",input:"src",audio:"src",video:"src"};d.getDomAttribute=function(a){if(a&&a.nodeName){var b=a.nodeName.toLowerCase();if("input"!==b||"image"===a.type)return d.domAttributes[b]}};d.encode=F;d.decode=decodeURIComponent;d.iso8859=
 function(){d.encode=escape;d.decode=unescape};d.unicode=function(){d.encode=F;d.decode=decodeURIComponent};d.characters={pathname:{encode:{expression:/%(24|26|2B|2C|3B|3D|3A|40)/ig,map:{"%24":"$","%26":"&","%2B":"+","%2C":",","%3B":";","%3D":"=","%3A":":","%40":"@"}},decode:{expression:/[\/\?#]/g,map:{"/":"%2F","?":"%3F","#":"%23"}}},reserved:{encode:{expression:/%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig,map:{"%3A":":","%2F":"/","%3F":"?","%23":"#","%5B":"[","%5D":"]","%40":"@",
 "%21":"!","%24":"$","%26":"&","%27":"'","%28":"(","%29":")","%2A":"*","%2B":"+","%2C":",","%3B":";","%3D":"="}}},urnpath:{encode:{expression:/%(21|24|27|28|29|2A|2B|2C|3B|3D|40)/ig,map:{"%21":"!","%24":"$","%27":"'","%28":"(","%29":")","%2A":"*","%2B":"+","%2C":",","%3B":";","%3D":"=","%40":"@"}},decode:{expression:/[\/\?#:]/g,map:{"/":"%2F","?":"%3F","#":"%23",":":"%3A"}}}};d.encodeQuery=function(a,b){var c=d.encode(a+"");void 0===b&&(b=d.escapeQuerySpace);return b?c.replace(/%20/g,"+"):c};d.decodeQuery=
 function(a,b){a+="";void 0===b&&(b=d.escapeQuerySpace);try{return d.decode(b?a.replace(/\+/g,"%20"):a)}catch(c){return a}};var G={encode:"encode",decode:"decode"},l,t=function(a,b){return function(c){try{return d[b](c+"").replace(d.characters[a][b].expression,function(e){return d.characters[a][b].map[e]})}catch(e){return c}}};for(l in G)d[l+"PathSegment"]=t("pathname",G[l]),d[l+"UrnPathSegment"]=t("urnpath",G[l]);G=function(a,b,c){return function(e){var f=c?function(I){return d[b](d[c](I))}:d[b];
 e=(e+"").split(a);for(var n=0,z=e.length;n<z;n++)e[n]=f(e[n]);return e.join(a)}};d.decodePath=G("/","decodePathSegment");d.decodeUrnPath=G(":","decodeUrnPathSegment");d.recodePath=G("/","encodePathSegment","decode");d.recodeUrnPath=G(":","encodeUrnPathSegment","decode");d.encodeReserved=t("reserved","encode");d.parse=function(a,b){b||(b={preventInvalidHostname:d.preventInvalidHostname});var c=a.indexOf("#");-1<c&&(b.fragment=a.substring(c+1)||null,a=a.substring(0,c));c=a.indexOf("?");-1<c&&(b.query=
-a.substring(c+1)||null,a=a.substring(0,c));a=a.replace(/^(https?|ftp|wss?)?:[/\\]*/,"$1://");"//"===a.substring(0,2)?(b.protocol=null,a=a.substring(2),a=d.parseAuthority(a,b)):(c=a.indexOf(":"),-1<c&&(b.protocol=a.substring(0,c)||null,b.protocol&&!b.protocol.match(d.protocol_expression)?b.protocol=void 0:"//"===a.substring(c+1,c+3).replace(/\\/g,"/")?(a=a.substring(c+3),a=d.parseAuthority(a,b)):(a=a.substring(c+1),b.urn=!0)));b.path=a;return b};d.parseHost=function(a,b){a||(a="");a=a.replace(/\\/g,
+a.substring(c+1)||null,a=a.substring(0,c));a=a.replace(/^(https?|ftp|wss?)?:[/\\]*/i,"$1://");"//"===a.substring(0,2)?(b.protocol=null,a=a.substring(2),a=d.parseAuthority(a,b)):(c=a.indexOf(":"),-1<c&&(b.protocol=a.substring(0,c)||null,b.protocol&&!b.protocol.match(d.protocol_expression)?b.protocol=void 0:"//"===a.substring(c+1,c+3).replace(/\\/g,"/")?(a=a.substring(c+3),a=d.parseAuthority(a,b)):(a=a.substring(c+1),b.urn=!0)));b.path=a;return b};d.parseHost=function(a,b){a||(a="");a=a.replace(/\\/g,
 "/");var c=a.indexOf("/");-1===c&&(c=a.length);if("["===a.charAt(0)){var e=a.indexOf("]");b.hostname=a.substring(1,e)||null;b.port=a.substring(e+2,c)||null;"/"===b.port&&(b.port=null)}else{var f=a.indexOf(":");e=a.indexOf("/");f=a.indexOf(":",f+1);-1!==f&&(-1===e||f<e)?(b.hostname=a.substring(0,c)||null,b.port=null):(e=a.substring(0,c).split(":"),b.hostname=e[0]||null,b.port=e[1]||null)}b.hostname&&"/"!==a.substring(c).charAt(0)&&(c++,a="/"+a);b.preventInvalidHostname&&d.ensureValidHostname(b.hostname,
 b.protocol);b.port&&d.ensureValidPort(b.port);return a.substring(c)||"/"};d.parseAuthority=function(a,b){a=d.parseUserinfo(a,b);return d.parseHost(a,b)};d.parseUserinfo=function(a,b){var c=a;-1!==a.indexOf("\\")&&(a=a.replace(/\\/g,"/"));var e=a.indexOf("/"),f=a.lastIndexOf("@",-1<e?e:a.length-1);-1<f&&(-1===e||f<e)?(e=a.substring(0,f).split(":"),b.username=e[0]?d.decode(e[0]):null,e.shift(),b.password=e[0]?d.decode(e.join(":")):null,a=c.substring(f+1)):(b.username=null,b.password=null);return a};
 d.parseQuery=function(a,b){if(!a)return{};a=a.replace(/&+/g,"&").replace(/^\?*&*|&+$/g,"");if(!a)return{};for(var c={},e=a.split("&"),f=e.length,n,z,I=0;I<f;I++)if(n=e[I].split("="),z=d.decodeQuery(n.shift(),b),n=n.length?d.decodeQuery(n.join("="),b):null,"__proto__"!==z)if(B.call(c,z)){if("string"===typeof c[z]||null===c[z])c[z]=[c[z]];c[z].push(n)}else c[z]=n;return c};d.build=function(a){var b="",c=!1;a.protocol&&(b+=a.protocol+":");a.urn||!b&&!a.hostname||(b+="//",c=!0);b+=d.buildAuthority(a)||
 "";"string"===typeof a.path&&("/"!==a.path.charAt(0)&&c&&(b+="/"),b+=a.path);"string"===typeof a.query&&a.query&&(b+="?"+a.query);"string"===typeof a.fragment&&a.fragment&&(b+="#"+a.fragment);return b};d.buildHost=function(a){var b="";if(a.hostname)b=d.ip6_expression.test(a.hostname)?b+("["+a.hostname+"]"):b+a.hostname;else return"";a.port&&(b+=":"+a.port);return b};d.buildAuthority=function(a){return d.buildUserinfo(a)+d.buildHost(a)};d.buildUserinfo=function(a){var b="";a.username&&(b+=d.encode(a.username));
 a.password&&(b+=":"+d.encode(a.password));b&&(b+="@");return b};d.buildQuery=function(a,b,c){var e="",f,n;for(f in a)if("__proto__"!==f&&B.call(a,f))if(A(a[f])){var z={};var I=0;for(n=a[f].length;I<n;I++)void 0!==a[f][I]&&void 0===z[a[f][I]+""]&&(e+="&"+d.buildQueryParameter(f,a[f][I],c),!0!==b&&(z[a[f][I]+""]=!0))}else void 0!==a[f]&&(e+="&"+d.buildQueryParameter(f,a[f],c));return e.substring(1)};d.buildQueryParameter=function(a,b,c){return d.encodeQuery(a,c)+(null!==b?"="+d.encodeQuery(b,c):"")};
diff --git a/test/urls.js b/test/urls.js
index efb192f..839f0da 100644
--- a/test/urls.js
+++ b/test/urls.js
@@ -2325,10 +2325,59 @@ var urls = [{
         ip4: false,
         ip6: false,
         idn: false,
         punycode: false
       }
+    }, {
+      name: 'excessive slash protocol https case-insensitive',
+      url: 'hTTps://////attacker.com',
+      _url: 'hTTps://attacker.com/',
+      parts: {
+        protocol: 'hTTps',
+        username: null,
+        password: null,
+        hostname: 'attacker.com',
+        port: null,
+        path: '/',
+        query: null,
+        fragment: null
+      },
+      accessors: {
+        protocol: 'hTTps',
+        username: '',
+        password: '',
+        port: '',
+        path: '/',
+        query: '',
+        fragment: '',
+        resource: '/',
+        authority: 'attacker.com',
+        origin: 'hTTps://attacker.com',
+        userinfo: '',
+        subdomain: '',
+        domain: 'attacker.com',
+        tld: 'com',
+        directory: '/',
+        filename: '',
+        suffix: '',
+        hash: '',
+        search: '',
+        host: 'attacker.com',
+        hostname: 'attacker.com'
+      },
+      is: {
+        urn: false,
+        url: true,
+        relative: false,
+        name: true,
+        sld: false,
+        ip: false,
+        ip4: false,
+        ip6: false,
+        idn: false,
+        punycode: false
+      }
     }, {
       name: 'no slash protocol ftp',
       url: 'ftp:attacker.com',
       _url: 'ftp://attacker.com/',
       parts: {

Vulnerable code:     string = string.replace(/^(https?|ftp|wss?)?:[/\\]*/, '$1://');
a.substring(c+1)||null,a=a.substring(0,c));a=a.replace(/^(https?|ftp|wss?)?:[/\\]*/,"$1://");"//"===a.substring(0,2)?(b.protocol=null,a=a.substring(2),a=d.parseAuthority(a,b)):(c=a.indexOf(":"),-1<c&&(b.protocol=a.substring(0,c)||null,b.protocol&&!b.protocol.match(d.protocol_expression)?b.protocol=void 0:"//"===a.substring(c+1,c+3).replace(/\\/g,"/")?(a=a.substring(c+3),a=d.parseAuthority(a,b)):(a=a.substring(c+1),b.urn=!0)));b.path=a;return b};d.parseHost=function(a,b){a||(a="");a=a.replace(/\\/g,
Fixed code:     string = string.replace(/^(https?|ftp|wss?)?:[/\\]*/i, '$1://');
a.substring(c+1)||null,a=a.substring(0,c));a=a.replace(/^(https?|ftp|wss?)?:[/\\]*/i,"$1://");"//"===a.substring(0,2)?(b.protocol=null,a=a.substring(2),a=d.parseAuthority(a,b)):(c=a.indexOf(":"),-1<c&&(b.protocol=a.substring(0,c)||null,b.protocol&&!b.protocol.match(d.protocol_expression)?b.protocol=void 0:"//"===a.substring(c+1,c+3).replace(/\\/g,"/")?(a=a.substring(c+3),a=d.parseAuthority(a,b)):(a=a.substring(c+1),b.urn=!0)));b.path=a;return b};d.parseHost=function(a,b){a||(a="");a=a.replace(/\\/g,
    }, {
      name: 'excessive slash protocol https case-insensitive',
      url: 'hTTps://////attacker.com',
      _url: 'hTTps://attacker.com/',
      parts: {
        protocol: 'hTTps',
        username: null,
        password: null,
        hostname: 'attacker.com',
        port: null,
        path: '/',
        query: null,
        fragment: null
      },
      accessors: {
        protocol: 'hTTps',
        username: '',
        password: '',
        port: '',
        path: '/',
        query: '',
        fragment: '',
        resource: '/',
        authority: 'attacker.com',
        origin: 'hTTps://attacker.com',
        userinfo: '',
        subdomain: '',
        domain: 'attacker.com',
        tld: 'com',
        directory: '/',
        filename: '',
        suffix: '',
        hash: '',
        search: '',
        host: 'attacker.com',
        hostname: 'attacker.com'
      },
      is: {
        urn: false,
        url: true,
        relative: false,
        name: true,
        sld: false,
        ip: false,
        ip4: false,
        ip6: false,
        idn: false,
        punycode: false
      }