Source code for cvss_rescore.cvsslib
import json
import logging
from cvss.cvss3 import CVSS3
from rule_engine import Rule, SymbolResolutionError, RuleSyntaxError
from .manualVettingException import ManualVettingException
[docs]class CvssLib:
rules_actions = None
logger = logging.getLogger()
@classmethod
def __init__(cls, rules_file_path: str):
"""
:param rules_file_path: str
:rtype: object
"""
cls.rules_actions = cls.__get_rules_actions(
rules_file_path=rules_file_path)
[docs] @classmethod
def get_modified_cvss(cls,
record: dict,
original_vector_string: str) -> tuple:
"""
:param record: dict - This is a single vulnerability record from your json output file
:param original_vector_string: str
:return:
:rtype: tuple - modified_vector_string, modified_environmental_score, \
modified_severity, rules_applied
:exception: SymbolResolutionError will log to error if the path in your
custom rule cannot be found in the source json file
:exception: RuleSyntaxError will log to error if the rule you have
defined cannot be parsed.
:exception: ManualVettingException will be thrown if no rules were matched.
This can be caught in your parent script
"""
# convert the original vector string to a dict
original_vector_obj = cls.__str2dict(original_vector_string)
# check if the vector string contains `CVSS`
if original_vector_obj.get('CVSS') is None:
raise ValueError('CVSS vector string does not contain CVSS, manual vetting required')
else:
# if we have a CVSS object and the value is 3.x, use the CVSS3 class
# if we have a CVSS object and the value is 2.x, raise an error
if original_vector_obj['CVSS'].startswith('3'):
cvss_obj = CVSS3(original_vector_string)
elif original_vector_obj['CVSS'].startswith('2'):
raise ValueError(f'CVSS version {original_vector_obj["CVSS"]} is unsupported, manual vetting required')
results = []
rules_applied = []
result = False
for rule_action in cls.rules_actions:
try:
rule = Rule(rule_action['rule'])
result = rule.matches(record)
except SymbolResolutionError as srerr:
cls.logger.error(f'{srerr.message}. The {srerr.symbol_name} block in the rules_actions.json file '
f'was not found in the json record')
# if true, we need to update the vector string and return it.
except RuleSyntaxError as rserr:
cls.logger.error(f'{rserr.message}. The block value in the '
f'rules_actions.json file '
f'contains incorrect syntax.')
if result is True:
rules_applied.append({
"description": rule_action['description'],
"vector_changes": rule_action["vector_changes"]
})
for vector_change in rule_action["vector_changes"]:
cvss_obj.metrics[vector_change['vector']] = vector_change['value']
cvss_obj.compute_temporal_score()
cvss_obj.compute_environmental_score()
results.append(result)
if True not in results:
raise ManualVettingException("No rescore rules were matched by this record. Manual vetting is required!")
modified_vector_string = f'CVSS:3.{cvss_obj.minor_version}/{cls.__dict2str(cvss_obj.metrics)}'
modified_environmental_score = cvss_obj.environmental_score
modified_severity = cvss_obj.severities()
return modified_vector_string, modified_environmental_score, \
modified_severity, rules_applied
@classmethod
def __str2dict(cls, vector_string: str) -> dict:
vector_dict = {}
vector_parts = vector_string.split('/')
for vector_part in vector_parts:
key = vector_part.split(':')[0]
value = vector_part.split(':')[1]
vector_dict[key] = value
return vector_dict
@classmethod
def __dict2str(cls, vector_object: dict) -> str:
vector_string = ''
for key in vector_object.keys():
value = vector_object[key]
vector_part = f'{key}:{value}'
vector_string = f'{vector_string}/{vector_part}'
# remove leading /
vector_string = vector_string.lstrip('/')
return vector_string
@classmethod
def __get_rules_actions(cls, rules_file_path: str) -> dict:
with open(rules_file_path, 'r') as r:
rules_actions = json.load(r)
return rules_actions