def __walk(self): """ information. :yield: A list with the filtered values :rtype: `list` """ for report in os.scandir(self.path): if fnmatch.fnmatch(report.name, self.pattern): try: nmap_report = NmapParser.parse_fromfile(report.path) yield from nmap_report.hosts except NmapParserException as ex: log.error(f"Error parsing {report} - {ex}")
def run(self, target, options, callback): """ Executes the scan on a given target. :param target: :param options: :param callback: callback function to report status to the server. :return: report object :rtype: `dscan.models.structures.Report` """ self.ctarget = (target, options) nmap_proc = None try: options = " ".join([options, f"-oN {self.report_name('nmap')}"]) nmap_proc = NmapProcess(targets=target, options=options, safe_mode=False, event_callback=self.show_status) log.info("Nmap scan started Sending success status") callback(Status.SUCCESS) rc = nmap_proc.run() if rc == 0: # after finished encode and hash the contents for transfer. self.__inc() data = nmap_proc.stdout.encode("utf-8") report_file = self.report_name("xml") with open(self.report_name("xml"), "wb") as rfile: rfile.write(data) rfile.flush() digest = hashlib.sha512(data).hexdigest() report = Report(len(data), os.path.basename(report_file), digest) self.print(target, 100) return report else: callback(Status.FAILED) log.error(f"Nmap Scan failed {nmap_proc.stderr}") except Exception as ex: log.error(f"something went wrong {ex}") callback(Status.FAILED) finally: if nmap_proc: nmap_proc.stop() # orthodox fix NmapProcess is leaving subprocess streams open. subproc = getattr(nmap_proc, "_NmapProcess__nmap_proc") if subproc: subproc.stdout.close() subproc.stderr.close()
def do_ready(self): """ After the authentication the agent notifies the server, that is ready to start scanning. This will handle the request and send a target to be scanned. """ log.info("is Ready for targets") log.info(f"Agent is running with uid {self.msg.uid}") if self.msg.uid != 0: log.info("Waning! agent is not running as root " "syn scans might abort not enough privileges!") target_data = self.ctx.pop(self.agent) if not target_data: if self.ctx.is_finished: log.info("Target is None and all stages are finished") # send empty command and terminate! cmd = Command("", "") self.request.sendall(cmd.pack()) self.connected = False else: log.info("Waiting for a stage to finish") cmd = ExitStatus(Status.UNFINISHED) self.request.sendall(cmd.pack()) return cmd = Command(*target_data) self.request.sendall(cmd.pack()) status_bytes = self.request.recv(1) if len(status_bytes) == 0: self.connected = False log.info("Disconnected!") self.ctx.interrupted(self.agent) return status, = struct.unpack("<B", status_bytes) if status == Status.SUCCESS.value: log.info("Started scanning !") self.ctx.running(self.agent) else: log.error("Scan command returned Error") log.info("Server is Terminating connection!") self.connected = False self.ctx.interrupted(self.agent)
def __cstage(self, force_next=False): """ :param force_next: if True wil force the stage to advance one step defaults to False. :type force_next: `bool` :return: An instance of `stage` :rtype: `stage` """ try: if not self.cstage_name or force_next: stage = self.stage_list.pop(0) self.active_stages[stage.name] = stage self.cstage_name = stage.name return self.active_stages[self.cstage_name] except IndexError: log.error("Stage list is empty !") return None
def get_report(self, agent, file_name): """ :param agent: str with ipaddress and port in ip:port format :type agent: `str` :param file_name: name of the file sent by the agent. :type file_name: `str` :return: file descriptor to save the scan report. """ try: _, tstage = self.__find_task_stage(agent) file_name = f"{tstage.name}-{file_name}" report_file = open(os.path.join(self.reports_path, file_name), "wb") return report_file except Exception as ex: log.error(f"Unable to open report for {file_name}") log.error(f"{ex}") return None
def save(self, targets): """ Takes a list of targets to optimize and saves it in the workspace path. :param targets: `list` of targets (`str` and top optimize. :type: targets: `list` of `str` """ assert targets, "Empty target list" ips = [] with open(self.fpath, 'wt') as qfile: for target in targets: try: if "/" in target: net = ipaddress.ip_network(target.strip()) if net.prefixlen < 24: subs = map(lambda n: f"{n.with_prefixlen}\n", net.subnets(new_prefix=24)) qfile.writelines(subs) else: qfile.write(f"{net.with_prefixlen}\n") else: ips.append(ipaddress.ip_address(target.strip())) except (TypeError, ValueError): log.error(f"Error optimizing target: {target}") # sorting the ip addresses. ips.sort(key=ipaddress.get_mixed_type_key) # find consecutive ip address ranges. if ips: for first, last in ipaddress._find_address_range(ips): ip_range = list( ipaddress.summarize_address_range(first, last)) # if the number of ranges is more than one network in cidr # format then the glob format x.x.x.x-y is more efficient, # since nmap supports this format. if len(ip_range) > 1: qfile.write(f"{first}-{last.exploded.split('.')[3]}\n") else: qfile.write(f"{ip_range.pop().with_prefixlen}\n")
def __init__(self, config, options): """ Holds the configuration parameters used at runtime for both server and agent! :param config: configparser with the configuration :type config: `configparser.ConfigParser` :param options: argument parser `argparse.ArgumentParser` with the user options """ self.wspace = options.name self.port = options.p self.outdir = os.path.join(options.name, config.get(*self.BASE)) os.makedirs(self.outdir, exist_ok=True) self.config = None if options.cmd == 'srv': self.config = ServerConfig(config, options, self.outdir) self.sslkey = self.get_work_path( config.get(*self.SSL_CERTS[0:3:2])) assert os.path.isfile( self.sslkey), "Certificate Private key not found" self.ciphers = config.get(*self.SSL_CERTS[0:4:3]) else: self.host = options.s # set cert properties self.sslcert = self.get_work_path(config.get(*self.SSL_CERTS[0:2:1])) self.srv_hostname = config.get(*self.SSL_CERTS[0:5:4]) assert os.path.isfile(self.sslcert), "Certificate file not found" digest: hashlib.sha512 = hashlib.sha512() try: with open(self.sslcert, 'rt') as cert: digest.update(cert.read().strip().encode("utf-8")) self.secret_key = digest.hexdigest().encode("utf-8") except OSError as ex: log.error(f"failed to open cert file {ex}") raise ex
def do_report(self): """ When the scan the ends, the agent notifies the server that is ready to send the report. This method will handle the report transfer save the report in the reports directory and make the target as finished if the file hashes match. """ log.info("Agent Reporting Complete Scan!") log.info(f"Filename {self.msg.filename} total file size " f"{self.msg.filesize} file hash {self.msg.filehash}") file_size = self.msg.filesize nbytes = 0 report = self.ctx.get_report(self.agent, self.msg.filename.decode("utf-8")) try: digest = hashlib.sha512() self.ctx.downloading(self.agent) while nbytes < file_size: data = self.request.recv(1024) report.write(data) digest.update(data) nbytes = nbytes + len(data) if not hmac.compare_digest(digest.hexdigest().encode("utf-8"), self.msg.filehash): log.error(f"Files are not equal! {digest.hexdigest()}") self.send_status(Status.FAILED) else: log.info("files are equal!") self.ctx.completed(self.agent) self.send_status(Status.SUCCESS) finally: if report: report.flush() report.close()