|
| 1 | +import json |
| 2 | +import requests |
| 3 | +from . import check_input_attribute, standard_error_message |
| 4 | +from pymisp import MISPAttribute, MISPEvent, MISPObject |
| 5 | + |
| 6 | +mispattributes = { |
| 7 | + 'input': ['ip-src', 'ip-dst'], |
| 8 | + 'format': 'misp_standard' |
| 9 | +} |
| 10 | +moduleinfo = { |
| 11 | + 'version': 1, |
| 12 | + 'author': 'Christian Studer', |
| 13 | + 'description': 'An expansion module to query ipinfo.io for additional information on an IP address', |
| 14 | + 'module-type': ['expansion', 'hover'] |
| 15 | +} |
| 16 | +moduleconfig = ['token'] |
| 17 | + |
| 18 | +_GEOLOCATION_OBJECT_MAPPING = { |
| 19 | + 'city': 'city', |
| 20 | + 'postal': 'zipcode', |
| 21 | + 'region': 'region', |
| 22 | + 'country': 'countrycode' |
| 23 | +} |
| 24 | + |
| 25 | + |
| 26 | +def handler(q=False): |
| 27 | + # Input checks |
| 28 | + if q is False: |
| 29 | + return False |
| 30 | + request = json.loads(q) |
| 31 | + if not request.get('attribute') or not check_input_attribute(request['attribute']): |
| 32 | + return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'} |
| 33 | + attribute = request['attribute'] |
| 34 | + if attribute.get('type') not in mispattributes['input']: |
| 35 | + return {'error': 'Wrong input attribute type.'} |
| 36 | + if not request.get('config'): |
| 37 | + return {'error': 'Missing ipinfo config.'} |
| 38 | + if not request['config'].get('token'): |
| 39 | + return {'error': 'Missing ipinfo token.'} |
| 40 | + |
| 41 | + # Query ipinfo.io |
| 42 | + query = requests.get( |
| 43 | + f"https://ipinfo.io/{attribute['value']}/json?token={request['config']['token']}" |
| 44 | + ) |
| 45 | + if query.status_code != 200: |
| 46 | + return {'error': f'Error while querying ipinfo.io - {query.status_code}: {query.reason}'} |
| 47 | + ipinfo = query.json() |
| 48 | + |
| 49 | + # Check if the IP address is not reserved for special use |
| 50 | + if ipinfo.get('bogon', False): |
| 51 | + return {'error': 'The IP address is reserved for special use'} |
| 52 | + |
| 53 | + # Initiate the MISP data structures |
| 54 | + misp_event = MISPEvent() |
| 55 | + input_attribute = MISPAttribute() |
| 56 | + input_attribute.from_dict(**attribute) |
| 57 | + misp_event.add_attribute(**input_attribute) |
| 58 | + |
| 59 | + # Parse the geolocation information related to the IP address |
| 60 | + geolocation = MISPObject('geolocation') |
| 61 | + for field, relation in _GEOLOCATION_OBJECT_MAPPING.items(): |
| 62 | + geolocation.add_attribute(relation, ipinfo[field]) |
| 63 | + for relation, value in zip(('latitude', 'longitude'), ipinfo['loc'].split(',')): |
| 64 | + geolocation.add_attribute(relation, value) |
| 65 | + geolocation.add_reference(input_attribute.uuid, 'locates') |
| 66 | + misp_event.add_object(geolocation) |
| 67 | + |
| 68 | + # Parse the domain information |
| 69 | + domain_ip = misp_event.add_object(name='domain-ip') |
| 70 | + for feature in ('hostname', 'ip'): |
| 71 | + domain_ip.add_attribute(feature, ipinfo[feature]) |
| 72 | + domain_ip.add_reference(input_attribute.uuid, 'resolves') |
| 73 | + if ipinfo.get('domain') is not None: |
| 74 | + for domain in ipinfo['domain']['domains']: |
| 75 | + domain_ip.add_attribute('domain', domain) |
| 76 | + |
| 77 | + # Parse the AS information |
| 78 | + asn = MISPObject('asn') |
| 79 | + asn.add_reference(input_attribute.uuid, 'includes') |
| 80 | + if ipinfo.get('asn') is not None: |
| 81 | + asn_info = ipinfo['asn'] |
| 82 | + asn.add_attribute('asn', asn_info['asn']) |
| 83 | + asn.add_attribute('description', asn_info['name']) |
| 84 | + misp_event.add_object(asn) |
| 85 | + elif ipinfo.get('org'): |
| 86 | + as_value, *description = ipinfo['org'].split(' ') |
| 87 | + asn.add_attribute('asn', as_value) |
| 88 | + asn.add_attribute('description', ' '.join(description)) |
| 89 | + misp_event.add_object(asn) |
| 90 | + |
| 91 | + |
| 92 | + # Return the results in MISP format |
| 93 | + event = json.loads(misp_event.to_json()) |
| 94 | + return { |
| 95 | + 'results': {key: event[key] for key in ('Attribute', 'Object')} |
| 96 | + } |
| 97 | + |
| 98 | + |
| 99 | +def introspection(): |
| 100 | + return mispattributes |
| 101 | + |
| 102 | + |
| 103 | +def version(): |
| 104 | + moduleinfo['config'] = moduleconfig |
| 105 | + return moduleinfo |
0 commit comments