CVE-2022-0624 (JavaScript, published 2022-06-28T09:15Z)
Description: value: Authorization Bypass Through User-Controlled Key in GitHub repository ionicabizau/parse-path prior to 5.0.0.
Severity: HIGH (CVSS v3 base score: 7.3)
Weakness: CWE-639 \u2014 Authorization Bypass Through User-Controlled Key
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.
Repository: https://github.com/ionicabizau/parse-path
Files changed:
lib/index.js
package.json
test/index.js
Commit message: Refactor the codebase to make it more secure


Vulnerable code:
// Dependencies
    , isSsh = require("is-ssh")
    , qs = require("query-string")
    ;
    url = (url || "").trim().replace(/\r?\n|\r/gm, "")
    var output = {
            protocols: protocols(url)
          , protocol: null
          , port: null
          , resource: ""
          , user: ""
          , pathname: ""
          , hash: ""
          , search: ""
          , href: url
          , query: Object.create(null)
        }
      , protocolIndex = url.indexOf("://")
      , resourceIndex = -1
      , splits = null
      , parts = null
      ;
    if (url.startsWith(".")) {
        if (url.startsWith("./")) {
            url = url.substring(2);
        }
        output.pathname = url;
        output.protocol = "file";
    const firstChar = url.charAt(1)
    if (!output.protocol) {
        if (!output.protocol) {
            if (isSsh(url)) {
                output.protocol = "ssh"
            } else if (firstChar === "/" || firstChar === "~") {
                url = url.substring(2)
                output.protocol = "file"
            } else {
                output.protocol = "file"
            }
        }
    }

    if (protocolIndex !== -1) {
        url = url.substring(protocolIndex + 3);
    }

    parts = url.split(/\/|\\/);
    if (output.protocol !== "file") {
        output.resource = parts.shift();
    } else {
        output.resource = "";
    }

    // user@domain
    splits = output.resource.split("@");
    if (splits.length === 2) {
        output.user = splits[0];
        output.resource = splits[1];
    }


    // domain.com:port
    splits = output.resource.split(":");
    if (splits.length === 2) {
        output.resource = splits[0];
        const port = splits[1];
        if (port) {
            output.port = Number(port);
            if (isNaN(output.port) || port.match(/^\d+$/) === null) {
                output.port = null;
                parts.unshift(port);
            }
        } else {
            output.port = null
        }
    }

    // Remove empty elements
    parts = parts.filter(Boolean);

    // Stringify the pathname
    if (output.protocol === "file") {
        output.pathname = output.href
    } else {
      output.pathname = output.pathname || ((output.protocol !== "file" || output.href[0] === "/" ? "/" : "") + parts.join("/"));
    }

    // #some-hash
    splits = output.pathname.split("#");
    if (splits.length === 2) {
        output.pathname = splits[0];
        output.hash = splits[1];
    }

    // ?foo=bar
    splits = output.pathname.split("?");
    if (splits.length === 2) {
        output.pathname = splits[0];
        output.search = splits[1];
    output.query = qs.parse(output.search);
    output.href = output.href.replace(/\/$/, "")
    output.pathname = output.pathname.replace(/\/$/, "")
}
    , qs = require("querystring")
          , port: null
          , port: null
          , port: null
          , port: null
          , port: null
            protocols: []
          , protocol: "ssh"
          , port: null
          , resource: "github.com"
          , user: "git"
          , pathname: "/IonicaBizau/git-stats.git"
            protocols: []
          , port: null
          , pathname: "/path/to/file.png"
            protocols: []
          , port: null
          , pathname: "./path/to/file.png"
            protocols: []
          , port: null
          , pathname: "./.path/to/file.png"
            protocols: []
          , port: null
          , pathname: ".path/to/file.png"
            protocols: []
          , port: null
          , pathname: "path/to/file.png"
          protocols: []
        , protocol: "ssh"
        , port: null
        , resource: "github.com"
        , user: "git"
        , pathname: "/9IonicaBizau/git-stats.git"
          protocols: []
        , protocol: "ssh"
        , port: null
        , resource: "github.com"
        , user: "git"
        , pathname: "/0xABC/git-stats.git"
        , port: null
        , port: null
            c[1].href = c[0].trim().replace(/\r?\n|\r/gm, "");
            c[1].query = qs.parse(c[1].search)
            test.expect(parseUrl(c[0])).toEqual(c[1]);

Fixed code:
    const output = {
        protocols: []
      , protocol: null
      , port: null
      , resource: ""
      , user: ""
      , password: ""
      , pathname: ""
      , hash: ""
      , search: ""
      , href: url
      , query: {}
    try {
        const parsed = new URL(url)
        output.protocols = protocols(parsed)
        output.port = parsed.port
        output.resource =  parsed.host
        output.user = parsed.username || ""
        output.password = parsed.password || ""
        output.pathname = parsed.pathname
        output.hash = parsed.hash.slice(1)
        output.search = parsed.search.slice(1)
        output.href = parsed.href
        output.query = Object.fromEntries(parsed.searchParams)
    } catch (e) {
        // TODO Maybe check if it is a valid local file path
        //      In any case, these will be parsed by higher
        //      level parsers such as parse-url, git-url-parse, git-up
        output.protocols = ["file"]
        output.protocol = output.protocols[0]
        output.port = ""
        output.resource =  ""
        output.user = ""
        output.pathname = ""
        output.hash = ""
        output.search = ""
        output.href = url
        output.query = {}
}
          , port: ""
          , href: "http://ionicabizau.net/blog"
          , query: {}
          , port: ""
          , href: "http://ionicabizau.net/blog"
          , query: {}
          , port: ""
          , query: { foo: "bar", bar: "42" }
          , href: "http://domain.com/path/name?foo=bar&bar=42#some-hash"
          , port: ""
          , query: {}
          , href: "http://domain.com/path/name#some-hash?foo=bar&bar=42"
          , port: ""
          , query: {}
          , href: "git+ssh://git@host.xz/path/name.git"
        // NOTE:parse-path will look at this as a local path
        //      For parsing it as url, please use parse-url
            protocols: ["file"]
          , protocol: "file"
          , port: ""
          , resource: ""
          , user: ""
          , pathname: ""
          , query: {}
          , href: "git@github.com:IonicaBizau/git-stats.git"
            protocols: ["file"]
          , port: ""
          , pathname: ""
          , query: {}
          , href: "/path/to/file.png"
            protocols: ["file"]
          , port: ""
          , pathname: ""
          , query: {}
          , href: "./path/to/file.png"
            protocols: ["file"]
          , port: ""
          , pathname: ""
          , query: {}
          , href: "./.path/to/file.png"
            protocols: ["file"]
          , port: ""
          , pathname: ""
          , query: {}
          , href: ".path/to/file.png"
            protocols: ["file"]
          , port: ""
          , pathname: ""
          , query: {}
          , href: "path/to/file.png"
          protocols: ["file"]
        , protocol: "file"
        , port: ""
        , resource: ""
        , user: ""
        , pathname: ""
        , query: {}
        , href: "git@github.com:9IonicaBizau/git-stats.git"
          protocols: ["file"]
        , protocol: "file"
        , port: ""
        , resource: ""
        , user: ""
        , pathname: ""
        , query: {}
        , href: "git@github.com:0xABC/git-stats.git"
        , port: ""
        , href: "https://attacker.com/@example.com"
        , query: {}
        , port: ""
        , href: "javascript://%0aalert(1)"
        , query: {}
            const cParsed = parseUrl(c[0])
            debugger
            test.expect(cParsed).toEqual(c[1]);