예제 #1
0
    def do_auth(self):
        """
        Handles the agent's authentication.
        """
        log.info(f"{self.client_address} initialized Authentication")
        challenge = os.urandom(128)
        self.request.sendall(Auth(challenge).pack())
        # wait for the client response

        self.msg = Structure.create(self.request)
        if not self.msg:
            # something's wrong with the message!
            self.send_status(Status.FAILED)
            return

        hmac_hash = hmac.new(self.server.secret_key, challenge, 'sha512')
        digest = hmac_hash.hexdigest().encode("utf-8")

        if hmac.compare_digest(digest, self.msg.data):
            log.info("Authenticated Successfully")
            self.authenticated = True
            self.send_status(Status.SUCCESS)
        else:
            self.send_status(Status.UNAUTHORIZED)
            self.request.close()
예제 #2
0
    def dispatcher(self):
        """
        Command dispatcher all logic to decode and dispatch the call.
        """
        self.msg = Structure.create(self.request)
        if not self.msg:
            self.connected = False
            log.info("Disconnected!")
            # mark any running task as interrupted
            # so that other agent can take it later
            self.ctx.interrupted(self.agent)
            return

        command_name = f"do_{self.msg.op_code.name.lower()}"
        if not hasattr(self, command_name):
            self.send_status(Status.FAILED)  # invalid command
            return

        command = getattr(self, command_name)
        # the only command authorized for unauthenticated agents
        if command_name != "do_AUTH" and not self.authenticated:
            self.send_status(Status.UNAUTHORIZED)
            self.connected = False
            self.request.close()
            return
        # call the command !
        command()
예제 #3
0
 def __getstate__(self):
     # Copy the object's state from self.__dict__ which contains
     # all our instance attributes. Always use the dict.copy()
     # method to avoid modifying the original state.
     log.info(f"saving file: {self._path} loc:{self.loc} state")
     state = self.__dict__.copy()
     # Remove the unpickable entries.
     del state['_fd']
     return state
예제 #4
0
    def save_context(self, ctx):
        """
        Serializes the context to resume later.

        :param ctx: instance of `Context`
        :type ctx: `Context`
        """
        log.info(f"Saving the current context {self.resume_path}")
        with open(self.resume_path, 'wb') as rfile:
            pickle.dump(ctx, rfile)
예제 #5
0
    def send_status(self, code):
        """
        Sends a status code to the server.

        :param code:
        :type code: `dscan.models.structures.Status`
        """
        log.info(f"Sending status code {code}")
        response = struct.pack("<B", code)
        self.request.sendall(response)
예제 #6
0
    def pop(self, agent):
        """
        Gets the next `Task` from the current Active Stage, if their are no
        pending `Tasks` to be executed.
        Pending tasks are tasks that are canceled or restored from a previous
        interrupted session.
        If a stage is finished (no more targets), the next stage will take
        another stage from the list until its finished.

        :param agent:
            str with ipaddress and port in ip:port format, this allows the
            server to manage multiple agents in one host.
            to run multiple clients at once.
        :return: A target to scan! `task`
        :rtype: `tuple`
        """
        with self._lock:
            task = None
            if agent in self.active:
                # This exists to make shore we don't lose targets.
                # this would be better if we knew how many tries a target
                # had faced. TODO
                log.info(f"Agent {agent} is requesting a new task with a "
                         f"task in execution sending it again!")
                task = self.active.get(agent)
                task.update(STATUS.SCHEDULED)
                return task.as_tuple()[2:]

            if len(self.pending) > 0:
                task = self.pending.pop(0)
            else:
                cstage = self.__cstage()
                if cstage:
                    task = cstage.next_task()
                    if not task:
                        # the only stage that needs to be finished
                        # to proceed is
                        # discovery as the other stages need the
                        # list of live hosts
                        if cstage.name != "discovery" or cstage.isfinished:
                            if cstage.isfinished:
                                cstage.process_results()
                                cstage.close()
                            cstage = self.__cstage(True)
                            if cstage:
                                task = cstage.next_task()

            # if we have a valid task save it in the active collection
            if task:
                self.active.update({agent: task})
                # the consumers only need scan related information...
                return task.as_tuple()[2:]
예제 #7
0
    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()
예제 #8
0
    def __setstate__(self, state):
        # Restore instance attributes (i.e., _path and nlines ...).
        self.__dict__.update(state)
        fd = None
        # if the loc is 0 then we have an uninitialized stage
        # that depends on unfinished stage, will be opened when the first
        # time a target is pulled.
        if self.loc != 0 and self.exists():
            # Restore the previously opened file's state.
            log.info(f"restoring file: {self._path} loc:{self.loc} state")
            fd = open(self._path, self.mode)

            # set the file to the prev location.
            fd.seek(self.loc)
        self._fd = fd
예제 #9
0
    def __getstate__(self):
        with self._lock:
            log.info("saving context state")
            state = self.__dict__.copy()
            for task in state['active'].values():
                task.update(STATUS.INTERRUPTED)
                state['pending'].append(task)

            # close file descriptors on all active stages
            for active_stage in state['active_stages'].values():
                active_stage.close()

            state['active'] = {}
            # Remove the unpickable entries.
            del state['_lock']
            return state
예제 #10
0
    def create(cls, sock):
        try:
            op_size = struct.calcsize(cls.HEADER)
            op_bytes = sock.recv(op_size)
            if len(op_bytes) == 0:
                # agent disconnected !
                return
            op, = struct.unpack(cls.HEADER, op_bytes)

            subs = cls.__subclasses__()
            for operation in subs:
                if operation.op_code.value == op:
                    return operation(sock=sock)
            return None
        except (struct.error, ValueError) as e:
            log.info("Error parsing the message %s" % e)
            return None
예제 #11
0
 def create(cls, options):
     """
     :param options: instance of `ServerConfig`
     :type options: ´ServerConfig´
     :return: instance of `Context`
     :rtype: ´Context`
     """
     rpath = options.resume_path
     if os.path.isfile(rpath) and os.stat(rpath).st_size > 0:
         log.info("Found resume file, loading...!")
         with open(options.resume_path, 'rb') as rfile:
             # i had to make this to make this testable with mocks!
             # load with file didn't work, some how!
             data = rfile.read()
             ctx = pickle.loads(data)
             return ctx
     else:
         return cls(options)
예제 #12
0
    def handle(self):
        """
        First method to be called by `BaseRequestHandler`.
        responsible for initial call to authentication `do_auth`,
        and `dispatcher`, the connection is kept alive as long as agent is
        connected and their are targets to be delivered.
        """
        log.info(f"{self.client_address} connected!")
        self.connected = True
        try:
            while self.is_connected:
                try:
                    # start by requesting authentication
                    if not self.authenticated:
                        self.do_auth()

                    self.dispatcher()
                except (socket.timeout, ConnectionError) as e:
                    log.info(f"{self.client_address} Timeout - {e}")
                    self.connected = False
                    # mark any running task as interrupted
                    # so that other agent can take it later
                    self.ctx.interrupted(self.agent)

                # wait a bit, in case a shutdown was requested!
                self._terminate.wait(1.0)
        finally:
            if self.ctx.is_finished:
                log.info("All stages are finished sending terminate event.")
                self.server.shutdown()
            self.request.close()
예제 #13
0
    def _update_task_status(self, agent, status):
        """
        Internal method updates  a task of a given stage status, its also
        responsible for managing the interrupted tasks.

        :param agent: str with ipaddress and port in ip:port format
        :param status: `STATUS` value to change.
        """
        with self._lock:
            task, tstage = self.__find_task_stage(agent)
            if task and tstage:
                task.update(status)
                if status == STATUS.COMPLETED:
                    tstage.inc_finished()
                    # clean the completed task
                    del self.active[agent]
                if status == status.INTERRUPTED:
                    log.info(f"Scan of {task.target} running on {agent} was "
                             f"interrupted")
                    self.pending.append(task)
                    del self.active[agent]
            else:
                log.debug(f"Agent {agent} is trying to update {status} on "
                          f"non existing task")
예제 #14
0
    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()
예제 #15
0
 def __setstate__(self, state):
     # restore the previous state, needed due to the existence of non
     # serializable objects
     self.__dict__.update(state)
     log.info("Restoring context state")
     self._lock = threading.Lock()
예제 #16
0
    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)