CVE-2022-39280: Inefficient Regular Expression Complexity (CWE-1333)
value: dparse is a parser for Python dependency files. dparse in versions before 0.5.2 contain a regular expression that is vulnerable to a Regular Expression Denial of Service. All the users parsing index server URLs with dparse are impacted by this vulnerability. A patch has been applied in version `0.5.2`, all the users are advised to upgrade to `0.5.2` as soon as possible. Users unable to upgrade should avoid passing index server URLs in the source file to be parsed.
CWE description: The product uses a regular expression with an inefficient, possibly exponential worst-case computational complexity that consumes excessive CPU cycles.
CVSS v3 base score: 7.5
Published: 2022-10-06T18:16Z
Repository: https://github.com/pyupio/dparse
Language: Python
Files changed: dparse/parser.py, dparse/regex.py
Commit date: 2022-08-04 15:55:11 -0500
Commit message: Removing index server validation

Diff: diff --git a/dparse/parser.py b/dparse/parser.py
index 340b612..64ee803 100644
--- a/dparse/parser.py
+++ b/dparse/parser.py
@@ -7,11 +7,11 @@ import yaml
 from io import StringIO
 
 from configparser import ConfigParser, NoOptionError
 
 
-from .regex import URL_REGEX, HASH_REGEX
+from .regex import HASH_REGEX
 
 from .dependencies import DependencyFile, Dependency
 from packaging.requirements import Requirement as PackagingRequirement, InvalidRequirement
 from . import filetypes
 import toml
@@ -174,14 +174,15 @@ class Parser(object):
         """
 
         :param line:
         :return:
         """
-        matches = URL_REGEX.findall(line)
-        if matches:
-            url = matches[0]
-            return url if url.endswith("/") else url + "/"
+        groups = re.split(pattern="[=\s]+", string=line.strip(), maxsplit=100)
+
+        if len(groups) >= 2:
+            return groups[1] if groups[1].endswith("/") else groups[1] + "/"
+
         return None
 
     @classmethod
     def resolve_file(cls, file_path, line):
         """
@@ -344,10 +345,11 @@ class PipfileParser(Parser):
                                 )
                             )
         except (toml.TomlDecodeError, IndexError) as e:
             pass
 
+
 class PipfileLockParser(Parser):
 
     def parse(self):
         """
         Parse a Pipfile.lock (as seen in pipenv)
diff --git a/dparse/regex.py b/dparse/regex.py
index 40cc409..4a1204e 100644
--- a/dparse/regex.py
+++ b/dparse/regex.py
@@ -1,39 +1,4 @@
 # -*- coding: utf-8 -*-
 from __future__ import absolute_import, print_function, unicode_literals
 
-import re
-# see https://gist.github.com/dperini/729294
-URL_REGEX = re.compile(
-    # protocol identifier
-    "(?:(?:https?|ftp)://)"
-    # user:pass authentication
-    "(?:\S+(?::\S*)?@)?"
-    "(?:"
-    # IP address exclusion
-    # private & local networks
-    "(?!(?:10|127)(?:\.\d{1,3}){3})"
-    "(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})"
-    "(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})"
-    # IP address dotted notation octets
-    # excludes loopback network 0.0.0.0
-    # excludes reserved space >= 224.0.0.0
-    # excludes network & broadcast addresses
-    # (first & last IP address of each class)
-    "(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])"
-    "(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}"
-    "(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))"
-    "|"
-    # host name
-    "(?:(?:[a-z\u00a1-\uffff0-9]-?)*[a-z\u00a1-\uffff0-9]+)"
-    # domain name
-    "(?:\.(?:[a-z\u00a1-\uffff0-9]-?)*[a-z\u00a1-\uffff0-9]+)*"
-    # TLD identifier
-    "(?:\.(?:[a-z\u00a1-\uffff]{2,}))"
-    ")"
-    # port number
-    "(?::\d{2,5})?"
-    # resource path
-    "(?:/\S*)?",
-    re.UNICODE)
-
-HASH_REGEX = r"--hash[=| ][\w]+:[\w]+"
+HASH_REGEX = r"--hash[=| ]\w+:\w+"

Vulnerable code: from .regex import URL_REGEX, HASH_REGEX
        matches = URL_REGEX.findall(line)
        if matches:
            url = matches[0]
            return url if url.endswith("/") else url + "/"
import re
# see https://gist.github.com/dperini/729294
URL_REGEX = re.compile(
    # protocol identifier
    "(?:(?:https?|ftp)://)"
    # user:pass authentication
    "(?:\S+(?::\S*)?@)?"
    "(?:"
    # IP address exclusion
    # private & local networks
    "(?!(?:10|127)(?:\.\d{1,3}){3})"
    "(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})"
    "(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})"
    # IP address dotted notation octets
    # excludes loopback network 0.0.0.0
    # excludes reserved space >= 224.0.0.0
    # excludes network & broadcast addresses
    # (first & last IP address of each class)
    "(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])"
    "(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}"
    "(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))"
    "|"
    # host name
    "(?:(?:[a-z\u00a1-\uffff0-9]-?)*[a-z\u00a1-\uffff0-9]+)"
    # domain name
    "(?:\.(?:[a-z\u00a1-\uffff0-9]-?)*[a-z\u00a1-\uffff0-9]+)*"
    # TLD identifier
    "(?:\.(?:[a-z\u00a1-\uffff]{2,}))"
    ")"
    # port number
    "(?::\d{2,5})?"
    # resource path
    "(?:/\S*)?",
    re.UNICODE)

HASH_REGEX = r"--hash[=| ][\w]+:[\w]+"
Fixed code: from .regex import HASH_REGEX
        groups = re.split(pattern="[=\s]+", string=line.strip(), maxsplit=100)

        if len(groups) >= 2:
            return groups[1] if groups[1].endswith("/") else groups[1] + "/"


HASH_REGEX = r"--hash[=| ]\w+:\w+"