misp-modules/misp_modules/modules/expansion/cve_advanced.py

186 lines
7.8 KiB
Python

import json
import requests
from . import check_input_attribute, standard_error_message
from collections import defaultdict
from pymisp import MISPAttribute, MISPEvent, MISPObject
misperrors = {'error': 'Error'}
mispattributes = {'input': ['vulnerability'], 'format': 'misp_standard'}
moduleinfo = {
'version': '2',
'author': 'Christian Studer',
'description': 'An expansion module to query the CIRCL CVE search API for more information about a vulnerability (CVE).',
'module-type': ['expansion', 'hover'],
'name': 'CVE Advanced Lookup',
'logo': 'cve.png',
'requirements': [],
'features': 'The module takes a vulnerability attribute as input and queries the CIRCL CVE search API to gather additional information.\n\nThe result of the query is then parsed to return additional information about the vulnerability, like its cvss score or some references, as well as the potential related weaknesses and attack patterns.\n\nThe vulnerability additional data is returned in a vulnerability MISP object, and the related additional information are put into weakness and attack-pattern MISP objects.',
'references': ['https://vulnerability.circl.lu', 'https://cve/mitre.org/'],
'input': 'Vulnerability attribute.',
'output': 'Additional information about the vulnerability, such as its cvss score, some references, or the related weaknesses and attack patterns.',
}
moduleconfig = ["custom_API"]
cveapi_url = 'https://cvepremium.circl.lu/api/'
class VulnerabilityParser():
def __init__(self, attribute, api_url):
misp_attribute = MISPAttribute()
misp_attribute.from_dict(**attribute)
misp_event = MISPEvent()
misp_event.add_attribute(**misp_attribute)
self.__misp_attribute = misp_attribute
self.__misp_event = misp_event
self.__api_url = api_url
self.references = defaultdict(list)
self.__capec_features = ('id', 'name', 'summary', 'prerequisites', 'solutions')
self.__vulnerability_mapping = {
'id': 'id', 'summary': 'summary',
'Modified': 'modified', 'cvss3': 'cvss-score',
'cvss3-vector': 'cvss-string'
}
self.__vulnerability_multiple_mapping = {
'vulnerable_configuration': 'vulnerable-configuration',
'vulnerable_configuration_cpe_2_2': 'vulnerable-configuration',
'references': 'references'
}
self.__weakness_mapping = {
'name': 'name', 'description_summary': 'description',
'status': 'status', 'weaknessabs': 'weakness-abs'
}
@property
def api_url(self) -> str:
return self.__api_url
@property
def capec_features(self) -> tuple:
return self.__capec_features
@property
def misp_attribute(self) -> MISPAttribute:
return self.__misp_attribute
@property
def misp_event(self) -> MISPEvent:
return self.__misp_event
@property
def vulnerability_mapping(self) -> dict:
return self.__vulnerability_mapping
@property
def vulnerability_multiple_mapping(self) -> dict:
return self.__vulnerability_multiple_mapping
@property
def weakness_mapping(self) -> dict:
return self.__weakness_mapping
def get_result(self):
if self.references:
self.__build_references()
event = json.loads(self.misp_event.to_json())
results = {key: event[key] for key in ('Attribute', 'Object') if (key in event and event[key])}
return {'results': results}
def parse_vulnerability_information(self, vulnerability):
vulnerability_object = MISPObject('vulnerability')
for feature, relation in self.vulnerability_mapping.items():
if vulnerability.get(feature):
vulnerability_object.add_attribute(relation, vulnerability[feature])
if 'Published' in vulnerability:
vulnerability_object.add_attribute('published', vulnerability['Published'])
vulnerability_object.add_attribute('state', 'Published')
for feature, relation in self.vulnerability_multiple_mapping.items():
if feature in vulnerability:
for value in vulnerability[feature]:
if isinstance(value, dict):
value = value['title']
vulnerability_object.add_attribute(relation, value)
vulnerability_object.add_reference(self.misp_attribute.uuid, 'related-to')
self.misp_event.add_object(vulnerability_object)
if 'cwe' in vulnerability and vulnerability['cwe'] not in ('Unknown', 'NVD-CWE-noinfo'):
self.__parse_weakness(vulnerability['cwe'], vulnerability_object.uuid)
if 'capec' in vulnerability:
self.__parse_capec(vulnerability['capec'], vulnerability_object.uuid)
def __build_references(self):
for object_uuid, references in self.references.items():
for misp_object in self.misp_event.objects:
if misp_object.uuid == object_uuid:
for reference in references:
misp_object.add_reference(**reference)
break
def __parse_capec(self, capec_values, vulnerability_uuid):
for capec in capec_values:
capec_object = MISPObject('attack-pattern')
for feature in self.capec_features:
capec_object.add_attribute(feature, capec[feature])
for related_weakness in capec['related_weakness']:
capec_object.add_attribute('related-weakness', f"CWE-{related_weakness}")
self.misp_event.add_object(capec_object)
self.references[vulnerability_uuid].append(
{
'referenced_uuid': capec_object.uuid,
'relationship_type': 'targeted-by'
}
)
def __parse_weakness(self, cwe_value, vulnerability_uuid):
cwe_string, cwe_id = cwe_value.split('-')[:2]
cwe = requests.get(f'{self.api_url}cwe/{cwe_id}')
if cwe.status_code == 200:
cwe = cwe.json()
weakness_object = MISPObject('weakness')
weakness_object.add_attribute('id', f'{cwe_string}-{cwe_id}')
for feature, relation in self.weakness_mapping.items():
if cwe.get(feature):
weakness_object.add_attribute(relation, cwe[feature])
self.misp_event.add_object(weakness_object)
self.references[vulnerability_uuid].append(
{
'referenced_uuid': weakness_object.uuid,
'relationship_type': 'weakened-by'
}
)
def check_url(url):
return f"{url}/" if not url.endswith('/') else url
def handler(q=False):
if q is False:
return False
request = json.loads(q)
if not request.get('attribute') or not check_input_attribute(request['attribute']):
return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
attribute = request['attribute']
if attribute.get('type') != 'vulnerability':
misperrors['error'] = 'Vulnerability id missing.'
return misperrors
api_url = check_url(request['config']['custom_API']) if request.get('config', {}).get('custom_API') else cveapi_url
r = requests.get(f"{api_url}cve/{attribute['value']}")
if r.status_code == 200:
vulnerability = r.json()
if not vulnerability:
misperrors['error'] = 'Non existing CVE'
return misperrors['error']
else:
misperrors['error'] = 'API not accessible'
return misperrors['error']
parser = VulnerabilityParser(attribute, api_url)
parser.parse_vulnerability_information(vulnerability)
return parser.get_result()
def introspection():
return mispattributes
def version():
moduleinfo['config'] = moduleconfig
return moduleinfo