diff --git a/cvss/cvss4.py b/cvss/cvss4.py index bd31b5d..a6e4c0e 100644 --- a/cvss/cvss4.py +++ b/cvss/cvss4.py @@ -608,6 +608,32 @@ def compute_severity(self): else: self.severity = "Critical" + def scores(self): + """ + Returns computed base score as tuple for backwards compatibility. + + Returns: + (tuple of floats): Base Score + """ + return (self.base_score,) + + def severities(self): + """ + Returns severities based on base score as tuple for backwards compatibility. + + Returns: + (tuple): Base Severity as string + """ + return (self.severity,) + + def rh_vector(self): + """ + Returns cleaned vector with score in Red Hat notation, e.g. score/vector. + + Example: 7.3/CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:A + """ + return str(self.base_score) + "/" + self.clean_vector() + def as_json(self, sort=False, minimal=False): """ Returns a dictionary formatted with attribute names and values defined by the official diff --git a/cvss/cvss_calculator.py b/cvss/cvss_calculator.py index c91e14f..18e46e0 100755 --- a/cvss/cvss_calculator.py +++ b/cvss/cvss_calculator.py @@ -12,16 +12,24 @@ import argparse import json -from cvss import CVSSError, ask_interactively +from cvss import CVSS2, CVSS3, CVSS4, CVSSError, ask_interactively PAD = 24 # string padding for score names +DEFAULT_VERSION = 3.1 def main(): try: parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("-2", action="store_true", help="compute CVSS2 instead") - parser.add_argument("-3", action="store_true", help="compute CVSS3.0 instead") + parser.add_argument( + "-2", action="store_true", help="compute CVSS2 (default {0})".format(DEFAULT_VERSION) + ) + parser.add_argument( + "-3", action="store_true", help="compute CVSS3.0 (default {0})".format(DEFAULT_VERSION) + ) + parser.add_argument( + "-4", action="store_true", help="compute CVSS4.0 (default {0})".format(DEFAULT_VERSION) + ) parser.add_argument("-a", "--all", action="store_true", help="ask for all metrics") parser.add_argument("-v", "--vector", help="input string with CVSS vector") parser.add_argument( @@ -32,17 +40,12 @@ def main(): ) args = parser.parse_args() - # Import the correct CVSS module - if getattr(args, "2"): - version = 2 - from cvss import CVSS2 as CVSS - else: - if getattr(args, "3"): - version = 3.0 - else: - version = 3.1 - from cvss import CVSS3 as CVSS - + version_mapping = {"2": 2, "3": 3.0, "3.1": 3.1, "4": 4.0} + # Find the key in args where the value is True + true_version_key = next((key for key, value in args.__dict__.items() if value), None) + # Use the found key to get the version from version_mapping, + # default to DEFAULT_VERSION if not found. + version = version_mapping.get(true_version_key, DEFAULT_VERSION) # Vector input, either from command line or interactively if args.vector: vector_string = args.vector @@ -51,27 +54,43 @@ def main(): # Compute scores and clean vector try: - cvss_vector = CVSS(vector_string) + # Init the correct CVSS module + if version == 2: + cvss_vector = CVSS2(vector_string) + elif 3.0 <= version < 4.0: + cvss_vector = CVSS3(vector_string) + elif version == 4.0: + cvss_vector = CVSS4(vector_string) + else: + raise CVSSError("Unknown version: {0}".format(version)) except CVSSError as e: print(e) else: scores = cvss_vector.scores() + severities = None if version == 2: print("CVSS2") - severities = None - elif version >= 3.0: + elif 3.0 <= version < 4.0: print("CVSS3") severities = cvss_vector.severities() + elif version >= 4.0: + print("CVSS4") + severities = cvss_vector.severities() else: raise ValueError("Unknown CVSS version: {0}".format(version)) for i, score_name in enumerate(["Base Score", "Temporal Score", "Environmental Score"]): - print(score_name + ":" + " " * (PAD - len(score_name) - 2), end="") - - if version >= 3.0: - print(scores[i], "({0})".format(severities[i])) - else: - print(scores[i]) + score = None + try: + if version >= 3.0: + score = scores[i], "({0})".format(severities[i]) + else: + score = (scores[i],) + except IndexError: + pass + if score: + print(score_name + ":" + " " * (PAD - len(score_name) - 2), end="") + print(*score) print("Cleaned vector: ", cvss_vector.clean_vector()) print("Red Hat vector: ", cvss_vector.rh_vector()) if args.json: diff --git a/cvss/interactive.py b/cvss/interactive.py index c5503e5..2040926 100644 --- a/cvss/interactive.py +++ b/cvss/interactive.py @@ -32,7 +32,7 @@ def ask_interactively(version=3.1, all_metrics=False, no_colors=False): Asks user to build CVSS vector string interactively. Args: - version (float): 2 or 3.0/3.1 for CVSS2 or CVSS3 respectively + version (float): 2 or 3.0/3.1 or 4 for CVSS2 or CVSS3 or CVSS4 respectively all_metrics (bool): If true, temporal and environmental metrics are asked, else only base metrics are asked for no_colors (bool): If true, terminal coloring is not used in interactive mode @@ -48,13 +48,20 @@ def ask_interactively(version=3.1, all_metrics=False, no_colors=False): METRICS_MANDATORY, METRICS_VALUE_NAMES, ) - elif version >= 3.0: + elif 3.0 <= version < 4.0: print("Interactive CVSS3 calculator") from .constants3 import ( METRICS_ABBREVIATIONS, METRICS_MANDATORY, METRICS_VALUE_NAMES, ) + elif version == 4.0: + print("Interactive CVSS4 calculator") + from .constants4 import ( + METRICS_ABBREVIATIONS, + METRICS_MANDATORY, + METRICS_VALUE_NAMES, + ) else: raise ValueError("Unknown version: {0}".format(version)) print() @@ -117,6 +124,8 @@ def ask_interactively(version=3.1, all_metrics=False, no_colors=False): vector_string = "CVSS:3.0/" + "/".join(vector) elif version == 3.1: vector_string = "CVSS:3.1/" + "/".join(vector) + elif version == 4.0: + vector_string = "CVSS:4.0/" + "/".join(vector) else: vector_string = "/".join(vector) return vector_string