CVE-2022-2900 (published 2022-09-14T11:15Z)

Server-Side Request Forgery (SSRF) in GitHub repository ionicabizau/parse-url prior to 8.1.0.

CVSS v3 base score: 9.1, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N)

Weakness information:
cwe_id: CWE-918
cwe_name: Server-Side Request Forgery (SSRF)
description: The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
extended_description: By providing URLs to unexpected hosts or ports, attackers can make it appear that the server is sending the request, possibly bypassing access controls such as firewalls that prevent the attackers from accessing the URLs directly. The server can be used as a proxy to conduct port scanning of hosts in internal networks, use other URLs such as that can access documents on the system (using file://), or use other protocols such as gopher:// or tftp://, which may provide greater control over the contents of requests.
         

Fix information:
repo_url: https://github.com/ionicabizau/parse-url
commit_details: repo_url: https://github.com/ionicabizau/parse-url
author: Ionică Bizău
author_date: 2022-08-03 11:22:27+03:00
msg: Throw if url is invalid. Add a length limit.
repository: repo_url: https://github.com/ionicabizau/parse-url
repo_name: IonicaBizau/parse-url
description: :rocket: An advanced url parser supporting git urls too.
repo_language: JavaScript
owner: ionicabizau
file_changes: filename: index.js
change_type: ModificationType.MODIFY
diff: @@ -32,18 +32,27 @@ import normalizeUrl from "normalize-url";
  *    - `search` (String): The url querystring value.
  *    - `href` (String): The input url.
  *    - `query` (Object): The url querystring, parsed as object.
+ *    - `parse_failed` (Boolean): Whether the parsing failed or not.
  */
 const parseUrl = (url, normalize = false) => {
 
     // Constants
     const GIT_RE = /(^(git@|http(s)?:\/\/)([\w\.\-@]+)(\/|:))(([\~,\.\w,\-,\_,\/]+)(.git){0,1}((\/){0,1}))/
 
-    if (typeof url !== "string" || !url.trim()) {
-        const err = new Error("Invalid url.")
+    const throwErr = msg => {
+        const err = new Error(msg)
         err.subject_url = url
         throw err
     }
 
+    if (typeof url !== "string" || !url.trim()) {
+        throwErr("Invalid url.")
+    }
+
+    if (url.length > parseUrl.MAX_INPUT_LENGTH) {
+        throwErr("Input exceeds maximum length. If needed, change the value of parseUrl.MAX_INPUT_LENGTH.")
+    }
+
     if (normalize) {
         if (typeof normalize !== "object") {
             normalize = {
@@ -56,7 +65,7 @@ const parseUrl = (url, normalize = false) => {
     const parsed = parsePath(url)
 
     // Potential git-ssh urls
-    if (parsed.protocol === "file") {
+    if (parsed.parse_failed) {
         const matched  = parsed.href.match(GIT_RE)
         if (matched) {
             parsed.protocols = ["ssh"]
@@ -65,10 +74,15 @@ const parseUrl = (url, normalize = false) => {
             parsed.host = matched[4]
             parsed.user = "git"
             parsed.pathname = `/${matched[6]}`
+            parsed.parse_failed = false
+        } else {
+            throwErr("URL parsing failed.")
         }
     }
 
     return parsed;
 }
 
+parseUrl.MAX_INPUT_LENGTH = 2048
+
 export default parseUrl;

programming_language: Scala
filename: index.js
change_type: ModificationType.MODIFY
diff: @@ -17,6 +17,7 @@ const INPUTS = [
           , hash: ""
           , search: ""
           , query: {}
+          , parse_failed: false
         }
     ]
   , [
@@ -32,6 +33,7 @@ const INPUTS = [
           , hash: ""
           , search: ""
           , query: {}
+          , parse_failed: false
         }
     ]
   , [
@@ -47,6 +49,7 @@ const INPUTS = [
           , hash: "some-hash?foo=bar"
           , search: ""
           , query: {}
+          , parse_failed: false
         }
     ]
   , [
@@ -62,6 +65,7 @@ const INPUTS = [
           , hash: ""
           , search: ""
           , query: {}
+          , parse_failed: false
         }
     ]
   , [
@@ -77,6 +81,7 @@ const INPUTS = [
           , hash: ""
           , search: ""
           , query: {}
+          , parse_failed: false
         }
     ]
   , [
@@ -92,6 +97,7 @@ const INPUTS = [
           , hash: ""
           , search: ""
           , query: {}
+          , parse_failed: false
         }
     ]
   , [
@@ -107,22 +113,24 @@ const INPUTS = [
           , hash: "http://a:1:1"
           , search: ""
           , query: {}
+          , parse_failed: false
         }
     ]
   , [
         ["git@github.my-enterprise.com:my-org/my-repo.git", false],
         {
             protocols: [ 'ssh' ]
-            , protocol: 'ssh'
-            , port: ''
-            , resource: 'github.my-enterprise.com'
-            , host: 'github.my-enterprise.com'
-            , user: 'git'
-            , password: ''
-            , pathname: '/my-org/my-repo.git'
-            , hash: ''
-            , search: ''
-            , query: {}
+          , protocol: 'ssh'
+          , port: ''
+          , resource: 'github.my-enterprise.com'
+          , host: 'github.my-enterprise.com'
+          , user: 'git'
+          , password: ''
+          , pathname: '/my-org/my-repo.git'
+          , hash: ''
+          , search: ''
+          , query: {}
+          , parse_failed: false
         }
     ]
   , [
@@ -138,6 +146,7 @@ const INPUTS = [
         , hash: ""
         , search: ""
         , query: {}
+        , parse_failed: false
       }
   ]
 ];
@@ -165,4 +174,17 @@ tester.describe("check urls", test => {
             parseUrl("")
         }).toThrow(/invalid url/i)
     })
+
+    test.should("throw if url is too long", () => {
+        parseUrl.MAX_INPUT_LENGTH = 10
+        test.expect(() => {
+            parseUrl("https://domain.com/")
+        }).toThrow(/input exceeds maximum length/i)
+    })
+
+    test.should("throw if url is invalid", () => {
+        test.expect(() => {
+            parseUrl("foo")
+        }).toThrow(/url parsing failed/i)
+    })
 });

programming_language: CoffeeScript