add: [farsight-passivedns] Optional feature to submit flex queries

- The rrset and rdata queries remain the same but
  with the parameter `flex_queries`, users can
  also get the results of the flex rrnames & flex
  rdata regex queries about their domain, hostname
  or ip address
- Results can thus include passive-dns objects
  containing the `raw_rdata` object_relation added
  with 0a3e948
chrisr3d_patch
chrisr3d 2020-11-13 20:38:02 +01:00
parent 993a614a20
commit dfec0e5cf4
No known key found for this signature in database
GPG Key ID: 6BBED1B63A6D639F
1 changed files with 53 additions and 46 deletions

View File

@ -9,12 +9,12 @@ mispattributes = {
'format': 'misp_standard' 'format': 'misp_standard'
} }
moduleinfo = { moduleinfo = {
'version': '0.3', 'version': '0.4',
'author': 'Christophe Vandeplas', 'author': 'Christophe Vandeplas',
'description': 'Module to access Farsight DNSDB Passive DNS', 'description': 'Module to access Farsight DNSDB Passive DNS',
'module-type': ['expansion', 'hover'] 'module-type': ['expansion', 'hover']
} }
moduleconfig = ['apikey', 'server', 'limit'] moduleconfig = ['apikey', 'server', 'limit', 'flex_queries']
DEFAULT_DNSDB_SERVER = 'https://api.dnsdb.info' DEFAULT_DNSDB_SERVER = 'https://api.dnsdb.info'
DEFAULT_LIMIT = 10 DEFAULT_LIMIT = 10
@ -28,6 +28,7 @@ class FarsightDnsdbParser():
self.passivedns_mapping = { self.passivedns_mapping = {
'bailiwick': {'type': 'text', 'object_relation': 'bailiwick'}, 'bailiwick': {'type': 'text', 'object_relation': 'bailiwick'},
'count': {'type': 'counter', 'object_relation': 'count'}, 'count': {'type': 'counter', 'object_relation': 'count'},
'raw_rdata': {'type': 'text', 'object_relation': 'raw_rdata'},
'rdata': {'type': 'text', 'object_relation': 'rdata'}, 'rdata': {'type': 'text', 'object_relation': 'rdata'},
'rrname': {'type': 'text', 'object_relation': 'rrname'}, 'rrname': {'type': 'text', 'object_relation': 'rrname'},
'rrtype': {'type': 'text', 'object_relation': 'rrtype'}, 'rrtype': {'type': 'text', 'object_relation': 'rrtype'},
@ -45,29 +46,15 @@ class FarsightDnsdbParser():
self.comment = 'Result from an %s lookup on DNSDB about the %s: %s' self.comment = 'Result from an %s lookup on DNSDB about the %s: %s'
def parse_passivedns_results(self, query_response): def parse_passivedns_results(self, query_response):
lookup_fields = (
'count',
'rrname',
'rrtype',
'bailiwick',
'time_first',
'time_last',
'zone_time_first',
'zone_time_last'
)
for query_type, results in query_response.items(): for query_type, results in query_response.items():
comment = self.comment % (query_type, self.type_to_feature[self.attribute['type']], self.attribute['value']) comment = self.comment % (query_type, self.type_to_feature[self.attribute['type']], self.attribute['value'])
for result in results: for result in results:
passivedns_object = MISPObject('passive-dns') passivedns_object = MISPObject('passive-dns')
for feature in lookup_fields: if result.get('rdata') and isinstance(result['rdata'], list):
if result.get(feature): for rdata in result.pop('rdata'):
passivedns_object.add_attribute(**self._parse_attribute(comment, feature, result[feature])) passivedns_object.add_attribute(**self._parse_attribute(comment, 'rdata', rdata))
if result.get('rdata'): for feature, value in result.items():
if isinstance(result['rdata'], list): passivedns_object.add_attribute(**self._parse_attribute(comment, feature, value))
for rdata in result['rdata']:
passivedns_object.add_attribute(**self._parse_attribute(comment, 'rdata', rdata))
else:
passivedns_object.add_attribute(**self._parse_attribute(comment, 'rdata', result['rdata']))
passivedns_object.add_reference(self.attribute['uuid'], 'related-to') passivedns_object.add_reference(self.attribute['uuid'], 'related-to')
self.misp_event.add_object(passivedns_object) self.misp_event.add_object(passivedns_object)
@ -95,11 +82,12 @@ def handler(q=False):
if attribute['type'] not in mispattributes['input']: if attribute['type'] not in mispattributes['input']:
return {'error': 'Unsupported attributes type'} return {'error': 'Unsupported attributes type'}
config = request['config'] config = request['config']
if config.get('server') is None: if not config.get('server'):
config['server'] = DEFAULT_DNSDB_SERVER config['server'] = DEFAULT_DNSDB_SERVER
client_args = {feature: config[feature] for feature in ('apikey', 'server')} client_args = {feature: config[feature] for feature in ('apikey', 'server')}
client = dnsdb2.Client(**client_args) client = dnsdb2.Client(**client_args)
if config.get('limit') is None: flex = add_flex_queries(config.get('flex_queries'))
if not config.get('limit'):
config['limit'] = DEFAULT_LIMIT config['limit'] = DEFAULT_LIMIT
lookup_args = { lookup_args = {
'limit': config['limit'], 'limit': config['limit'],
@ -107,39 +95,58 @@ def handler(q=False):
'ignore_limited': True 'ignore_limited': True
} }
to_query = lookup_ip if attribute['type'] in ('ip-src', 'ip-dst') else lookup_name to_query = lookup_ip if attribute['type'] in ('ip-src', 'ip-dst') else lookup_name
response = to_query(client, attribute['value'], lookup_args) try:
if not isinstance(response, dict): response = to_query(client, attribute['value'], lookup_args, flex)
return {'error': response} except dnsdb2.DnsdbException as e:
return {'error': e.__str__()}
if not response: if not response:
return {'error': f"Empty results on Farsight DNSDB for the queries {attribute['type']}: {attribute['value']}."} return {'error': f"Empty results on Farsight DNSDB for the {self.type_to_feature[attribute['type']]}: {attribute['value']}."}
parser = FarsightDnsdbParser(attribute) parser = FarsightDnsdbParser(attribute)
parser.parse_passivedns_results(response) parser.parse_passivedns_results(response)
return parser.get_results() return parser.get_results()
def lookup_name(client, name, lookup_args): def add_flex_queries(flex):
if not flex:
return False
if flex in ('True', 'true', True, '1', 1):
return True
return False
def flex_queries(client, name, lookup_args):
response = {} response = {}
try: rdata = list(client.flex_rdata_regex(name.replace('.', '\.'), **lookup_args))
# RRSET = entries in the left-hand side of the domain name related labels if rdata:
res = client.lookup_rrset(name, **lookup_args) response['flex_rdata'] = rdata
response['rrset'] = list(res) rrnames = list(client.flex_rrnames_regex(name.replace('.', '\.'), **lookup_args))
except dnsdb2.DnsdbException as e: if rrnames:
return e response['flex_rrnames'] = rrnames
try:
# RDATA = entries on the right-hand side of the domain name related labels
res = client.lookup_rdata_name(name, **lookup_args)
response['rdata'] = list(res)
except dnsdb2.DnsdbException as e:
return e
return response return response
def lookup_ip(client, ip, lookup_args): def lookup_name(client, name, lookup_args, flex):
try: response = {}
res = client.lookup_rdata_ip(ip, **lookup_args) # RRSET = entries in the left-hand side of the domain name related labels
response = {'rdata': list(res)} rrset_response = list(client.lookup_rrset(name, **lookup_args))
except dnsdb2.DnsdbException as e: if rrset_response:
return e response['rrset'] = rrset_response
# RDATA = entries on the right-hand side of the domain name related labels
rdata_response = client.lookup_rdata_name(name, **lookup_args)
if rdata_response:
response['rdata'] = rdata_response
if flex:
response.update(flex_queries(client, name, lookup_args))
return response
def lookup_ip(client, ip, lookup_args, flex):
response = {}
res = list(client.lookup_rdata_ip(ip, **lookup_args))
if res:
response['rdata'] = res
if flex:
response.update(flex_queries(client, ip, lookup_args))
return response return response