Beispiel #1
0
    def delay_for(self, delay, original_wait_time):
        """
        Sends a request to the remote end that "should" delay the response in
        :param seconds.

        :param original_wait_time: The time that it takes to perform the
                                   request without adding any delays.

        :return: (True, response) if there was a delay. In order to make
                 things right we first send some requests to measure the
                 original wait time.
        """
        delay_str = self.delay_obj.get_string_for_delay(delay)
        mutant = self.mutant.copy()
        mutant.set_token_value(delay_str)

        #    Send, it is important to notice that we don't use the cache
        #    to avoid any interference
        response = self.uri_opener.send_mutant(mutant, cache=False)

        #    Test
        delta = original_wait_time * self.DELTA_PERCENT
        current_response_wait_time = response.get_wait_time()

        upper_bound = (delay * 2) + original_wait_time + delta
        lower_bound = original_wait_time + delay - delta

        args = (id(self), upper_bound, current_response_wait_time, lower_bound)
        out.debug('(Test id: %s) %s > %s > %s' % args)

        if upper_bound > current_response_wait_time > lower_bound:
            return True, response

        return False, response
Beispiel #2
0
 def _log_generic(self, msg, delay, response):
     args = (self._debugging_id,
             id(self),
             self.mutant.get_url(),
             self.mutant.get_token_name(),
             delay,
             self.delay_obj,
             response.get_wait_time(),
             response.id)
     out.debug(msg % args)
Beispiel #3
0
class ExactDelayController(DelayMixIn):
    """
    Given that more than one vulnerability can be detected using time delays,
    just to name a couple blind SQL injections and OS commandings, I decided to
    create a generic class that will help me detect those vulnerabilities in an
    accurate and generic manner.

    This class works for EXACT time delays, this means that we control "exactly"
    how many seconds the remote server will "sleep" before returning the
    response. A good example to understand this is MySQL's sleep(x) vs.
    benchmark(...).
    """

    # 25% more/less than the original wait time
    DELTA_PERCENT = 1.25

    #
    # Note that these delays are applied ONLY if all the previous delays worked
    # so adding more here will only increase accuracy and not performance since
    # you'll only get slower scans when there is a vulnerability, which is not
    # the most common case
    #
    DELAY_SECONDS = [12, 8, 15, 20, 4, 4, 4]

    def __init__(self, mutant, delay_obj, uri_opener):
        """
        :param mutant: The mutant that will be sent (one or more times) to the
                       remote server in order to detect the time delay.

        :param delay_obj: A delay object as defined in delay.py file. Basically
                          an object that contains the string that would delay
                          the remote server (ie. sleep(%s) )
        """
        if not isinstance(delay_obj, ExactDelay):
            raise TypeError(
                'ExactDelayController requires ExactDelay as input')

        self.mutant = mutant
        self.mutant.set_token_value(mutant.get_token().get_original_value())

        self.delay_obj = delay_obj
        self.uri_opener = uri_opener

    def delay_is_controlled(self):
        """
        All the logic/magic is in this method. The logic is very simple:
            * Try to delay the response in 4 seconds, if it works
            * Try to delay the response in 1 seconds, if it works
            * Try to delay the response in 6 seconds, if it works
              (note that these delays are actually determined by DELAY_SECONDS)
            * Then we have found a vulnerability!

        We go up and down and change the amount of seconds in order to make sure
        that WE are controlling the delay and there is no other external factor
        in place.
        """
        responses = []

        for delay in self.DELAY_SECONDS:
            # Update the wait time before each test
            original_wait_time = self.get_original_time()

            success, response = self.delay_for(delay, original_wait_time)
            if success:
                self._log_success(delay, response)
                responses.append(response)
            else:
                self._log_failure(delay, response)
                return False, []

        return True, responses

    def _log_success(self, delay, response):
        msg = (u'(Test id: %s) Successfully controlled HTTP response delay for'
               u' URL %s - parameter "%s" for %s seconds using %r, response'
               u' wait time was: %s seconds and response ID: %s.')
        self._log_generic(msg, delay, response)

    def _log_failure(self, delay, response):
        msg = (u'(Test id: %s) Failed to control HTTP response delay for'
               u' URL %s - parameter "%s" for %s seconds using %r, response'
               u' wait time was: %s seconds and response ID: %s.')
        self._log_generic(msg, delay, response)

    def _log_generic(self, msg, delay, response):
        args = (id(self), self.mutant.get_url(), self.mutant.get_token_name(),
                delay, self.delay_obj, response.get_wait_time(), response.id)
        out.debug(msg % args)

    def delay_for(self, delay, original_wait_time):
        """
        Sends a request to the remote end that "should" delay the response in
        `delay` seconds.

        :param original_wait_time: The time that it takes to perform the
                                   request without adding any delays.

        :return: (True, response) if there was a delay. In order to make
                 things right we first send some requests to measure the
                 original wait time.
        """
        delay_str = self.delay_obj.get_string_for_delay(delay)
        mutant = self.mutant.copy()
        mutant.set_token_value(delay_str)

        # Set the upper and lower bounds
        delta = original_wait_time * self.DELTA_PERCENT

        upper_bound = (delay * 2) + original_wait_time + delta
        lower_bound = original_wait_time + delay - delta

        # Send, it is important to notice that we don't use the cache
        # to avoid any interference
        try:
            response = self.uri_opener.send_mutant(mutant,
                                                   cache=False,
                                                   timeout=upper_bound * 10)
        except HTTPRequestException, hre:
            # NOTE: In some cases where the remote web server timeouts we reach
            #       this code section. The handling of that situation is done
            #       with the new_no_content_resp() below.
            return False, new_no_content_resp(self.mutant.get_uri())

        # Test if the delay worked
        current_response_wait_time = response.get_wait_time()
        args = (id(self), current_response_wait_time, lower_bound)
        out.debug(u'(Test id: %s) %s > %s' % args)

        if current_response_wait_time > lower_bound:
            return True, response

        return False, response
Beispiel #4
0
def debug(msg):
    if DEBUG:
        out.debug(msg)

        if is_running_tests():
            print(msg)
Beispiel #5
0
 def _log_generic(self, msg, delay, response):
     args = (id(self), self.mutant.get_url(), self.mutant.get_var(), delay,
             self.delay_obj, response.get_wait_time())
     out.debug(msg % args)
    def delay_for(self, delay, original_wait_time, grep, reverse=False):
        """
        Sends a request to the remote end that "should" delay the response in
        `delay` seconds.

        :param delay: The delay object
        :param original_wait_time: The time that it takes to perform the
                                   request without adding any delays.
        :param grep: Should the framework grep the HTTP response sent for testing?
        :param reverse: Should we reverse the delay_str before sending it?

        :return: (True, response) if there was a delay. In order to make
                 things right we first send some requests to measure the
                 original wait time.
        """
        delay_str = self.delay_obj.get_string_for_delay(delay)
        delay_str = delay_str if not reverse else delay_str[::-1]

        mutant = self.mutant.copy()
        mutant.set_token_value(delay_str)

        # Set the upper and lower bounds
        delta = original_wait_time * self.DELTA_PERCENT

        # Upper bound is the highest number we'll wait for a response, it
        # doesn't mean that it is the highest delay that might happen on
        # the application.
        #
        # So, for example if the application logic (for some reason) runs
        # our payload three times, and we send:
        #
        #   sleep(10)
        #
        # The delay will be of 30 seconds, but we don't want to wait all
        # that time (using high timeouts for HTTP requests is *very* bad when
        # scanning slow apps).
        #
        # We just wait until `upper_bound` is reached
        upper_bound = original_wait_time + delta + delay * 2

        # The lower_bound is the lowest number of seconds we require for this
        # HTTP response to be considered "delayed".
        #
        # I tried with different variations of this, the first one included
        # original_wait_time and delta, but that failed in this scenario:
        #
        #   * RTT (which defines original_wait_time) is inaccurately high: 3 seconds
        #     instead of 1 second which was the expected result. This could be
        #     because of outliers in the measurement
        #
        #           https://github.com/andresriancho/w3af/issues/16902
        #
        #   * lower_bound is then set to original_wait_time - delta + delay
        #
        #   * The payload is sent and the response is delayed for 4 seconds
        #
        #   * The delay_for method yields false because of the bad RTT
        lower_bound = delay

        # Send, it is important to notice that we don't use the cache
        # to avoid any interference
        try:
            response = self.uri_opener.send_mutant(
                mutant,
                grep=grep,
                cache=False,
                timeout=upper_bound,
                debugging_id=self.get_debugging_id())
        except HTTPRequestException:
            #
            # We reach this part of the code when the server response times out
            #
            # Note that in the timeout parameter of send_mutant we're sending
            # the upper bound, so we reach this code if that upper bound is
            # exceeded. That can be because of:
            #
            #   * Network delays unrelated to our payload
            #
            #   * Our payload having an effect on the server, delaying the
            #     response so much that it triggers the timeout
            #
            args = (id(self), upper_bound, lower_bound, delay, upper_bound)
            msg = (u'[id: %s] HTTP response delay was %.2f.'
                   u' (lower, expected, upper): %.2f, %.2f, %.2f.')
            out.debug(msg % args)

            return True, new_no_content_resp(self.mutant.get_uri())

        # We reach this code when the HTTP timeout wasn't reached, but we might still
        # have a working delay. This is most of the cases I've seen.
        current_response_wait_time = response.get_wait_time()
        args = (id(self), current_response_wait_time, lower_bound, delay,
                upper_bound)
        msg = (u'[id: %s] HTTP response delay was %.2f.'
               u' (lower, expected, upper): %.2f, %.2f, %.2f.')
        out.debug(msg % args)

        if current_response_wait_time > lower_bound:
            return True, response

        return False, response
    def delay_for(self, delay, original_wait_time, grep, reverse=False):
        """
        Sends a request to the remote end that "should" delay the response in
        `delay` seconds.

        :param delay: The delay object
        :param original_wait_time: The time that it takes to perform the
                                   request without adding any delays.
        :param grep: Should the framework grep the HTTP response sent for testing?
        :param reverse: Should we reverse the delay_str before sending it?

        :return: (True, response) if there was a delay. In order to make
                 things right we first send some requests to measure the
                 original wait time.
        """
        delay_str = self.delay_obj.get_string_for_delay(delay)
        delay_str = delay_str if not reverse else delay_str[::-1]

        mutant = self.mutant.copy()
        mutant.set_token_value(delay_str)

        # Set the upper and lower bounds
        delta = original_wait_time * self.DELTA_PERCENT

        # Upper bound is the highest number we'll wait for a response, it
        # doesn't mean that it is the highest delay that might happen on
        # the application.
        #
        # So, for example if the application logic (for some reason) runs
        # our payload three times, and we send:
        #
        #   sleep(10)
        #
        # The delay will be of 30 seconds, but we don't want to wait all
        # that time (using high timeouts for HTTP requests is *very* bad when
        # scanning slow apps).
        #
        # We just wait until `upper_bound` is reached
        upper_bound = original_wait_time + delta + delay * 2

        # The lower_bound is the lowest number of seconds we require for this
        # HTTP response to be considered "delayed".
        #
        # I tried with different variations of this, the first one included
        # original_wait_time and delta, but that failed in this scenario:
        #
        #   * RTT (which defines original_wait_time) is inaccurately high: 3 seconds
        #     instead of 1 second which was the expected result. This could be
        #     because of outliers in the measurement
        #
        #           https://github.com/andresriancho/w3af/issues/16902
        #
        #   * lower_bound is then set to original_wait_time - delta + delay
        #
        #   * The payload is sent and the response is delayed for 4 seconds
        #
        #   * The delay_for method yields false because of the bad RTT
        lower_bound = delay

        # Send, it is important to notice that we don't use the cache
        # to avoid any interference
        try:
            response = self.uri_opener.send_mutant(mutant,
                                                   grep=grep,
                                                   cache=False,
                                                   timeout=upper_bound,
                                                   debugging_id=self.get_debugging_id())
        except HTTPRequestException:
            #
            # We reach this part of the code when the server response times out
            #
            # Note that in the timeout parameter of send_mutant we're sending
            # the upper bound, so we reach this code if that upper bound is
            # exceeded. That can be because of:
            #
            #   * Network delays unrelated to our payload
            #
            #   * Our payload having an effect on the server, delaying the
            #     response so much that it triggers the timeout
            #
            args = (id(self), upper_bound, lower_bound, delay, upper_bound)
            msg = (u'[id: %s] HTTP response delay was %.2f.'
                   u' (lower, expected, upper): %.2f, %.2f, %.2f.')
            out.debug(msg % args)

            return True, new_no_content_resp(self.mutant.get_uri())

        # We reach this code when the HTTP timeout wasn't reached, but we might still
        # have a working delay. This is most of the cases I've seen.
        current_response_wait_time = response.get_wait_time()
        args = (id(self), current_response_wait_time, lower_bound, delay, upper_bound)
        msg = (u'[id: %s] HTTP response delay was %.2f.'
               u' (lower, expected, upper): %.2f, %.2f, %.2f.')
        out.debug(msg % args)

        if current_response_wait_time > lower_bound:
            return True, response

        return False, response