diff --git a/misp_modules/modules/expansion/abuseipdb.py b/misp_modules/modules/expansion/abuseipdb.py index 874a970..afab5c9 100644 --- a/misp_modules/modules/expansion/abuseipdb.py +++ b/misp_modules/modules/expansion/abuseipdb.py @@ -5,12 +5,31 @@ from . import check_input_attribute, checking_error, standard_error_message import dns.resolver misperrors = {'error': 'Error'} -mispattributes = {'input': ['hostname', 'domain', 'domain|ip'], 'format': 'misp_standard'} +mispattributes = {'input': ['ip-src', 'ip-dst', 'hostname', 'domain', 'domain|ip'], 'format': 'misp_standard'} moduleinfo = {'version': '0.1', 'author': 'Stephanie S', 'description': 'AbuseIPDB MISP expansion module', 'module-type': ['expansion', 'hover']} -moduleconfig = ['api_key', 'max_age_in_days'] +moduleconfig = ['api_key', 'max_age_in_days', 'abuse_threshold'] + +def get_ip(request): + # Need to get the ip from the domain + resolver = dns.resolver.Resolver() + resolver.timeout = 2 + resolver.lifetime = 2 + + try: + ip = resolver.query(request["attribute"]["value"], 'A') + return ip + except dns.resolver.NXDOMAIN: + misperrors['error'] = "NXDOMAIN" + return misperrors + except dns.exception.Timeout: + misperrors['error'] = "Timeout" + return misperrors + except Exception: + misperrors['error'] = "DNS resolving error" + return misperrors def handler(q=False): if q is False: @@ -21,33 +40,24 @@ def handler(q=False): return {"error": "AbuseIPDB API key is missing"} if "max_age_in_days" not in request["config"]: return {"error": "AbuseIPDB max age in days is missing"} + if "abuse_threshold" not in request["config"]: + return {"error": "AbuseIPDB abuse threshold is missing"} if not request.get('attribute') or not check_input_attribute(request['attribute'], requirements=('type', 'value')): return {'error': f'{standard_error_message}, {checking_error}.'} if request['attribute']['type'] not in mispattributes['input']: return {'error': 'Unsupported attribute type.'} - # Need to get the ip from the domain - resolver = dns.resolver.Resolver() - resolver.timeout = 2 - resolver.lifetime = 2 + if (request['attribute']['type'] == 'hostname' or request['attribute']['type'] == 'domain' or request['attribute']['type'] == 'domain|ip'): + ip = get_ip(request)[0] - try: - ip = resolver.query(request["attribute"]["value"], 'A') - except dns.resolver.NXDOMAIN: - misperrors['error'] = "NXDOMAIN" - return misperrors - except dns.exception.Timeout: - misperrors['error'] = "Timeout" - return misperrors - except Exception: - misperrors['error'] = "DNS resolving error" - return misperrors - + else: + ip = request["attribute"]["value"] + api_key = request["config"]["api_key"] max_age_in_days = request["config"]["max_age_in_days"] api_endpoint = 'https://api.abuseipdb.com/api/v2/check' querystring = { - 'ipAddress': ip[0], + 'ipAddress': ip, 'maxAgeInDays': max_age_in_days } headers = { @@ -65,6 +75,13 @@ def handler(q=False): is_public = response_json['data']['isPublic'] abuse_confidence_score = response_json['data']['abuseConfidenceScore'] + abuse_threshold = request["config"]["abuse_threshold"] + + if (request["config"]["abuse_threshold"] is not None): + abuse_threshold = request["config"]["abuse_threshold"] + else: + abuse_threshold = 70 + if (is_whitelisted == False): is_whitelisted = 0 if (is_tor == False): @@ -79,9 +96,15 @@ def handler(q=False): else: event = MISPEvent() obj = MISPObject('abuseipdb') - attribute = MISPAttribute() event.add_attribute(**request['attribute']) + if int(abuse_confidence_score) >= int(abuse_threshold): + malicious_attribute = obj.add_attribute('is-malicious', **{'type': 'boolean', 'value': 1}) + malicious_attribute.add_tag(f'ioc:artifact-state="malicious"') + else: + malicious_attribute = obj.add_attribute('is-malicious', **{'type': 'boolean', 'value': 0}) + malicious_attribute.add_tag(f'ioc:artifact-state="not-malicious"') + if is_whitelisted is not None: obj.add_attribute('is-whitelisted', **{'type': 'boolean', 'value': is_whitelisted}) obj.add_attribute('is-tor', **{'type': 'boolean', 'value': is_tor}) diff --git a/misp_modules/modules/expansion/google_safe_browsing.py b/misp_modules/modules/expansion/google_safe_browsing.py new file mode 100644 index 0000000..4920e94 --- /dev/null +++ b/misp_modules/modules/expansion/google_safe_browsing.py @@ -0,0 +1,76 @@ +# import requests +import json +from pymisp import MISPObject, MISPAttribute, MISPEvent +from . import check_input_attribute, checking_error, standard_error_message +from pysafebrowsing import SafeBrowsing + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['url'], 'format': 'misp_standard'} +moduleinfo = {'version': '0.1', 'author': 'Stephanie S', + 'description': 'Google safe browsing expansion module', + 'module-type': ['expansion', 'hover']} + +moduleconfig = ['api_key'] + +def handler(q=False): + if q is False: + return False + request = json.loads(q) + + if "config" not in request or "api_key" not in request["config"]: + return {"error": "Google Safe Browsing API key is missing"} + if not request.get('attribute') or not check_input_attribute(request['attribute'], requirements=('type', 'value')): + return {'error': f'{standard_error_message}, {checking_error}.'} + if request['attribute']['type'] not in mispattributes['input']: + return {'error': 'Unsupported attribute type.'} + + api_key = request["config"]["api_key"] + url = request["attribute"]["value"] + + s = SafeBrowsing(api_key) + try: + response = s.lookup_urls([url]) + + event = MISPEvent() + obj = MISPObject('google-safe-browsing') + event.add_attribute(**request['attribute']) + + if (response[url]['malicious'] != False): + # gsb threat types: THREAT_TYPE_UNSPECIFIED, MALWARE, SOCIAL_ENGINEERING, UNWANTED_SOFTWARE, POTENTIALLY_HARMFUL_APPLICATION + gsb_circl_threat_taxonomy = {"MALWARE": 'malware', "SOCIAL_ENGINEERING": 'social-engineering'} + + threats = response[url]['threats'] + malicious = response[url]['malicious'] + platforms = response[url]['platforms'] + + malicious_attribute = obj.add_attribute('malicious', **{'type': 'boolean', 'value': malicious}) + malicious_attribute.add_tag(f'ioc:artifact-state="malicious"') + threat_attribute = obj.add_attribute('threats', **{'type': 'text', 'value': str(" ".join(threats))}) + for threat in threats: + # If the threat exists as a key in taxonomy_dict, add that tag + if (gsb_circl_threat_taxonomy.get(threat) is not None): + threat_attribute.add_tag(f'circl:incident="{gsb_circl_threat_taxonomy.get(threat)}"') + else: + threat_attribute.add_tag(f'threat-type:{str(threat).lower()}') + obj.add_attribute('platforms', **{'type': 'text', 'value': str(" ".join(platforms))}) + + else: + malicious_attribute = obj.add_attribute('malicious', **{'type': 'boolean', 'value': 0}) # 0 == False + malicious_attribute.add_tag(f'ioc:artifact-state="not-malicious"') + + obj.add_reference(request['attribute']['uuid'], "describes") + event.add_object(obj) + + # Avoid serialization issue + event = json.loads(event.to_json()) + return {"results": {'Object': event['Object'], 'Attribute': event['Attribute']}} + + except Exception as error: + return {"error": "An error occurred: " + str(error)} + +def introspection(): + return mispattributes + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo