diff --git a/misp_modules/modules/expansion/abuseipdb.py b/misp_modules/modules/expansion/abuseipdb.py index 874a970f..afab5c96 100644 --- a/misp_modules/modules/expansion/abuseipdb.py +++ b/misp_modules/modules/expansion/abuseipdb.py @@ -5,27 +5,14 @@ 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'] - -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": "AbuseIPDB API key is missing"} - if "max_age_in_days" not in request["config"]: - return {"error": "AbuseIPDB max age in days 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.'} +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 @@ -33,6 +20,7 @@ def handler(q=False): try: ip = resolver.query(request["attribute"]["value"], 'A') + return ip except dns.resolver.NXDOMAIN: misperrors['error'] = "NXDOMAIN" return misperrors @@ -42,12 +30,34 @@ def handler(q=False): except Exception: misperrors['error'] = "DNS resolving error" return misperrors - + +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": "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.'} + + if (request['attribute']['type'] == 'hostname' or request['attribute']['type'] == 'domain' or request['attribute']['type'] == 'domain|ip'): + ip = get_ip(request)[0] + + 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 00000000..4920e946 --- /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