def doActiveScan(self, scanner, httpRequestResponse, insertionPoint):
        """
        Performs an active scan and stores issues found.

        Because the scanner fails sometimes with random errors when HTTP requests timeout and etcetera, we retry a couple of times. This allows us to scan faster because we can be more resilient to errors.

        Args:
            scanner: a IScannerCheck object as returned by extension.getActiveScanners().
            httpRequestResponse: the value to pass to doActiveScan. This should be the modified request, i.e. repeatedHttpRequestResponse.
            insertionPoint: the insertionPoint to scan.
        """
        retries = 5
        while retries > 0:
            utility.sleep(self.state, 1)
            try:
                issues = scanner.doActiveScan(httpRequestResponse,
                                              insertionPoint)
                with self.lock:
                    if issues:
                        for issue in issues:
                            self.callbacks.addScanIssue(issue)

                break
            except (java.lang.Exception, java.lang.NullPointerException):
                retries -= 1
                logging.error(
                    "Java exception while fuzzing individual param, retrying it. %d retries left."
                    % retries,
                    exc_info=True)
            except:
                retries -= 1
                logging.error(
                    "Exception while fuzzing individual param, retrying it. %d retries left."
                    % retries,
                    exc_info=True)
Exemple #2
0
 def __discover(self, delay = 1, daemon = False):
     while True:
         try:
             s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
             s.bind(('', self.this_device['PORT']))
             while True:
                 message = utility.strToDict(s.recvfrom(self.buffer_size)[0].decode('utf-8'))
                 if message and message['HOST'] != self.this_device['HOST']:
                     if message['HOST'] not in self.device_list or (message['HOST'] in self.device_list and self.device_list[message['HOST']]['STATUS'] != 'REACHABLE'):
                         self.device_list[message['HOST']] = message
                         try:
                             hs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                             hs.settimeout(2)
                             hs.sendto('PING!'.encode('utf-8'), (message['IP'], message['PORT'] + 1))
                             return_message = hs.recvfrom(self.buffer_size)
                             hs.settimeout(None)
                             if return_message[0].decode('utf-8') != 'PONG!':
                                 utility.warn("Handshake with device at [" + message['IP'] + ":" + str(message['PORT']) + "] Failed! Mark device [" + message['HOST'] + "] UNKNOWN" , False)
                                 self.device_list[message['HOST']]['STATUS'] = 'UNKNOWN'
                             else:
                                 self.device_list[message['HOST']]['STATUS'] = 'REACHABLE'
                                 if not daemon: utility.info("Discovered device [" + message['HOST'] + "] at [" + message['IP'] + "]")
                         except Exception as e:
                             if not daemon: utility.warn("Handshake with device at [" + message['IP'] + ":" + str(message['PORT']) + "] Failed! Mark device [" + message['HOST'] + "] UNREACHABLE" , False)
                             self.device_list[message['HOST']]['STATUS'] = 'UNREACHABLE'
                         finally:
                             hs.close()
                         break
             utility.sleep(delay, True)
         except Exception as e:
             utility.warn(str(e) + "\n>>> Continue ...")
         finally:
             s.close()
Exemple #3
0
def connectMXA(command, delayTime=5, daemon=False):
    if not daemon:
        utility.info("##################### " + Fore.YELLOW + "MXA Control" +
                     Style.RESET_ALL + " ######################")
    try:
        if not daemon:
            utility.info('Send command: [' + command +
                         '] to the MXA box at [' + str(TCP_IP) + ':' +
                         str(TELNET_PORT) + ']')
        tn = Telnet(TCP_IP, int(TELNET_PORT))
        ##tn.write(('\r\n').encode('ascii'))
        tn.read_until(b'SCPI>')
        tn.write((command + '\r\n').encode('ascii'))
        utility.sleep(delayTime, True)
        result = (utility.regexParser(tn.read_very_eager().decode('ascii'),
                                      ' (.*)\r\nSCPI.*',
                                      daemon)).replace('SCPI> ', '').replace(
                                          command, '').replace('\r\n', '')
        if result:
            if not daemon: utility.info('Response:\n' + result)
        else:
            result = ''
    except OSError:
        utility.error('Connection to ' + str(TCP_IP) + ':' + str(TELNET_PORT) +
                      ' Failed!')
        exit(1)
    tn.close()
    return result
Exemple #4
0
def connectSSH(commands,
               host="10.155.208.121",
               username="******",
               password="******",
               daemon=False):
    result = True
    if not daemon: utility.info("...SSH Connect to " + host)
    sshClient = pexpect.spawn('ssh -o StrictHostKeyChecking=no ' + username +
                              '@' + host)
    sshClient.expect("Password: "******":.*")
    sshClient.sendline("qrb_command_mode")
    sshClient.expect("#")
    if not daemon:
        if "Entered Into QRB Command Mode" in sshClient.before.decode('utf-8'):
            utility.info("Entered Into QRB Command Mode")
    for command in commands:
        sshClient.sendline(command)
        sshClient.expect("#")
        if not daemon:
            if "S\r" in sshClient.before.decode('utf-8'):
                utility.info('Command [' + command +
                             '] execution successful on host [' + host + ']')
            else:
                utility.warn('Command [' + command +
                             '] execution FAIL on host [' + host + ']',
                             track=False)
                result = False
    sshClient.sendline("exit")
    utility.sleep(1, daemon=True)
    sshClient.sendline("exit")
    return result
Exemple #5
0
    def resendAllButtonClicked(self, event):
        """
        Gets called when the user clicks the `Resend ALL` button. This initiates the main IDOR checking.

        Args:
            event: the event as passed by Swing. Documented here: https://docs.oracle.com/javase/7/docs/api/java/util/EventObject.html
        """
        if self.state.status == STATUS_FAILED:
            self.messageDialog("Confirm status check button says OK.")
            return

        endpoints = self.state.endpointTableModel.endpoints
        resendAllButton = event.source

        futures = []
        nb = 0
        for key in endpoints:
            endpoint = endpoints[key]
            for request in endpoint.requests:
                runnable = PythonFunctionRunnable(
                    resend_request_model,
                    args=[self.state, self.burpCallbacks, request])
                futures.append(self.state.executorService.submit(runnable))
                nb += 1

        while len(futures) > 0:
            utility.sleep(self.state, 1)
            resendAllButton.setText("%s remaining" % (len(futures)))

            for future in futures:
                if future.isDone():
                    futures.remove(future)

        resendAllButton.setText("Resent")
Exemple #6
0
 def __healthCheck(self, interval = 300, daemon = False):
     while True:
         utility.sleep(interval, True)
         for key in self.device_list:
             if key is 'SELF': next
             ## if self.device_list[key]['STATUS'] is 'UNREACHABLE': next # Skip the unreachable devices
             else:
                 try:
                     s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                     s.settimeout(2)
                     s.sendto('PING!'.encode('utf-8'), (self.device_list[key]['IP'], self.device_list[key]['PORT'] + 1))
                     message = s.recvfrom(self.buffer_size)
                     s.settimeout(None)
                     if message[0].decode('utf-8') != 'PONG!':
                         utility.warn("Health check device at [" + self.device_list[key]['IP'] + ":" + str(self.device_list[key]['PORT']) + "] not responding correctly! Mark device [" + key+ "] UNKNOWN" , False)
                         self.device_list[key]['STATUS'] = 'UNKNOWN'
                     else:
                         self.device_list[key]['STATUS'] = 'REACHABLE'
                 except Exception as e:
                     if key in self.device_list and self.device_list[key]['STATUS'] == 'REACHABLE':
                         utility.warn("Health check device at [" + self.device_list[key]['IP'] + "] failed! Mark device [" + key + "] UNREACHABLE" , False)
                         self.device_list[key]['STATUS'] = 'UNREACHABLE'
                 finally:
                     s.close()
         if not daemon:
             utility.info("        Health Check Result:\n--------------------------------------------------------------------------------")
             for device in self.device_list:
                 print(self.device_list[device]['STATUS'] + '    ' + self.device_list[device]['HOST'] + ': [' + self.device_list[device]['IP'] + ']')
Exemple #7
0
def mqtt_connect():
    global Client
    global MQTT_Timer

    Client = MQTTClient(CLIENT_ID, SERVICE_IP)
    Client.set_callback(mqtt_callback)

    while 1:
        try:
            Client.connect(clean_session=True)
            break
        except Exception as e:
            print(e)
            if "sock" in dir(Client):
                Client.sock.close()
            utility.sleep(1)

    mqtt_subscribe()
    utility.sleep(1)
    mqtt_publish()

    try:
        MQTT_Timer.deinit()
    except Exception as e:
        print(e)

    MQTT_Timer.init(mode=Timer.PERIODIC,
                    period=1000,
                    callback=mqtt_timer_callback)
    print("start to listeing...")
Exemple #8
0
def restart_mqtt_timer(delay: int):
    global MQTT_Timer
    MQTT_Timer.deinit()
    utility.sleep(delay)
    MQTT_Timer.init(mode=Timer.PERIODIC,
                    period=1000,
                    callback=mqtt_timer_callback)
    def fuzzEndpoints(self):
        """
        Fuzzes endpoints as present in the state based on the user preferences.

        We attempt to fuzz only one request per endpoint, using our own criteria to differentiate between endpoints as defined in `EndpontTableModel.generateEndpointHash`. For each endpoint, we iterate through requests until we can find a single request whose status code is the same between both the original and the repeated request, we only fuzz once. Requests are ordered based on the number of parameters they have, giving preference to those with the higher number of parameters in order to attempt to maximise attack surface.

        Returns:
            tuple: (int, int) number of items scanned and number of items for which the scan could not be completed due to an exception.
        """

        endpoints = self.state.endpointTableModel.endpoints

        futures = []
        endpointsNotReproducibleCount = 0
        nbFuzzedTotal = 0
        nbExceptions = 0
        for key in endpoints:
            endpoint = endpoints[key]
            nbExceptions += self.checkMaxConcurrentRequests(
                futures, 10
            )  # Only work on some futures at a time. This prevents a memory leak.

            if endpoint.fuzzed:
                continue

            sortedRequests = sorted(
                endpoint.requests,
                key=lambda x: len(x.analyzedRequest.parameters),
                reverse=True)

            fuzzed = False
            for request in sortedRequests:
                utility.sleep(self.state, 0.2)
                try:
                    resend_request_model(self.state, self.callbacks, request)
                except NoResponseException:
                    continue

                if request.wasReproducible():
                    endpointsNotReproducibleCount = 0
                    nbFuzzedTotal += 1

                    frm_futures = self.fuzzRequestModel(request)
                    futures.append((endpoint, request, frm_futures))

                    fuzzed = True
                    break

            if not fuzzed:
                endpointsNotReproducibleCount += 1
                log("Did not fuzz '%s' because no reproducible requests are possible with the current replacement rules"
                    % endpoint.url)

        nbExceptions += self.checkMaxConcurrentRequests(
            futures, 0)  # ensure all requests are done.

        return nbFuzzedTotal, nbExceptions
Exemple #10
0
def mqtt_timer_callback(timer):
    print("------------------")
    try:
        Client.check_msg()
    except Exception as e:
        print(e)
        Client.sock.close()
        Client.connect()
        mqtt_subscribe()
        utility.sleep(0.5)
        mqtt_publish()
    UI_Logic()
Exemple #11
0
 def __broadcast(self, delay = 1, daemon = False):
     try:
         s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
         s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
         if not daemon: utility.info("Starting broadcasting at: [" + self.this_device['BROADCAST_IP'] + ":" + str(self.this_device['PORT']) + "]")
         while True:
             self.__loadConfig(self.this_device['PORT'])
             s.sendto(str(self.this_device).encode('utf-8'), (self.this_device['BROADCAST_IP'], int(self.this_device['PORT'])))
             utility.sleep(delay, True)
     except Exception as e:
         utility.warn(str(e) + "\n>>> Continue ...")
     finally:
         s.close()
    def fuzzRequestModel(self, request):
        """
        Sends a RequestModel to be fuzzed by burp.

        Burp has a helper function for running active scans, however I am not using it for two reasons. Firstly, as of 2.x the mechanism for configuring scans got broken in a re-shuffle of burp code. Secondly, burp's session handling for large scans is not perfect, once the session expires the scan continues to fuzz requests with an expired session, and implementing my own session handling on top of IScannerCheck objects is not possible due to a bug in getStatus() where requests that have errored out still have a "scanning" status. If these issues are resolved we can get rid of this workaround.

        We work around this by importing extensions' JAR files and interacting with them using the same APIs that burp uses.

        Args:
            request: an instance of RequestModel.
        """
        utility.sleep(self.state, 0.2)

        insertionPointsGenerator = InsertionPointsGenerator(self.callbacks)

        futures = []
        for name, extension in self.extensions:
            for activeScanner in extension.getScannerChecks():
                if name == "shelling":
                    onlyParameters = True
                else:
                    onlyParameters = False

                insertionPoints = insertionPointsGenerator.getInsertionPoints(
                    request, onlyParameters)

                for insertionPoint in insertionPoints:
                    runnable = PythonFunctionRunnable(
                        self.doActiveScan,
                        args=[
                            activeScanner, request.repeatedHttpRequestResponse,
                            insertionPoint
                        ])
                    futures.append(
                        self.state.fuzzExecutorService.submit(runnable))

            for factory in extension.getContextMenuFactories():
                if name == "paramminer":
                    menuItems = factory.createMenuItems(
                        ContextMenuInvocation(
                            [request.repeatedHttpRequestResponse]))
                    for menuItem in menuItems:
                        # trigger "Guess headers/parameters/JSON!" functionality.
                        runnable = PythonFunctionRunnable(menuItem.doClick)
                        futures.append(
                            self.state.fuzzExecutorService.submit(runnable))

        return futures
Exemple #13
0
def main():
	global PORT, debug_flag, NAME, TIMEOUT
	parser = argparse.ArgumentParser(description='SAuto Framework CLI tools')
	parser.add_argument('-e', '--execute', metavar='Command', nargs='+', help='Execute the remote command on sauto Devices')
	parser.add_argument('-n', '--name', metavar='Device Hostname', help='The hostname of the remote device for execute command, if not given, execute on self')
	parser.add_argument('-p', '--port', metavar='Port Number', type=int, help='The port for initialize the SAuto Framework. The communication port is always given port number add 1')
	parser.add_argument('-t', '--timeout', metavar='Timeout in seconds', type=int, help='The timeout value when executing command on remote device')
	parser.add_argument('-d', '--debug', metavar='[INFO/CLIENT/DEBUG/DAEMON]', help='Set debug flag : [INFO/CLIENT/DEBUG/DAEMON], else using default INFO')
	parser.add_argument('-D', '--daemon', dest='DAEMON', action='store_true', help='Execute the SAuto in daemon mode')
	args = parser.parse_args()
	## Varify the port number?
	if args.port: PORT = args.port
	if args.debug in ['INFO', 'CLIENT', 'DEBUG', 'DAEMON']: debug_flag = args.debug
	if args.name: NAME = args.name
	if args.timeout: TIMEOUT = args.timeout
	sauto = SAuto(port = PORT, debug = debug_flag)
	utility.sleep(3, True)
	if args.execute:
		sauto.execute(sauto.getDevice(NAME), ' '.join(args.execute), timeout = TIMEOUT)
	if args.DAEMON:
		while True: utility.sleep(30, True)
Exemple #14
0
def connectJFW(command, delayTime=2, daemon=False):
    if not daemon:
        utility.info("###################### " + Fore.YELLOW + 'JFW Control' +
                     Style.RESET_ALL + " #####################")
    MESSAGE = (command + '\r\n').encode('ascii')
    try:
        if not daemon:
            utility.info('Send command: [' + command +
                         '] to the JFW box at [' + str(TCP_IP) + ':' +
                         str(TCP_PORT) + ']')
        tn = Telnet(TCP_IP, int(TCP_PORT))
        tn.write(MESSAGE)
        utility.sleep(delayTime, daemon=True)
        result = tn.read_very_eager().decode('ascii')
        if not daemon: utility.info('Response:\n' + result)
        tn.close()
    except Exception as e:
        utility.error(
            str(e) + ' - Connection to ' + str(TCP_IP) + ':' + str(TCP_PORT) +
            ' Failed!')
        result = str(
            e) + ' - JFW does not allow multiple login on the same device!'
    return result
    def run(self):
        print("[EventsProcessor]: started")
        while (True):
            try:
                sleep(3)
                current_number_of_history = len(
                    self.histories.get_history_list())
                # check last message time with current time
                diff_in_sec = currrent_time_secs(
                ) - self.histories.get_latest_history_entry_time()

                if current_number_of_history == self.total_processed_history:  # No new data
                    # print("[EventsProcessor] running : No new data since {} secs".format(
                    #     diff_in_sec))
                    continue

                # Check is there 10 sec gaps between two message.
                if diff_in_sec <= 10:
                    print(
                        "[EventsProcessor] new data found, waiting {} secs for more data"
                        .format(diff_in_sec))
                    self.wait_counter += self.wait_counter
                    # Wait maximum 50 sec, If data is continously comming , time diff always will be less then 10 sec
                    if self.wait_counter < 5:
                        continue

                # Reset wait count
                self.wait_counter = 0

                # Load the temp history into distributed board
                # My Choice: Casual Ordering
                # VC from server1 = [1,0,0,1], VC from server2 = [1,0,0,0], So server2 -> server1 (server2 precced server1)
                # VC from server1 = [1,0,0,1], VC from server2 = [1,0,1,0], same oder, in that case odered by server ID ascending
                #    server1 precced server2

                total_data_to_process = current_number_of_history - self.total_processed_history
                if total_data_to_process > 0:
                    print(
                        "[EventsProcessor]: Processing started, #data to process: {} "
                        .format(total_data_to_process))

                    # temp_history: [Data] = self.histories.get_history_list(
                    # )[self.total_processed_history:current_number_of_history].copy()
                    # # self.histories.clearHistory()
                    # self.total_processed_history = current_number_of_history
                    # temp_history.sort(key=lambda log: (
                    #     log.vector_sum, log.server_id))

                    # for data in temp_history:
                    #     print("[EventsProcessor]: processing ,text = {} , vc = {} , server_id = {}, action_type = {}".format(
                    #         data.text, data.vector_clock, data.server_id, data.action_type))
                    #     if data.action_type == ActionType.ADD:
                    #         self.distributed_board.add_on_board(data)
                    #     elif data.action_type == ActionType.UPDATE:
                    #         ret = self.distributed_board.update_text_from_borad(
                    #             data)
                    #         if not ret:
                    #             print("[EventsProcessor]: update failed, element_id = {}".format(
                    #                 data.element_id))
                    #     elif data.action_type == ActionType.DELETE:
                    #         self.distributed_board.delete_if_exist(
                    #             data.element_id)

                    ## For Huge amount of data the following process is not Appropriate, Because I am processing all the data every time

                    self.total_processed_history = current_number_of_history

                    # Load the temp history into distributed board
                    # My Choice: Casual Ordering
                    # VC from server1 = [1,0,0,1], VC from server2 = [1,0,0,0], So server2 -> server1 (server2 precced server1)
                    # VC from server1 = [1,0,0,1], VC from server2 = [1,0,1,0], same oder, in that case odered by server ID ascending
                    #    server1 precced server2
                    self.histories.get_history_list().sort(
                        key=lambda log: (log.vector_sum, log.server_id))
                    temp_dictionary = dict()
                    for data in self.histories.get_history_list():
                        print(
                            "[EventsProcessor]: processing ,text = {} , vc = {} , server_id = {}, action_type = {}"
                            .format(data.text, data.vector_clock,
                                    data.server_id, data.action_type))
                        if data.action_type == ActionType.ADD:
                            temp_dictionary[data.element_id] = data
                        elif data.action_type == ActionType.UPDATE:
                            if data.element_id in temp_dictionary:
                                temp_dictionary[
                                    data.element_id].text = data.text
                        elif data.action_type == ActionType.DELETE:
                            if data.element_id in temp_dictionary:
                                del temp_dictionary[data.element_id]

                    self.distributed_board.board_data = temp_dictionary

                    ## just for Task4 time calculation
                    if len(self.distributed_board.board_data) == 40:
                        set_last_entry_time()
                        last_message_time = get_time_difference_between_first_and_last_entry(
                        )
                        print(" first {}, last {} , diff {}".format(
                            Constants.first_entry_time_in_second,
                            Constants.last_entry_time, last_message_time))
                else:
                    print("[EventsProcessor]: No data yet")
            except Exception as ex:
                print_stack_trace(ex)
            finally:
                pass
Exemple #16
0
def main():
    global JFW_TABLE_NAME
    parser = argparse.ArgumentParser(
        description='Tools for controlling JFW box')
    parser.add_argument(
        '--REBOOT',
        dest='REBOOT',
        action='store_true',
        help='Reboot the JFW system, has a 20 seconds delay after reboot')
    parser.add_argument(
        '-d',
        '--delay',
        nargs='?',
        const=2,
        metavar='Seconds',
        type=int,
        help=
        'Set the system delay time waiting for JFW box responding, default 2 seconds'
    )
    parser.add_argument('-e',
                        '--execute',
                        metavar='Command',
                        nargs='+',
                        help='Execute the remote command on JFW box')
    parser.add_argument('-H',
                        '--health',
                        dest='HEALTH',
                        action='store_true',
                        help='Health check all attenuates status')
    parser.add_argument(
        '-s',
        '--sql',
        metavar='SQLite_File_Path',
        help=
        'Load the SQLite database path instead of configuration json file. Using parameter None or null to use default database'
    )
    parser.add_argument(
        '-n',
        '--name',
        metavar='SQLite_Table_Name',
        help='Define the name of the table to be load when loading the SQLite')
    parser.add_argument(
        '-i',
        '--id',
        metavar='JFW_ID#',
        type=int,
        help='The id number of the JFW device in SQLite database, default is 1'
    )
    args = parser.parse_args()
    delayTime = 2
    jid = 1
    if len(sys.argv) < 2: parser.print_help()
    if args.delay: delayTime = args.delay
    if args.id: jid = args.id
    if args.name: JFW_TABLE_NAME = args.name
    if args.sql:
        if args.sql is 'None' or 'none' or 'default' or 'Default' or 'null':
            loadSQLite(jid)
        else:
            loadSQLite(jid, args.sql)
    if args.REBOOT:
        connectJFW('REBOOT', delayTime)
        utility.sleep(20, daemon=True)
    if args.HEALTH: healthCheck()
    if args.execute: connectJFW(' '.join(args.execute), delayTime)
Exemple #17
0
        ui_page.cancel_OK_state()

        database.set(StoreKey.reset, "false")
        database.commit()
        utility.reboot()
    else:
        ui_page.swich_page(ui_page.PAGENAMES.only_label)
        ui_page.set_label_text("connecting...")

        logic_mqtt.update_info()
        print("info updated")

        MQTT_OK = False
        if logic_wifi.is_wifi_ok():
            if utility.is_port_open(database.get(StoreKey.service_ip), "1883"):
                utility.sleep(1)
                logic_mqtt.mqtt_connect()
                print("mqtt connected")
                utility.sleep(1)
                MQTT_OK = True
            else:
                MQTT_OK = False

        print("we got all info that we need")
        ui_page.swich_page(ui_page.PAGENAMES.verify_password)

        def abc():
            condition = logic_mqtt.GateName in logic_mqtt.SubscribeDict[
                "doors_that_open"]
            if (condition):
                ui_page.set_label_text("The door is open")
    def checkMaxConcurrentRequests(self, futures, maxRequests):
        """
        Blocking function that waits until we can add more futures.

        It is in charge of marking requests as fuzzed once completed.

        Args:
            futures: futures as defined in `fuzzButtonClicked`
            maxRequests: maximum requests that should be pending at this time. If we have more futures than this number, this function will block until the situation changes. We check for changes by calling `isDone()` on each of the available futures.

        Return:
            int: number of exceptions thrown during scan. 0 means no errors.
        """
        nbExceptions = 0
        while len(futures) > maxRequests:
            utility.sleep(self.state, 0.5)
            for tuple in futures:
                endpoint, request, frm_futures = tuple
                request_done = False
                for future in frm_futures:
                    if future.isDone():
                        try:
                            future.get()
                        except ExecutionException as exc:
                            logging.error("Failure fuzzing %s" % endpoint.url,
                                          exc_info=True)
                            sendMessageToSlack(
                                self.callbacks,
                                "Failure fuzzing %s, exception: %s" %
                                (endpoint.url, exc.cause))

                            nbExceptions += 1

                        frm_futures.remove(future)

                    if len(frm_futures) == 0:
                        request_done = True

                if request_done:
                    futures.remove(tuple)
                    resend_request_model(self.state, self.callbacks, request)

                    textAreaText = self.state.sessionCheckTextarea.text
                    sessionCheckReproducible, _ = resend_session_check(
                        self.state, self.callbacks, textAreaText)

                    if request.wasReproducible() and sessionCheckReproducible:
                        self.state.endpointTableModel.setFuzzed(endpoint, True)
                        log("Finished fuzzing %s" % endpoint.url)
                    elif not request.wasReproducible():
                        log("Fuzzing complete but did not mark as fuzzed because no longer reproducible at %s."
                            % endpoint.url)
                    else:
                        log("Fuzzing complete but did not mark as fuzzed because the session check request is no longer reproducible."
                            )
                        raise SessionCheckNotReproducibleException(
                            "Base request no longer reproducible.")

                    break

        return nbExceptions