def test_json_schema_severities(self): v = 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L/E:P/CR:L/IR:L/AR:L/MAV:P' json = CVSS3(v).as_json() self.assertEqual(json['baseSeverity'], "HIGH") self.assertEqual(json['temporalSeverity'], "MEDIUM") self.assertEqual(json['environmentalSeverity'], "LOW")
def test_parse_from_text_both_versions(self): v1 = 'CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H' v2 = 'AV:N/AC:L/Au:N/C:C/I:C/A:C' i = 'xxx. ' + v1 + ' ' + v2 + '. xxx' e = set() e.add(CVSS3(v1)) e.add(CVSS2(v2)) self.assertEqual(set(parser.parse_cvss_from_text(i)), e)
def test_clean_vector(self): """ Tests for cleaning-up vector, where fields are not in order or some fields have X values. """ v = 'CVSS:3.0/S:C/C:H/I:H/A:N/AV:P/AC:H/PR:H/UI:R/E:H/RL:O/RC:R/CR:H/IR:X/AR:X/MAC:H/MPR:X/MUI:X/MC:L/MA:X' self.assertEqual( 'CVSS:3.0/AV:P/AC:H/PR:H/UI:R/S:C/C:H/I:H/A:N/E:H/RL:O/RC:R/CR:H/MAC:H/MC:L', CVSS3(v).clean_vector()) v = 'CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:N/I:L/A:N/E:P/RC:C/MAV:N/MPR:H/MUI:X/MS:U/MI:X' self.assertEqual( 'CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:N/I:L/A:N/E:P/RC:C/MAV:N/MPR:H/MS:U', CVSS3(v).clean_vector()) v = 'CVSS:3.0/A:N/E:P/RC:C/MAV:N/AV:A/AC:H/S:U/C:N/I:L/MPR:H/MUI:X/MS:U/MI:X/PR:H/UI:R' self.assertEqual( 'CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:N/I:L/A:N/E:P/RC:C/MAV:N/MPR:H/MS:U', CVSS3(v).clean_vector())
def test_parse_from_text_optional_sentence_cases(self): # Missing space after end of sentence and before vector v = 'CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H' i = '.' + v e = [CVSS3(v)] self.assertEqual(parser.parse_cvss_from_text(i), e) # End of sentence v = 'CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H' i = v + '.' e = [CVSS3(v)] self.assertEqual(parser.parse_cvss_from_text(i), e) # Missing space after dot before vector v = 'CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H' i = 'xxx.' + v e = [CVSS3(v)] self.assertEqual(parser.parse_cvss_from_text(i), e)
def run_tests_from_file(self, test_name): with open(path.join(WD, test_name)) as f: for line in f: vector, expected_scores = line.split(' - ') expected_scores = expected_scores.replace('(', '').replace( ')', '').split(', ') expected_scores = tuple(float(a) for a in expected_scores) result = CVSS3(vector) results_scores = result.scores() self.assertEqual(expected_scores, results_scores, test_name + ' - ' + vector)
def run_rh_tests_from_file(self, test_name): with open(path.join(WD, test_name)) as f: for line in f: vector, expected_scores = line.split(' - ') expected_scores = expected_scores.replace('(', '').replace( ')', '').strip().split(', ') expected_scores = tuple( float(a) if a != 'None' else None for a in expected_scores) tested_rh_vector = str(expected_scores[0]) + '/' + vector result = CVSS3.from_rh_vector(tested_rh_vector) results_scores = result.scores() self.assertEqual(expected_scores, results_scores, test_name + ' - ' + vector)
def test_json_ordering(self): vectors_to_schema = { 'vectors_random3': 'schemas/cvss-v3.0.json', 'vectors_random31': 'schemas/cvss-v3.1.json', } for vectors_file_path, schema_file_path in vectors_to_schema.items(): with open(path.join(WD, vectors_file_path)) as f: for line in f: vector, _ = line.split(' - ') cvss = CVSS3(vector).as_json(sort=True) old_key = '' for key in cvss: if key < old_key: self.fail( 'dict ordering was not preserved: key {} less than previous key {} for CVSS object {}' .format(key, old_key, cvss)) old_key = key
def test_json_schema_repr(self): try: import jsonschema except ImportError: return vectors_to_schema = { 'vectors_random3': 'schemas/cvss-v3.0.json', 'vectors_random31': 'schemas/cvss-v3.1.json', } for vectors_file_path, schema_file_path in vectors_to_schema.items(): with open(path.join(WD, vectors_file_path)) as f: for line in f: vector, _ = line.split(' - ') cvss = CVSS3(vector) with open(path.join(WD, schema_file_path)) as schema_file: schema = json.load(schema_file) try: jsonschema.validate(instance=cvss.as_json(), schema=schema) except jsonschema.exceptions.ValidationError: self.fail('jsonschema validation failed on vector: {}'. format(vector))
def test_parse_from_text_multiple_vectors_same_cvss(self): v = 'CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H' e = [CVSS3(v)] i = 'Title: {0}\nThis is an overview of {0} problem.\nLinks: {0}'.format( v) self.assertEqual(parser.parse_cvss_from_text(i), e)
def test_parse_from_text_cvss3(self): i = 'CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H' e = [CVSS3(i)] self.assertEqual(parser.parse_cvss_from_text(i), e) i = 'CVSS' e = [] self.assertEqual(parser.parse_cvss_from_text(i), e) # Truncated vector i = 'CVSS:3' e = [] self.assertEqual(parser.parse_cvss_from_text(i), e) i = 'CVSS:3.0' e = [] self.assertEqual(parser.parse_cvss_from_text(i), e) i = 'CVSS:3.0/' e = [] self.assertEqual(parser.parse_cvss_from_text(i), e) i = 'CVSS:3.0/AV:N' e = [] self.assertEqual(parser.parse_cvss_from_text(i), e) i = 'CVSS:3.0/AV:X' e = [] self.assertEqual(parser.parse_cvss_from_text(i), e) i = 'CVSS:3.0/AV:ZZZ' e = [] self.assertEqual(parser.parse_cvss_from_text(i), e) i = 'CVSS:3.0/AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N/MAV:A/MAC:L/MPR:N/MUI:N/MS:U/MC:N/MI:N/MA:N' e = [CVSS3(i)] self.assertEqual(parser.parse_cvss_from_text(i), e) # Missing mandatory prefix i = 'AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N' e = [] self.assertEqual(parser.parse_cvss_from_text(i), e) v1 = 'CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H' v2 = 'CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N' i = ' '.join([v1, v2]) e = set() e.add(CVSS3(v1)) e.add(CVSS3(v2)) self.assertEqual(set(parser.parse_cvss_from_text(i)), e) # Correct text v = 'CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H' i = 'xxx ' + v e = [CVSS3(v)] self.assertEqual(parser.parse_cvss_from_text(i), e) v = 'CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H' i = v + ' xxx' e = [CVSS3(v)] self.assertEqual(parser.parse_cvss_from_text(i), e)
def test_severities(self): """ Tests for computing severities. """ v = 'CVSS:3.0/AV:N/AC:H/PR:L/UI:N/S:C/C:N/I:N/A:N' self.assertEqual(('None', 'None', 'None'), CVSS3(v).severities(), v) v = 'CVSS:3.0/AV:P/AC:H/PR:L/UI:R/S:U/C:N/I:L/A:N' self.assertEqual(('Low', 'Low', 'Low'), CVSS3(v).severities(), v) v = 'CVSS:3.0/AV:N/AC:H/PR:L/UI:R/S:U/C:N/I:L/A:N' self.assertEqual(('Low', 'Low', 'Low'), CVSS3(v).severities(), v) v = 'CVSS:3.0/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L' self.assertEqual(('Low', 'Low', 'Low'), CVSS3(v).severities(), v) v = 'CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L' self.assertEqual(('Medium', 'Medium', 'Medium'), CVSS3(v).severities(), v) v = 'CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:L' self.assertEqual(('Medium', 'Medium', 'Medium'), CVSS3(v).severities(), v) v = 'CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N' self.assertEqual(('Medium', 'Medium', 'Medium'), CVSS3(v).severities(), v) v = 'CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L' self.assertEqual(('High', 'High', 'High'), CVSS3(v).severities(), v) v = 'CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H' self.assertEqual(('High', 'High', 'High'), CVSS3(v).severities(), v) v = 'CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N' self.assertEqual(('Critical', 'Critical', 'Critical'), CVSS3(v).severities(), v) v = 'CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:H' self.assertEqual(('Critical', 'Critical', 'Critical'), CVSS3(v).severities(), v) v = 'CVSS:3.0/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:P/RL:W/IR:M/AR:H/MAV:N/MAC:H/MPR:L/MUI:N/MC:N/MI:N' self.assertEqual(('High', 'High', 'Medium'), CVSS3(v).severities(), v) v = 'CVSS:3.0/AV:P/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N/E:H/RC:U/CR:M/MAV:P/MAC:L/MUI:R/MC:N/MI:N' self.assertEqual(('Medium', 'Low', 'None'), CVSS3(v).severities(), v)
def get_item(self, vulnerability, test): # vulnerable and unaffected versions can be in string format for a single vulnerable version, # or an array for multiple versions depending on the language. if isinstance(vulnerability['semver']['vulnerable'], list): vulnerable_versions = ", ".join( vulnerability['semver']['vulnerable']) else: vulnerable_versions = vulnerability['semver']['vulnerable'] # Following the CVSS Scoring per https://nvd.nist.gov/vuln-metrics/cvss if 'cvssScore' in vulnerability: # If we're dealing with a license finding, there will be no cvssScore if vulnerability['cvssScore'] <= 3.9: severity = "Low" elif vulnerability['cvssScore'] >= 4.0 and vulnerability[ 'cvssScore'] <= 6.9: severity = "Medium" elif vulnerability['cvssScore'] >= 7.0 and vulnerability[ 'cvssScore'] <= 8.9: severity = "High" else: severity = "Critical" else: # Re-assign 'severity' directly severity = vulnerability['severity'].title() # Construct "file_path" removing versions vulnPath = '' for index, item in enumerate(vulnerability['from']): if index == 0: vulnPath += "@".join(item.split("@")[0:-1]) else: vulnPath += " > " + "@".join(item.split("@")[0:-1]) # create the finding object finding = Finding( title=vulnerability['from'][0] + ": " + vulnerability['title'], test=test, severity=severity, severity_justification="Issue severity of: **" + severity + "** from a base " + "CVSS score of: **" + str(vulnerability.get('cvssScore')) + "**", description="## Component Details\n - **Vulnerable Package**: " + vulnerability['packageName'] + "\n- **Current Version**: " + str(vulnerability['version']) + "\n- **Vulnerable Version(s)**: " + vulnerable_versions + "\n- **Vulnerable Path**: " + " > ".join(vulnerability['from']) + "\n" + vulnerability['description'], mitigation= "A fix (if available) will be provided in the description.", component_name=vulnerability['packageName'], component_version=vulnerability['version'], false_p=False, duplicate=False, out_of_scope=False, impact=severity, static_finding=True, dynamic_finding=False, file_path=vulnPath, vuln_id_from_tool=vulnerability['id'], ) # CVSSv3 vector if 'CVSSv3' in vulnerability: finding.cvssv3 = CVSS3(vulnerability['CVSSv3']).clean_vector() # manage CVE and CWE with idnitifiers cve_references = '' cwe_references = '' if 'identifiers' in vulnerability: if 'CVE' in vulnerability['identifiers']: cves = vulnerability['identifiers']['CVE'] if cves: # Per the current json format, if several CVEs listed, take the first one. finding.cve = cves[0] if len(cves) > 1: cve_references = ', '.join(cves) if 'CWE' in vulnerability['identifiers']: cwes = vulnerability['identifiers']['CWE'] if cwes: # Per the current json format, if several CWEs, take the first one. finding.cwe = int(cwes[0].split("-")[1]) if len(vulnerability['identifiers']['CVE']) > 1: cwe_references = ', '.join(cwes) else: finding.cwe = 1035 references = '' if 'id' in vulnerability: references = "**SNYK ID**: https://app.snyk.io/vuln/{}\n\n".format( vulnerability['id']) if cve_references or cwe_references: references += "Several CVEs or CWEs were reported: \n\n{}\n{}\n".format( cve_references, cwe_references) # Append vuln references to references section for item in vulnerability.get('references', []): references += "**" + item['title'] + "**: " + item['url'] + "\n" finding.references = references finding.description = finding.description.strip() # Find remediation string limit indexes remediation_index = finding.description.find("## Remediation") references_index = finding.description.find("## References") # Add the remediation substring to mitigation section if (remediation_index != -1) and (references_index != -1): finding.mitigation = finding.description[ remediation_index:references_index] return finding