|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# |
| 3 | +# pdns-import is a simple import from Passive DNS cof format (in an array) |
| 4 | +# and import these back into a Passive DNS backend |
| 5 | +# |
| 6 | +# This software is part of the D4 project. |
| 7 | +# |
| 8 | +# The software is released under the GNU Affero General Public version 3. |
| 9 | +# |
| 10 | +# Copyright (c) 2019 Alexandre Dulaunoy - [email protected] |
| 11 | +# Copyright (c) Computer Incident Response Center Luxembourg (CIRCL) |
| 12 | + |
| 13 | + |
| 14 | +import re |
| 15 | +import redis |
| 16 | +import fileinput |
| 17 | +import json |
| 18 | +import configparser |
| 19 | +import time |
| 20 | +import logging |
| 21 | +import sys |
| 22 | +import argparse |
| 23 | + |
| 24 | +parser = argparse.ArgumentParser(description='Import array of standard Passive DNS cof format into your Passive DNS server') |
| 25 | +parser.add_argument('--file', dest='filetoimport', help='JSON file to import') |
| 26 | +args = parser.parse_args() |
| 27 | + |
| 28 | +config = configparser.RawConfigParser() |
| 29 | +config.read('../etc/analyzer.conf') |
| 30 | + |
| 31 | +expirations = config.items('expiration') |
| 32 | +excludesubstrings = config.get('exclude', 'substring').split(',') |
| 33 | +myuuid = config.get('global', 'my-uuid') |
| 34 | +myqueue = "analyzer:8:{}".format(myuuid) |
| 35 | +mylogginglevel = config.get('global', 'logging-level') |
| 36 | +logger = logging.getLogger('pdns ingestor') |
| 37 | +ch = logging.StreamHandler() |
| 38 | +if mylogginglevel == 'DEBUG': |
| 39 | + logger.setLevel(logging.DEBUG) |
| 40 | + ch.setLevel(logging.DEBUG) |
| 41 | +elif mylogginglevel == 'INFO': |
| 42 | + logger.setLevel(logging.INFO) |
| 43 | + ch.setLevel(logging.INFO) |
| 44 | +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') |
| 45 | +ch.setFormatter(formatter) |
| 46 | +logger.addHandler(ch) |
| 47 | + |
| 48 | +logger.info("Starting and using FIFO {} from D4 server".format(myqueue)) |
| 49 | + |
| 50 | +d4_server = config.get('global', 'd4-server') |
| 51 | +r = redis.Redis(host="127.0.0.1",port=6400) |
| 52 | +r_d4 = redis.Redis(host=d4_server.split(':')[0], port=d4_server.split(':')[1], db=2) |
| 53 | + |
| 54 | + |
| 55 | +with open('../etc/records-type.json') as rtypefile: |
| 56 | + rtype = json.load(rtypefile) |
| 57 | + |
| 58 | +dnstype = {} |
| 59 | + |
| 60 | +stats = True |
| 61 | + |
| 62 | +for v in rtype: |
| 63 | + dnstype[(v['type'])] = v['value'] |
| 64 | + |
| 65 | +while (True): |
| 66 | + expiration = None |
| 67 | + if not (args.filetoimport): |
| 68 | + parser.print_help() |
| 69 | + sys.exit(0) |
| 70 | + with open(args.filetoimport) as dnsimport: |
| 71 | + records = json.load(dnsimport) |
| 72 | + |
| 73 | + print (records) |
| 74 | + if records is False: |
| 75 | + logger.debug('Parsing of passive DNS line failed: {}'.format(l.strip())) |
| 76 | + continue |
| 77 | + for rdns in records: |
| 78 | + logger.debug("parsed record: {}".format(r)) |
| 79 | + if 'rrname' not in rdns: |
| 80 | + logger.debug('Parsing of passive DNS line is incomplete: {}'.format(l.strip())) |
| 81 | + continue |
| 82 | + if rdns['rrname'] and rdns['rrtype']: |
| 83 | + rdns['type'] = dnstype[rdns['rrtype']] |
| 84 | + rdns['v'] = rdns['rdata'] |
| 85 | + excludeflag = False |
| 86 | + for exclude in excludesubstrings: |
| 87 | + if exclude in rdns['rrname']: |
| 88 | + excludeflag = True |
| 89 | + if excludeflag: |
| 90 | + logger.debug('Excluded {}'.format(rdns['rrname'])) |
| 91 | + continue |
| 92 | + if rdns['type'] == '16': |
| 93 | + rdns['v'] = rdns['v'].replace("\"", "", 1) |
| 94 | + query = "r:{}:{}".format(rdns['rrname'],rdns['type']) |
| 95 | + logger.debug('redis sadd: {} -> {}'.format(query,rdns['v'])) |
| 96 | + r.sadd(query, rdns['v']) |
| 97 | + res = "v:{}:{}".format(rdns['v'], rdns['type']) |
| 98 | + logger.debug('redis sadd: {} -> {}'.format(res,rdns['rrname'])) |
| 99 | + r.sadd(res, rdns['q']) |
| 100 | + |
| 101 | + firstseen = "s:{}:{}:{}".format(rdns['rrname'], rdns['v'], rdns['type']) |
| 102 | + if not r.exists(firstseen): |
| 103 | + r.set(firstseen, rdns['time_first']) |
| 104 | + logger.debug('redis set: {} -> {}'.format(firstseen, rdns['time_first'])) |
| 105 | + |
| 106 | + |
| 107 | + lastseen = "l:{}:{}:{}".format(rdns['rrname'], rdns['v'], rdns['type']) |
| 108 | + last = r.get(lastseen) |
| 109 | + if last is None or int(last) < int(rdns['timestamp']): |
| 110 | + r.set(lastseen, rdns['time_last']) |
| 111 | + logger.debug('redis set: {} -> {}'.format(lastseen, rdns['time_last'])) |
| 112 | + |
| 113 | + occ = "o:{}:{}:{}".format(rdns['rrname'], rdns['v'], rdns['type']) |
| 114 | + r.set(occ, rdns['count']) |
| 115 | + |
| 116 | + |
| 117 | + if stats: |
| 118 | + r.incrby('stats:processed', amount=1) |
| 119 | + if not r: |
| 120 | + logger.info('empty passive dns record') |
| 121 | + continue |
0 commit comments