def _update_attack(self, db_params): # two types of update for DNS: # attack - update attack details (query count, last seen, etc.) # domain - update attack domain-related details (number of returned records, amp rate...) addr_int = utils.addr_to_int(db_params['ip']) for attack in self.session.query(DBThread.Attack).\ filter(DBThread.Attack.src_id == addr_int): if attack.domain.domain_name == db_params['dns_name'] and\ attack.domain.dns_type == db_params['dns_type'] and\ attack.domain.dns_cls == db_params['dns_class']: # attack found, check if this is currently active attack if attack.latest + self.new_attack_interval >= db_params['last_seen']: if db_params['type'] == 'attack': # update attack and source last seen time attack.source.last_seen = db_params['last_seen'] attack.latest = db_params['last_seen'] attack.count = db_params['count'] elif db_params['type'] == 'domain': attack.num_entries = db_params['num_entries'] attack.amplification = db_params['amp'] attack.response = db_params['response'] self.session.add(attack) self.session.commit()
def log_packet(self, msg, addr, sport, dport, timestamp, incoming_pkt, req_size, resp_size, last=False): data = {} data['time'] = str(timestamp) data['src_ip'] = addr data['src_port'] = sport data['dst_port'] = dport data['req_size'] = req_size data['resp_size'] = resp_size if self.server.log_req_packets: data['req_pkt'] = incoming_pkt.decode('ascii') raw_json = json.dumps(data) self.logger.info('%s - %s' % (msg, raw_json)) if not last: db_params = { 'ip': utils.addr_to_int(addr), 'port': sport, 'dport': dport, 'time': timestamp, 'request_pkt': incoming_pkt, 'input_size': req_size, 'output_size': resp_size, } self.server.log_queue.put({ 'type': 'insert', 'db_params': db_params }) # if last packet, send to hpfeeds and notifier/alerter if last: if self.server.hpfeeds_client: self.server.hpfeeds_client.publish('genericpot.events', raw_json) # send notification if alerter is enabled # THIS OPERATION CAN BE SLOW! if self.server.alerter: self.server.alerter.alert(addr, int(sport))
def _add_attack(self, db_params): # ip addresses are stored as integers in order to ensure # efficient lookups and additions addr_int = utils.addr_to_int(db_params['ip']) # check if source and domain already exist in the database and add them if necessary source = self.session.query(DBThread.Source).\ filter(DBThread.Source.src_ip == addr_int).one_or_none() domain = self.session.query(DBThread.Domain).\ filter(DBThread.Domain.domain_name == db_params['dns_name']).\ filter(DBThread.Domain.dns_type == db_params['dns_type']).\ filter(DBThread.Domain.dns_cls == db_params['dns_class']).one_or_none() if not source: source = DBThread.Source( src_ip=addr_int, src_port=db_params['port'], first_seen=db_params['time'], last_seen=db_params['time'] ) self.session.add(source) self.session.commit() else: source.last_seen = db_params['time'] if not domain: domain = DBThread.Domain( domain_name=db_params['dns_name'], opcode=db_params['opcode'], dns_type=db_params['dns_type'], dns_cls=db_params['dns_class'], first_seen=db_params['time'] ) self.session.add(domain) self.session.commit() # add new attack related to the specified source and domain attack = DBThread.Attack( src_id=source.src_ip, domain_id=domain.id, start=db_params['time'], latest=db_params['time'] ) self.session.add(attack) self.session.commit()
def create_server(conf, logger_name, log_queue, output_queue, hpf_client=None, alerter=None): global LOGGER_NAME LOGGER_NAME = logger_name server, ip, port = genpot.create_base_server( ThreadedNTPServer, NTPServer, conf, logger_name, log_queue, output_queue, hpf_client, alerter ) # parse NTP configuration and apply default settings for mode 3 and mode 7 responses # this is done during start-up because these settings are static during server lifetime leap = conf.getint('NTP', 'leap') precision = conf.getint('NTP', 'precision') root_delay = int(conf.get('NTP', 'root_delay'), 16) dispersion = int(conf.get('NTP', 'dispersion'), 16) ref_id = utils.addr_to_int(conf.get('NTP', 'reference_id')) offset = conf.getfloat('NTP', 'timestamp_offset') NTPMode3Packet.set_response_defaults( leap=leap, precision=precision, root_delay=root_delay, dispersion=dispersion, ref_id=ref_id, ref_timestamp_offset=offset) peer_lists = _get_monlist_peer_lists(conf) NTPMode7Packet.set_response_defaults(peer_lists=peer_lists) msg = "NTPot started at %s:%d" % (ip, port) logging.getLogger(LOGGER_NAME).info(msg) print(msg) return server
def handle(self): try: addr = self.client_address[0] port = self.client_address[1] data = self.request[0] sock = self.request[1] first = False last = False # parse request and check if valid packet # invalid packets are discarded # logging only to output log, not to DB try: request = SSDPServer.SSDPRequest(data) except SSDPException as msg: self.logger.error('%s:%d - %s' % (addr, port, msg)) return # IP addresses in transaction log and database will be stored as integers/long addr_int = utils.addr_to_int(addr) now = datetime.datetime.now() log_msg = 'New SSDP packet received' # check if SSDP request from this IP address was already received - ENTERING CRITICAL SECTION HERE! # IP address and ST are the only criteria useful for disrimination of different attacks on the same host # other variables (MX, MAN, HOST headers) should be more or less constant and so irrelevant with self.server.tx_log_lock: req_key = ( addr_int, request.st ) if req_key in self.server.transaction_log: addr_log = self.server.transaction_log[req_key] # check if this is an already existing attack or a new attack # attack is classified as NEW if more than new_attack_duration_interval # minutes have passed since the last seen packet if addr_log['last_seen'] + self.server.new_attack_interval < now: # consider this as a new attack, reset cache data first = True addr_log['count'] = 1 addr_log['last_seen'] = now log_msg = 'New attack detected' else: # update transaction log and database last-seen time and packet count and do not respond to the packet addr_log['last_seen'] = now addr_log['count'] += 1 # add the pair to the request cache set - this set will be frequently flushed to DB self.server.ip_log.add(req_key) # if count >= threshold, ignore the packet, never respond if addr_log['count'] > self.server.threshold: return # log reaching of threshold and mark packet as last that will be accepted elif addr_log['count'] == self.server.threshold: last = True self.logger.info( 'Threshold reached for host %s and search target %s - will not respond to this host pair' % (addr, request.st)) log_msg = 'Last packet - threshold reached' else: # add host to transaction log first = True self.server.transaction_log[req_key] = {} self.server.transaction_log[req_key]['last_seen'] = now self.server.transaction_log[req_key]['count'] = 1 targets = [] # send info about all supported devices if ssdp:all is specified as ST if request.st == 'ssdp:all': targets = self.server.device_list else: for device in self.server.device_list: if request.st == device.device_type or \ request.st == device.uuid: targets.append(device) b64_resp = b'' output_size = 0 responses = [] for target in targets: response = self._create_response(request.st, target) responses.append(response) b64_resp += base64.b64encode(response) output_size += len(response) # log packets to file and database # log and then send the packets, because sending is interrupted by sleep # this is per specification! if first or last: b64_req = base64.b64encode(data) input_size = len(data) self.log_packet( log_msg, addr, port, now, request, b64_req, input_size, b64_resp, output_size, last ) for response in responses: sock.sendto(response, self.client_address) # sleep random number of seconds between 1 and the value # received in the request - per specification time.sleep(random.randint(1, int(request.mx))) except Exception: t = traceback.format_exc() self.logger.error('Unknown error during communication with %s:%d - %s' % (addr, port, base64.b64encode(data))) self.logger.error('Stacktrace: %s' % t)
def _get_monlist_peer_lists(conf): logger = logging.getLogger(LOGGER_NAME) try: generate_random = conf.getboolean('monlist', 'generate_random') num_peers = conf.getint('monlist', 'peers_num') if num_peers < 0: logger.error('Invalid peers_num value, must be greater than zero!') return peers = [] # get 'daddr' (i.e. local_address) from the config file # this value is the same for all the peers daddr = utils.addr_to_int(conf.get('monlist', 'local_address')) for i in range(1, num_peers + 1): peer = None if generate_random: # while generating data, make it look "realistic" # avg_int and count should not differ much between peers! # use values generated for the first peer as a baseline first_avg_int = 10 first_count = 50 if i > 1: first_avg_int = peers[0].avg_int first_count = peers[0].count avg_int = random.randint(first_avg_int - 5, first_avg_int + 5) last_int = random.randint(0, 100) restr = 0 count = random.randint(first_count - 10, first_count + 10) addr = (random.randint(0, 255) << 24) | (random.randint(0, 255) << 16) | \ (random.randint(0, 255) << 8) | random.randint(0, 255) peer = NTPMode7Packet.MonlistPeer(avg_int, last_int, restr, count, addr, daddr) peers.append(peer) else: try: section = 'peer-' + str(i) avg_int = conf.getint(section, 'avg_int') last_int = conf.getint(section, 'last_int') restr = conf.getint(section, 'restr') count = conf.getint(section, 'count') addr = utils.addr_to_int(conf.get(section, 'addr')) flags = conf.getint(section, 'flags') port = conf.getint(section, 'port') mode = conf.getint(section, 'mode') version = conf.getint(section, 'version') v6_flag = conf.getint(section, 'v6_flag') peer = NTPMode7Packet.MonlistPeer( avg_int, last_int, restr, count, addr, daddr, flags, port, mode, version, v6_flag) peers.append(peer) except configparser.NoSectionError: logger.warn('No section %s, ignoring peer...' % section) continue except configparser.NoOptionError as msg: logger.warn('Option error: %s. Ignoring peer...' % msg) continue peer_lists = [peers[i:i + 6] for i in range(0, len(peers), 6)] return peer_lists except configparser.Error as msg: logger.error('Error occurred while parsing monlist section in configuration file: %s' % msg)
def handle(self): try: data = self.request[0] sock = self.request[1] addr = self.client_address[0] port = self.client_address[1] first = False last = False # ignore empty packets if not len(data): self.logger.error('%s:%d - %s' % (addr, port, 'Empty packet received')) return try: packet = self._ntp_packet(data) except NTPException as msg: self.logger.error('%s:%d - %s' % (addr, port, msg)) return # IP addresses in transaction log and database will be stored as integers/long addr_int = utils.addr_to_int(addr) mode = packet.get_mode() now = datetime.datetime.now() log_msg = 'New NTP packet received' # check if this type of packet was already received - ENTERING CRITICAL SECTION HERE! with self.server.tx_log_lock: if addr_int in self.server.transaction_log: addr_log = self.server.transaction_log[addr_int] # take mode into account if addr_log['mode'] == mode: # check if this is an already existing attack or a new attack # attack is classified as NEW if more than new_attack_duration_interval # minutes have passed since the last seen packet if addr_log['last_seen'] + self.server.new_attack_interval < now: # consider this as a new attack, reset cache data first = True addr_log['count'] = 1 addr_log['last_seen'] = now log_msg = 'New attack detected' else: # update transaction log and database last-seen time and packet count and do not respond to the packet addr_log['last_seen'] = now addr_log['count'] += 1 # add the address to ip set - this set will be frequently flushed to DB self.server.ip_log.add(addr_int) # if count >= threshold, ignore the packet, never respond if addr_log['count'] > self.server.threshold: return # log reaching of threshold and mark packet as last that will be accepted elif addr_log['count'] == self.server.threshold: last = True self.logger.info( 'Threshold reached for host %s and mode %d - will not respond to this host/mode pair' % (addr, mode)) log_msg = 'Last packet - threshold reached' else: # add host to transaction log first = True self.server.transaction_log[addr_int] = {} self.server.transaction_log[addr_int]['mode'] = mode self.server.transaction_log[addr_int]['last_seen'] = now self.server.transaction_log[addr_int]['count'] = 1 # handle the received packets (list of response packets) and send the appropriate NTP responses (or exit if no responses should be returned) try: responses = packet.handle() except Exception as msg: self.logger.error('Error while parsing NTP packet from %s:%d or unable to create proper response: %s' % (addr, port, msg)) return if not len(responses): return b64_resp = b'' output_size = 0 for response in responses: sock.sendto(response, self.client_address) b64_resp += base64.b64encode(response) output_size += len(response) # log packet to file and database if first or last: b64_req = base64.b64encode(data) input_size = len(data) self.log_packet( log_msg, addr, port, mode, packet.get_mode_name(), now, b64_req, input_size, b64_resp, output_size, last) except Exception: t = traceback.format_exc() self.logger.error('Unknown error during communication with %s:%d - %s' % (addr, port, base64.b64encode(data))) self.logger.error('Stacktrace: %s' % t)
def handle(self): try: addr = self.client_address[0] port = self.client_address[1] dport = self.server.server_address[1] data = self.request[0] sock = self.request[1] first = False last = False # no need to check for validity, any packet is accepted # IP addresses in transaction log and database will be stored as integers/long addr_int = utils.addr_to_int(addr) now = datetime.datetime.now() log_msg = 'New genericpot packet received' # check if the request from this IP address was already received - ENTERING CRITICAL SECTION HERE! with self.server.tx_log_lock: # since generic pot can be ran on any port, use port that the service is running on for # attack discrimination req_key = (addr_int, dport) if req_key in self.server.transaction_log: addr_log = self.server.transaction_log[req_key] # check if this is an already existing attack or a new attack # attack is classified as NEW if more than new_attack_duration_interval # minutes have passed since the last seen packet if addr_log[ 'last_seen'] + self.server.new_attack_interval < now: # consider this as a new attack, reset cache data first = True addr_log['count'] = 1 addr_log['last_seen'] = now log_msg = 'New attack detected' else: # update transaction log and database last-seen time and packet count and do not respond to the packet addr_log['last_seen'] = now addr_log['count'] += 1 # add the IP address to the request cache set - this set will be frequently flushed to DB self.server.ip_log.add(req_key) # if count >= threshold, ignore the packet, never respond if addr_log['count'] > self.server.threshold: return # log reaching of threshold and mark packet as last that will be accepted elif addr_log['count'] == self.server.threshold: last = True self.logger.info( 'Threshold reached for host %s and port %d - will not respond to this host' % (addr, dport)) log_msg = 'Last packet - threshold reached' else: # add host to transaction log first = True self.server.transaction_log[req_key] = {} self.server.transaction_log[req_key]['last_seen'] = now self.server.transaction_log[req_key]['count'] = 1 if self.random: # if multiplier is specified, response size is multiplied by request size # do not create response that is larger than 1 MB! resp_size = self.response_size if isinstance(resp_size, float): resp_size = min(int(len(data) * self.response_size), 1048576) response = random.randbytes(resp_size) else: # not random, use predefined response response = self.response resp_size = self.response_size sock.sendto(response, self.client_address) if first or last: b64_req = base64.b64encode(data) input_size = len(data) self.log_packet(log_msg, addr, port, dport, now, b64_req, input_size, resp_size, last) except Exception: t = traceback.format_exc() self.logger.error( 'Unknown error during communication with %s:%d - %s' % (addr, port, base64.b64encode(data))) self.logger.error('Stacktrace: %s' % t)
def handle(self): try: addr = self.client_address[0] port = self.client_address[1] data = self.request[0] sock = self.request[1] first = False last = False # no need to check for validity. any packet is accepted # IP addresses in transaction log and database will be stored as integers/long addr_int = utils.addr_to_int(addr) now = datetime.datetime.now() log_msg = 'New chargen packet received' # check if the request from this IP address was already received - ENTERING CRITICAL SECTION HERE! with self.server.tx_log_lock: if addr_int in self.server.transaction_log: addr_log = self.server.transaction_log[addr_int] # check if this is an already existing attack or a new attack # attack is classified as NEW if more than new_attack_duration_interval # minutes have passed since the last seen packet if addr_log[ 'last_seen'] + self.server.new_attack_interval < now: # consider this as a new attack, reset cache data first = True addr_log['count'] = 1 addr_log['last_seen'] = now log_msg = 'New attack detected' else: # update transaction log and database last-seen time and packet count and do not respond to the packet addr_log['last_seen'] = now addr_log['count'] += 1 # add the IP address to the request cache set - this set will be frequently flushed to DB self.server.ip_log.add(addr_int) # if count >= threshold, ignore the packet, never respond if addr_log['count'] > self.server.threshold: return # log reaching of threshold and mark packet as last that will be accepted elif addr_log['count'] == self.server.threshold: last = True self.logger.info( 'Threshold reached for host %s - will not respond to this host' % addr) log_msg = 'Last packet - threshold reached' else: # add host to transaction log first = True self.server.transaction_log[addr_int] = {} self.server.transaction_log[addr_int]['last_seen'] = now self.server.transaction_log[addr_int]['count'] = 1 # access needs to be synchronized since multiple threads can transform the alphabet # response is shifted alphabet, depending on response size and line length in config response = '' with self.server.alphabet_lock: for i in range(1 + int(self.response_size / self.line_len)): suf_len = min(self.line_len, abs(self.response_size - len(response) - 2)) response += self.server.alphabet[0:suf_len] + '\r\n' self.server.alphabet = self.server.alphabet[ 1:] + self.server.alphabet[0] # last line if suf_len < self.line_len: break sock.sendto(response.encode('ascii'), self.client_address) if first or last: b64_req = base64.b64encode(data) input_size = len(data) output_size = len(response) self.log_packet(log_msg, addr, port, now, b64_req, input_size, output_size, last) except Exception: t = traceback.format_exc() self.logger.error( 'Unknown error during communication with %s:%d - %s' % (addr, port, base64.b64encode(data))) self.logger.error('Stacktrace: %s' % t)