Пример #1
0
    def attack(self):
        methods = ""
        if self.do_get:
            methods += "G"
        if self.do_post:
            methods += "PF"

        mutator = Mutator(methods=methods,
                          payloads=self.random_string,
                          qs_inject=self.must_attack_query_string,
                          skip=self.options.get("skipped_parameters"))

        http_resources = self.persister.get_links(
            attack_module=self.name) if self.do_get else []
        forms = self.persister.get_forms(
            attack_module=self.name) if self.do_post else []

        for original_request in chain(http_resources, forms):
            if self.verbose >= 1:
                print("[+] {}".format(original_request))

            for mutated_request, parameter, taint, flags in mutator.mutate(
                    original_request):
                try:
                    # We don't display the mutated request here as the payload is not interesting
                    try:
                        response = self.crawler.send(mutated_request)
                    except ReadTimeout:
                        # We just inserted harmless characters, if we get a timeout here, it's not interesting
                        continue
                    else:
                        # We keep a history of taint values we sent because in case of stored value, the taint code
                        # may be found in another webpage by the permanentxss module.
                        self.tried_xss[taint] = (mutated_request, parameter,
                                                 flags)

                        # Reminder: valid_xss_content_type is not called before before content is not necessary
                        # reflected here, may be found in another webpage so we have to inject tainted values
                        # even if the Content-Type seems uninteresting.
                        if taint.lower() in response.content.lower(
                        ) and valid_xss_content_type(mutated_request):
                            # Simple text injection worked in HTML response, let's try with JS code
                            payloads = generate_payloads(
                                response.content, taint, self.PAYLOADS_FILE)

                            # TODO: check that and make it better
                            if flags.method == PayloadType.get:
                                method = "G"
                            elif flags.method == PayloadType.file:
                                method = "F"
                            else:
                                method = "P"

                            self.attempt_exploit(method, payloads,
                                                 original_request, parameter,
                                                 taint)
                except KeyboardInterrupt as exception:
                    yield exception

            yield original_request
Пример #2
0
def test_missing_value():
    req2 = Request("http://perdu.com/directory/?high=tone", )
    # Filename of the target URL should be injected but it is missing here, we should not raise a mutation
    mutator = Mutator(payloads=[("[FILE_NAME]::$DATA", Flags())])
    count = 0
    for __ in mutator.mutate(req2):
        count += 1
    assert count == 0
Пример #3
0
 def __init__(
         self, session_id: str, methods="FGP", payloads=None, qs_inject=False, max_queries_per_pattern: int = 1000,
         parameters=None,  # Restrict attack to a whitelist of parameters
         skip=None,  # Must not attack those parameters (blacklist)
         endpoint: str = "http://wapiti3.ovh/"
 ):
     Mutator.__init__(
         self, methods=methods, payloads=payloads, qs_inject=qs_inject,
         max_queries_per_pattern=max_queries_per_pattern, parameters=parameters, skip=skip)
     self._session_id = session_id
     self._endpoint = endpoint
Пример #4
0
    def __init__(self, crawler, persister, attack_options, stop_event):
        Attack.__init__(self, crawler, persister, attack_options, stop_event)
        methods = ""
        if self.do_get:
            methods += "G"
        if self.do_post:
            methods += "PF"

        self.mutator = Mutator(methods=methods,
                               payloads=random_string_with_flags,
                               qs_inject=self.must_attack_query_string,
                               skip=self.options.get("skipped_parameters"))
Пример #5
0
    def get_mutator(self):
        methods = ""
        if self.do_get:
            methods += "G"
        if self.do_post:
            # No file uploads, we won't attack filenames but file contents
            methods += "P"

        return Mutator(methods=methods,
                       payloads=self.payloads,
                       qs_inject=self.must_attack_query_string,
                       skip=self.options.get("skipped_parameters"))
Пример #6
0
class mod_xss(Attack):
    """Detects stored (aka permanent) Cross-Site Scripting vulnerabilities on the web server."""

    name = "xss"

    # two dict exported for permanent XSS scanning
    # GET_XSS structure :
    # {uniq_code : http://url/?param1=value1&param2=uniq_code&param3..., next_uniq_code : ...}
    # GET_XSS = {}
    # POST XSS structure :
    # {uniq_code: [target_url, {param1: val1, param2: uniq_code, param3:...}, referer_ul], next_uniq_code : [...]...}
    # POST_XSS = {}
    tried_xss = {}
    PHP_SELF = []

    # key = taint code, value = (payload, flags)
    successful_xss = {}

    PAYLOADS_FILE = path_join(Attack.DATA_DIR, "xssPayloads.ini")

    MSG_VULN = _("XSS vulnerability")

    def __init__(self, crawler, persister, attack_options, stop_event):
        Attack.__init__(self, crawler, persister, attack_options, stop_event)
        methods = ""
        if self.do_get:
            methods += "G"
        if self.do_post:
            methods += "PF"

        self.mutator = Mutator(methods=methods,
                               payloads=random_string_with_flags,
                               qs_inject=self.must_attack_query_string,
                               skip=self.options.get("skipped_parameters"))

    async def attack(self, request: Request):
        for mutated_request, parameter, taint, flags in self.mutator.mutate(
                request):
            # We don't display the mutated request here as the payload is not interesting
            try:
                response = await self.crawler.async_send(mutated_request)
            except RequestError:
                self.network_errors += 1
                # We just inserted harmless characters, if we get a timeout here, it's not interesting
                continue
            else:
                # We keep a history of taint values we sent because in case of stored value, the taint code
                # may be found in another webpage by the permanentxss module.
                self.tried_xss[taint] = (mutated_request, parameter, flags)

                # Reminder: valid_xss_content_type is not called before before content is not necessary
                # reflected here, may be found in another webpage so we have to inject tainted values
                # even if the Content-Type seems uninteresting.
                if taint.lower() in response.content.lower(
                ) and valid_xss_content_type(mutated_request):
                    # Simple text injection worked in HTML response, let's try with JS code
                    payloads = generate_payloads(response.content, taint,
                                                 self.PAYLOADS_FILE,
                                                 self.external_endpoint)

                    # TODO: check that and make it better
                    if flags.method == PayloadType.get:
                        method = "G"
                    elif flags.method == PayloadType.file:
                        method = "F"
                    else:
                        method = "P"

                    await self.attempt_exploit(method, payloads, request,
                                               parameter, taint)

    async def attempt_exploit(self, method, payloads, original_request,
                              parameter, taint):
        timeouted = False
        page = original_request.path
        saw_internal_error = False

        attack_mutator = Mutator(methods=method,
                                 payloads=payloads,
                                 qs_inject=self.must_attack_query_string,
                                 parameters=[parameter],
                                 skip=self.options.get("skipped_parameters"))

        for evil_request, xss_param, xss_payload, xss_flags in attack_mutator.mutate(
                original_request):
            if self.verbose == 2:
                logging.info("[¨] {0}".format(evil_request))

            try:
                response = await self.crawler.async_send(evil_request)
            except ReadTimeout:
                self.network_errors += 1
                if timeouted:
                    continue

                self.log_orange("---")
                self.log_orange(Messages.MSG_TIMEOUT, page)
                self.log_orange(Messages.MSG_EVIL_REQUEST)
                self.log_orange(evil_request.http_repr())
                self.log_orange("---")

                if xss_param == "QUERY_STRING":
                    anom_msg = Messages.MSG_QS_TIMEOUT
                else:
                    anom_msg = Messages.MSG_PARAM_TIMEOUT.format(xss_param)

                await self.add_anom_medium(request_id=original_request.path_id,
                                           category=Messages.RES_CONSUMPTION,
                                           request=evil_request,
                                           info=anom_msg,
                                           parameter=xss_param)
                timeouted = True
            except RequestError:
                self.network_errors += 1
            else:
                if (response.status not in (301, 302, 303)
                        and valid_xss_content_type(evil_request)
                        and self.check_payload(response, xss_flags, taint)):
                    self.successful_xss[taint] = (xss_payload, xss_flags)
                    message = _(
                        "XSS vulnerability found via injection in the parameter {0}"
                    ).format(xss_param)
                    if has_strong_csp(response):
                        message += ".\n" + _(
                            "Warning: Content-Security-Policy is present!")

                    await self.add_vuln_medium(
                        request_id=original_request.path_id,
                        category=NAME,
                        request=evil_request,
                        parameter=xss_param,
                        info=message)

                    if xss_param == "QUERY_STRING":
                        injection_msg = Messages.MSG_QS_INJECT
                    else:
                        injection_msg = Messages.MSG_PARAM_INJECT

                    self.log_red("---")
                    self.log_red(injection_msg, self.MSG_VULN, page, xss_param)

                    if has_strong_csp(response):
                        self.log_red(
                            _("Warning: Content-Security-Policy is present!"))

                    self.log_red(Messages.MSG_EVIL_REQUEST)
                    self.log_red(evil_request.http_repr())
                    self.log_red("---")

                    # stop trying payloads and jump to the next parameter
                    break

                if response.status == 500 and not saw_internal_error:
                    if xss_param == "QUERY_STRING":
                        anom_msg = Messages.MSG_QS_500
                    else:
                        anom_msg = Messages.MSG_PARAM_500.format(xss_param)

                    await self.add_anom_high(
                        request_id=original_request.path_id,
                        category=Messages.ERROR_500,
                        request=evil_request,
                        info=anom_msg,
                        parameter=xss_param)

                    self.log_orange("---")
                    self.log_orange(Messages.MSG_500, page)
                    self.log_orange(Messages.MSG_EVIL_REQUEST)
                    self.log_orange(evil_request.http_repr())
                    self.log_orange("---")
                    saw_internal_error = True

    def check_payload(self, response, flags, taint):
        config_reader = ConfigParser(interpolation=None)
        config_reader.read_file(
            open(path_join(self.DATA_DIR, self.PAYLOADS_FILE)))

        for section in config_reader.sections():
            if section == flags.section:
                expected_value = config_reader[section]["value"].replace(
                    '[EXTERNAL_ENDPOINT]', self.external_endpoint)
                expected_value = expected_value.replace("__XSS__", taint)
                tag_names = config_reader[section]["tag"].split(",")
                attribute = config_reader[section]["attribute"]
                case_sensitive = config_reader[section].getboolean(
                    "case_sensitive")
                match_type = config_reader[section].get("match_type", "exact")

                attribute_constraint = {
                    attribute: True
                } if attribute not in ["full_string", "string"] else {}

                for tag in response.soup.find_all(tag_names,
                                                  attrs=attribute_constraint):
                    non_exec_parent = find_non_exec_parent(tag)

                    if non_exec_parent and not (tag.name == "frame" and
                                                non_exec_parent == "frameset"):
                        continue

                    if attribute == "string" and tag.string:
                        if case_sensitive:
                            if expected_value in tag.string:
                                return True
                        else:
                            if expected_value.lower() in tag.string.lower():
                                return True
                    elif attribute == "full_string" and tag.string:
                        if case_sensitive:
                            if match_type == "exact" and expected_value == tag.string.strip(
                            ):
                                return True
                            if match_type == "starts_with" and tag.string.strip(
                            ).startswith(expected_value):
                                return True
                        else:
                            if match_type == "exact" and expected_value.lower(
                            ) == tag.string.strip().lower():
                                return True
                            if match_type == "starts_with" and \
                                    tag.string.strip().lower().startswith(expected_value.lower()):
                                return True
                    else:
                        # Found attribute specified in .ini file in attributes of the HTML tag
                        if attribute in tag.attrs:
                            if case_sensitive:
                                if match_type == "exact" and tag[
                                        attribute] == expected_value:
                                    return True
                                if match_type == "starts_with" and tag[
                                        attribute].startswith(expected_value):
                                    return True
                            else:
                                if match_type == "exact" and tag[
                                        attribute].lower(
                                        ) == expected_value.lower():
                                    return True
                                if match_type == "starts_with" and \
                                        expected_value.lower().startswith(tag[attribute].lower()):
                                    return True
                break

        return False
Пример #7
0
    async def finish(self):
        endpoint_url = f"{self.internal_endpoint}get_ssrf.php?session_id={self._session_id}"
        logging.info(_("[*] Asking endpoint URL {} for results, please wait...").format(endpoint_url))
        await sleep(2)
        # A la fin des attaques on questionne le endpoint pour savoir s'il a été contacté
        endpoint_request = Request(endpoint_url)
        try:
            response = await self.crawler.async_send(endpoint_request)
        except RequestError:
            self.network_errors += 1
            logging.error(_("[!] Unable to request endpoint URL '{}'").format(self.internal_endpoint))
        else:
            data = response.json
            if isinstance(data, dict):
                for request_id in data:
                    original_request = await self.persister.get_path_by_id(request_id)
                    if original_request is None:
                        raise ValueError("Could not find the original request with that ID")

                    page = original_request.path
                    for hex_param in data[request_id]:
                        parameter = unhexlify(hex_param).decode("utf-8")

                        for infos in data[request_id][hex_param]:
                            request_url = infos["url"]
                            # Date in ISO format
                            request_date = infos["date"]
                            request_ip = infos["ip"]
                            request_method = infos["method"]
                            # request_size = infos["size"]

                            if parameter == "QUERY_STRING":
                                vuln_message = Messages.MSG_QS_INJECT.format(self.MSG_VULN, page)
                            else:
                                vuln_message = _(
                                    "{0} via injection in the parameter {1}.\n"
                                    "The target performed an outgoing HTTP {2} request at {3} with IP {4}.\n"
                                    "Full request can be seen at {5}"
                                ).format(
                                    self.MSG_VULN,
                                    parameter,
                                    request_method,
                                    request_date,
                                    request_ip,
                                    request_url
                                )

                            mutator = Mutator(
                                methods="G" if original_request.method == "GET" else "PF",
                                payloads=[("http://external.url/page", Flags())],
                                qs_inject=self.must_attack_query_string,
                                parameters=[parameter],
                                skip=self.options.get("skipped_parameters")
                            )

                            mutated_request, __, __, __ = next(mutator.mutate(original_request))

                            await self.add_vuln_critical(
                                request_id=original_request.path_id,
                                category=NAME,
                                request=mutated_request,
                                info=vuln_message,
                                parameter=parameter,
                                wstg=WSTG_CODE
                            )

                            log_red("---")
                            log_red(
                                Messages.MSG_QS_INJECT if parameter == "QUERY_STRING"
                                else Messages.MSG_PARAM_INJECT,
                                self.MSG_VULN,
                                page,
                                parameter
                            )
                            log_red(Messages.MSG_EVIL_REQUEST)
                            log_red(mutated_request.http_repr())
                            log_red("---")
Пример #8
0
    def attempt_exploit(self, method, payloads, original_request, parameter,
                        taint):
        timeouted = False
        page = original_request.path
        saw_internal_error = False

        attack_mutator = Mutator(methods=method,
                                 payloads=payloads,
                                 qs_inject=self.must_attack_query_string,
                                 parameters=[parameter],
                                 skip=self.options.get("skipped_parameters"))

        for evil_request, xss_param, xss_payload, xss_flags in attack_mutator.mutate(
                original_request):
            if self.verbose == 2:
                print("[¨] {0}".format(evil_request))

            try:
                response = self.crawler.send(evil_request)
            except ReadTimeout:
                if timeouted:
                    continue

                self.log_orange("---")
                self.log_orange(Anomaly.MSG_TIMEOUT, page)
                self.log_orange(Anomaly.MSG_EVIL_REQUEST)
                self.log_orange(evil_request.http_repr())
                self.log_orange("---")

                if xss_param == "QUERY_STRING":
                    anom_msg = Anomaly.MSG_QS_TIMEOUT
                else:
                    anom_msg = Anomaly.MSG_PARAM_TIMEOUT.format(xss_param)

                self.add_anom(request_id=original_request.path_id,
                              category=Anomaly.RES_CONSUMPTION,
                              level=Anomaly.MEDIUM_LEVEL,
                              request=evil_request,
                              info=anom_msg,
                              parameter=xss_param)
                timeouted = True

            else:
                if (response.status not in (301, 302, 303)
                        and valid_xss_content_type(evil_request)
                        and self.check_payload(response, xss_flags, taint)):
                    self.successful_xss[taint] = (xss_payload, xss_flags)
                    message = _(
                        "XSS vulnerability found via injection in the parameter {0}"
                    ).format(xss_param)
                    if has_csp(response):
                        message += ".\n" + _(
                            "Warning: Content-Security-Policy is present!")

                    self.add_vuln(request_id=original_request.path_id,
                                  category=Vulnerability.XSS,
                                  level=Vulnerability.HIGH_LEVEL,
                                  request=evil_request,
                                  parameter=xss_param,
                                  info=message)

                    if xss_param == "QUERY_STRING":
                        injection_msg = Vulnerability.MSG_QS_INJECT
                    else:
                        injection_msg = Vulnerability.MSG_PARAM_INJECT

                    self.log_red("---")
                    self.log_red(injection_msg, self.MSG_VULN, page, xss_param)

                    if has_csp(response):
                        self.log_red(
                            _("Warning: Content-Security-Policy is present!"))

                    self.log_red(Vulnerability.MSG_EVIL_REQUEST)
                    self.log_red(evil_request.http_repr())
                    self.log_red("---")

                    # stop trying payloads and jump to the next parameter
                    break
                elif response.status == 500 and not saw_internal_error:
                    if xss_param == "QUERY_STRING":
                        anom_msg = Anomaly.MSG_QS_500
                    else:
                        anom_msg = Anomaly.MSG_PARAM_500.format(xss_param)

                    self.add_anom(request_id=original_request.path_id,
                                  category=Anomaly.ERROR_500,
                                  level=Anomaly.HIGH_LEVEL,
                                  request=evil_request,
                                  info=anom_msg,
                                  parameter=xss_param)

                    self.log_orange("---")
                    self.log_orange(Anomaly.MSG_500, page)
                    self.log_orange(Anomaly.MSG_EVIL_REQUEST)
                    self.log_orange(evil_request.http_repr())
                    self.log_orange("---")
                    saw_internal_error = True
Пример #9
0
    def finish(self):
        endpoint_url = "{}get_xxe.php?session_id={}".format(
            self.internal_endpoint, self._session_id)
        print(
            _("[*] Asking endpoint URL {} for results, please wait...").format(
                endpoint_url))
        sleep(2)
        # A la fin des attaques on questionne le endpoint pour savoir s'il a été contacté
        endpoint_request = Request(endpoint_url)
        try:
            response = self.crawler.send(endpoint_request)
        except RequestException:
            self.network_errors += 1
            print(
                _("[!] Unable to request endpoint URL '{}'").format(
                    self.internal_endpoint))
            return

        data = response.json
        if not isinstance(data, dict):
            return

        for request_id in data:
            original_request = self.persister.get_path_by_id(request_id)
            if original_request is None:
                continue
                # raise ValueError("Could not find the original request with ID {}".format(request_id))

            page = original_request.path
            for hex_param in data[request_id]:
                parameter = unhexlify(hex_param).decode("utf-8")

                for infos in data[request_id][hex_param]:
                    request_url = infos["url"]
                    # Date in ISO format
                    request_date = infos["date"]
                    request_ip = infos["ip"]
                    request_size = infos["size"]
                    payload_name = infos["payload"]

                    if parameter == "QUERY_STRING":
                        vuln_message = Messages.MSG_QS_INJECT.format(
                            self.MSG_VULN, page)
                    elif parameter == "raw body":
                        vuln_message = _(
                            "Out-Of-Band {0} by sending raw XML in request body"
                        ).format(self.MSG_VULN)
                    else:
                        vuln_message = _(
                            "Out-Of-Band {0} via injection in the parameter {1}"
                        ).format(self.MSG_VULN, parameter)

                    more_infos = _(
                        "The target sent {0} bytes of data to the endpoint at {1} with IP {2}.\n"
                        "Received data can be seen at {3}.").format(
                            request_size, request_date, request_ip,
                            request_url)

                    vuln_message += "\n" + more_infos

                    # placeholder if shit happens
                    payload = (
                        "<xml>"
                        "See https://phonexicum.github.io/infosec/xxe.html#attack-vectors"
                        "</xml>")

                    for payload, flags in self.payloads:
                        if "{}.dtd".format(payload_name) in payload:
                            payload = payload.replace(
                                "[PATH_ID]", str(original_request.path_id))
                            payload = payload.replace("[PARAM_AS_HEX]",
                                                      "72617720626f6479")
                            break

                    if parameter == "raw body":
                        mutated_request = Request(original_request.path,
                                                  method="POST",
                                                  enctype="text/xml",
                                                  post_params=payload)
                    elif parameter == "QUERY_STRING":
                        mutated_request = Request("{}?{}".format(
                            original_request.path, quote(payload)),
                                                  method="GET")
                    elif parameter in original_request.get_keys or parameter in original_request.post_keys:
                        mutator = Mutator(
                            methods="G"
                            if original_request.method == "GET" else "P",
                            payloads=[(payload, Flags())],
                            qs_inject=self.must_attack_query_string,
                            parameters=[parameter],
                            skip=self.options.get("skipped_parameters"))

                        mutated_request, __, __, __ = next(
                            mutator.mutate(original_request))
                    else:
                        mutator = FileMutator(
                            payloads=[(payload, Flags())],
                            parameters=[parameter],
                            skip=self.options.get("skipped_parameters"))
                        mutated_request, __, __, __ = next(
                            mutator.mutate(original_request))

                    self.add_vuln(request_id=original_request.path_id,
                                  category=NAME,
                                  level=HIGH_LEVEL,
                                  request=mutated_request,
                                  info=vuln_message,
                                  parameter=parameter)

                    self.log_red("---")
                    self.log_red(vuln_message)
                    self.log_red(Messages.MSG_EVIL_REQUEST)
                    self.log_red(mutated_request.http_repr())
                    self.log_red("---")
Пример #10
0
def test_mutations():
    req = Request("http://perdu.com/page.php",
                  method="POST",
                  get_params=[["p", "login.php"]],
                  post_params=[["user", "admin"], ["password", "letmein"]],
                  file_params=[["file", ["pix.gif", "GIF89a", "image/gif"]]])
    mutator = Mutator(payloads=[("INJECT", set())])
    count = 0
    for __ in mutator.mutate(req):
        count += 1
    assert count == 4

    mutator = Mutator(payloads=[("PAYLOAD_1",
                                 set()), ("PAYLOAD_2",
                                          set()), ("PAYLOAD_3", set())])
    count = 0
    for __ in mutator.mutate(req):
        count += 1
    assert count == 12

    mutator = Mutator(methods="G",
                      payloads=[("PAYLOAD_1", set()), ("PAYLOAD_2", set()),
                                ("PAYLOAD_3", set())])
    count = 0
    for __ in mutator.mutate(req):
        count += 1
    assert count == 3

    mutator = Mutator(methods="P",
                      payloads=[("PAYLOAD_1", set()), ("PAYLOAD_2", set()),
                                ("PAYLOAD_3", set())])
    count = 0
    for __ in mutator.mutate(req):
        count += 1
    assert count == 6

    mutator = Mutator(methods="PF",
                      payloads=[("PAYLOAD_1", set()), ("PAYLOAD_2", set()),
                                ("PAYLOAD_3", set())])
    count = 0
    for __ in mutator.mutate(req):
        count += 1
    assert count == 9

    mutator = Mutator(payloads=[("PAYLOAD_1", set()), ("PAYLOAD_2", set()),
                                ("PAYLOAD_3", set())],
                      parameters=["user", "file"])
    count = 0
    for __ in mutator.mutate(req):
        count += 1
    assert count == 6

    mutator = Mutator(payloads=[("PAYLOAD_1", set()), ("PAYLOAD_2", set()),
                                ("PAYLOAD_3", set())],
                      skip={"p"})
    count = 0
    for __, __, __, __ in mutator.mutate(req):
        count += 1
    assert count == 9

    # JSESSIONID is marked as annoying parameter
    req2 = Request("http://perdu.com/page.php",
                   method="POST",
                   get_params=[["JSESSIONID", "deadbeef"]],
                   post_params=[["user", "admin"], ["password", "letmein"]],
                   file_params=[["file", ["pix.gif", "GIF89a", "image/gif"]]])
    mutator = Mutator(payloads=[("INJECT", set())])
    count = 0
    for __ in mutator.mutate(req2):
        count += 1
    assert count == 3

    # Inject into query string. Will only work if method is GET without any parameter
    req3 = Request("http://perdu.com/page.php")
    mutator = Mutator(payloads=[("PAYLOAD_1", set()), ("PAYLOAD_2", set())],
                      qs_inject=True)
    count = 0
    for __, __, __, __ in mutator.mutate(req3):
        count += 1
    assert count == 2
Пример #11
0
    def attack(self):
        methods = ""
        if self.do_get:
            methods += "G"
        if self.do_post:
            methods += "PF"

        mutator = SsrfMutator(session_id=self._session_id,
                              methods=methods,
                              payloads=self.payloads,
                              qs_inject=self.must_attack_query_string,
                              skip=self.options.get("skipped_parameters"))

        http_resources = self.persister.get_links(
            attack_module=self.name) if self.do_get else []
        forms = self.persister.get_forms(
            attack_module=self.name) if self.do_post else []

        for original_request in chain(http_resources, forms):
            if self.verbose >= 1:
                print("[+] {}".format(original_request))

            # Let's just send payloads, we don't care of the response as what we want to know is if the target
            # contacted the endpoint.
            for mutated_request, parameter, payload, flags in mutator.mutate(
                    original_request):
                try:
                    if self.verbose == 2:
                        print("[¨] {0}".format(mutated_request))

                    try:
                        self.crawler.send(mutated_request)
                    except ReadTimeout:
                        continue
                except (KeyboardInterrupt, RequestException) as exception:
                    yield exception

            yield original_request

        # A la fin des attaques on questionne le endpoint pour savoir s'il a été contacté
        endpoint_request = Request(
            "https://wapiti3.ovh/get_ssrf.php?id={}".format(self._session_id))
        try:
            response = self.crawler.send(endpoint_request)
        except ReadTimeout:
            pass
        else:
            data = response.json
            if isinstance(data, dict):
                for request_id in data:
                    original_request = self.persister.get_path_by_id(
                        request_id)
                    if not original_request:
                        raise ValueError(
                            "Could not find the original request with that ID")

                    page = original_request.path
                    for hex_param in data[request_id]:
                        parameter = unhexlify(hex_param).decode("utf-8")

                        for infos in data[request_id][hex_param]:
                            request_url = infos["url"]
                            # Date in ISO format
                            request_date = infos["date"]
                            request_ip = infos["ip"]
                            request_method = infos["method"]
                            # request_size = infos["size"]

                            if parameter == "QUERY_STRING":
                                vuln_message = Vulnerability.MSG_QS_INJECT.format(
                                    self.MSG_VULN, page)
                            else:
                                vuln_message = _(
                                    "{0} via injection in the parameter {1}.\n"
                                    "The target performed an outgoing HTTP {2} request at {3} with IP {4}.\n"
                                    "Full request can be seen at {5}").format(
                                        self.MSG_VULN, parameter,
                                        request_method, request_date,
                                        request_ip, request_url)

                            mutator = Mutator(
                                methods="G"
                                if original_request.method == "GET" else "P",
                                payloads=[("http://external.url/page", set())],
                                qs_inject=self.must_attack_query_string,
                                skip=self.options.get("skipped_parameters"))

                            mutated_request, parameter, taint, flags = next(
                                mutator.mutate(original_request))

                            self.add_vuln(request_id=original_request.path_id,
                                          category=Vulnerability.SSRF,
                                          level=Vulnerability.HIGH_LEVEL,
                                          request=mutated_request,
                                          info=vuln_message,
                                          parameter=parameter)

                            self.log_red("---")
                            self.log_red(
                                Vulnerability.MSG_QS_INJECT
                                if parameter == "QUERY_STRING" else
                                Vulnerability.MSG_PARAM_INJECT, self.MSG_VULN,
                                page, parameter)
                            self.log_red(Vulnerability.MSG_EVIL_REQUEST)
                            self.log_red(mutated_request.http_repr())
                            self.log_red("---")
Пример #12
0
class ModuleXss(Attack):
    """Detects stored (aka permanent) Cross-Site Scripting vulnerabilities on the web server."""

    name = "xss"

    # two dict exported for permanent XSS scanning
    # GET_XSS structure :
    # {uniq_code : http://url/?param1=value1&param2=uniq_code&param3..., next_uniq_code : ...}
    # GET_XSS = {}
    # POST XSS structure :
    # {uniq_code: [target_url, {param1: val1, param2: uniq_code, param3:...}, referer_ul], next_uniq_code : [...]...}
    # POST_XSS = {}
    tried_xss = {}
    PHP_SELF = []

    # key = taint code, value = (payload, flags)
    successful_xss = {}

    PAYLOADS_FILE = path_join(Attack.DATA_DIR, "xssPayloads.ini")

    MSG_VULN = _("XSS vulnerability")

    RANDOM_WEBSITE = f"https://{random_string(length=6)}.com/"

    def __init__(self, crawler, persister, attack_options, stop_event):
        Attack.__init__(self, crawler, persister, attack_options, stop_event)
        methods = ""
        if self.do_get:
            methods += "G"
        if self.do_post:
            methods += "PF"

        self.mutator = Mutator(methods=methods,
                               payloads=random_string_with_flags,
                               qs_inject=self.must_attack_query_string,
                               skip=self.options.get("skipped_parameters"))

    @property
    def external_endpoint(self):
        return self.RANDOM_WEBSITE

    async def attack(self, request: Request):
        for mutated_request, parameter, taint, flags in self.mutator.mutate(
                request):
            # We don't display the mutated request here as the payload is not interesting
            try:
                response = await self.crawler.async_send(mutated_request)
            except RequestError:
                self.network_errors += 1
                # We just inserted harmless characters, if we get a timeout here, it's not interesting
                continue
            else:
                # We keep a history of taint values we sent because in case of stored value, the taint code
                # may be found in another webpage by the permanentxss module.
                self.tried_xss[taint] = (mutated_request, parameter, flags)

                # Reminder: valid_xss_content_type is not called before before content is not necessary
                # reflected here, may be found in another webpage so we have to inject tainted values
                # even if the Content-Type seems uninteresting.
                if taint.lower() in response.content.lower(
                ) and valid_xss_content_type(mutated_request):
                    # Simple text injection worked in HTML response, let's try with JS code
                    payloads = generate_payloads(response.content, taint,
                                                 self.PAYLOADS_FILE,
                                                 self.external_endpoint)

                    # TODO: check that and make it better
                    if flags.method == PayloadType.get:
                        method = "G"
                    elif flags.method == PayloadType.file:
                        method = "F"
                    else:
                        method = "P"

                    await self.attempt_exploit(method, payloads, request,
                                               parameter, taint)

    async def attempt_exploit(self, method, payloads, original_request,
                              parameter, taint):
        timeouted = False
        page = original_request.path
        saw_internal_error = False

        attack_mutator = Mutator(methods=method,
                                 payloads=payloads,
                                 qs_inject=self.must_attack_query_string,
                                 parameters=[parameter],
                                 skip=self.options.get("skipped_parameters"))

        for evil_request, xss_param, xss_payload, xss_flags in attack_mutator.mutate(
                original_request):
            log_verbose(f"[¨] {evil_request}")

            try:
                response = await self.crawler.async_send(evil_request)
            except ReadTimeout:
                self.network_errors += 1
                if timeouted:
                    continue

                log_orange("---")
                log_orange(Messages.MSG_TIMEOUT, page)
                log_orange(Messages.MSG_EVIL_REQUEST)
                log_orange(evil_request.http_repr())
                log_orange("---")

                if xss_param == "QUERY_STRING":
                    anom_msg = Messages.MSG_QS_TIMEOUT
                else:
                    anom_msg = Messages.MSG_PARAM_TIMEOUT.format(xss_param)

                await self.add_anom_medium(request_id=original_request.path_id,
                                           category=Messages.RES_CONSUMPTION,
                                           request=evil_request,
                                           info=anom_msg,
                                           parameter=xss_param,
                                           wstg=RESOURCE_CONSUMPTION_WSTG_CODE)
                timeouted = True
            except RequestError:
                self.network_errors += 1
            else:
                if (response.status not in (301, 302, 303)
                        and valid_xss_content_type(evil_request)
                        and check_payload(self.DATA_DIR, self.PAYLOADS_FILE,
                                          self.external_endpoint,
                                          self.proto_endpoint, response,
                                          xss_flags, taint)):
                    self.successful_xss[taint] = (xss_payload, xss_flags)
                    message = _(
                        "XSS vulnerability found via injection in the parameter {0}"
                    ).format(xss_param)
                    if has_strong_csp(response):
                        message += ".\n" + _(
                            "Warning: Content-Security-Policy is present!")

                    await self.add_vuln_medium(
                        request_id=original_request.path_id,
                        category=NAME,
                        request=evil_request,
                        parameter=xss_param,
                        info=message,
                        wstg=WSTG_CODE)

                    if xss_param == "QUERY_STRING":
                        injection_msg = Messages.MSG_QS_INJECT
                    else:
                        injection_msg = Messages.MSG_PARAM_INJECT

                    log_red("---")
                    log_red(injection_msg, self.MSG_VULN, page, xss_param)

                    if has_strong_csp(response):
                        log_red(
                            _("Warning: Content-Security-Policy is present!"))

                    log_red(Messages.MSG_EVIL_REQUEST)
                    log_red(evil_request.http_repr())
                    log_red("---")

                    # stop trying payloads and jump to the next parameter
                    break

                if response.status == 500 and not saw_internal_error:
                    if xss_param == "QUERY_STRING":
                        anom_msg = Messages.MSG_QS_500
                    else:
                        anom_msg = Messages.MSG_PARAM_500.format(xss_param)

                    await self.add_anom_high(
                        request_id=original_request.path_id,
                        category=Messages.ERROR_500,
                        request=evil_request,
                        info=anom_msg,
                        parameter=xss_param,
                        wstg=INTERNAL_ERROR_WSTG_CODE)

                    log_orange("---")
                    log_orange(Messages.MSG_500, page)
                    log_orange(Messages.MSG_EVIL_REQUEST)
                    log_orange(evil_request.http_repr())
                    log_orange("---")
                    saw_internal_error = True
Пример #13
0
    async def attempt_exploit(self, method, payloads, original_request,
                              parameter, taint):
        timeouted = False
        page = original_request.path
        saw_internal_error = False

        attack_mutator = Mutator(methods=method,
                                 payloads=payloads,
                                 qs_inject=self.must_attack_query_string,
                                 parameters=[parameter],
                                 skip=self.options.get("skipped_parameters"))

        for evil_request, xss_param, xss_payload, xss_flags in attack_mutator.mutate(
                original_request):
            log_verbose(f"[¨] {evil_request}")

            try:
                response = await self.crawler.async_send(evil_request)
            except ReadTimeout:
                self.network_errors += 1
                if timeouted:
                    continue

                log_orange("---")
                log_orange(Messages.MSG_TIMEOUT, page)
                log_orange(Messages.MSG_EVIL_REQUEST)
                log_orange(evil_request.http_repr())
                log_orange("---")

                if xss_param == "QUERY_STRING":
                    anom_msg = Messages.MSG_QS_TIMEOUT
                else:
                    anom_msg = Messages.MSG_PARAM_TIMEOUT.format(xss_param)

                await self.add_anom_medium(request_id=original_request.path_id,
                                           category=Messages.RES_CONSUMPTION,
                                           request=evil_request,
                                           info=anom_msg,
                                           parameter=xss_param,
                                           wstg=RESOURCE_CONSUMPTION_WSTG_CODE)
                timeouted = True
            except RequestError:
                self.network_errors += 1
            else:
                if (response.status not in (301, 302, 303)
                        and valid_xss_content_type(evil_request)
                        and check_payload(self.DATA_DIR, self.PAYLOADS_FILE,
                                          self.external_endpoint,
                                          self.proto_endpoint, response,
                                          xss_flags, taint)):
                    self.successful_xss[taint] = (xss_payload, xss_flags)
                    message = _(
                        "XSS vulnerability found via injection in the parameter {0}"
                    ).format(xss_param)
                    if has_strong_csp(response):
                        message += ".\n" + _(
                            "Warning: Content-Security-Policy is present!")

                    await self.add_vuln_medium(
                        request_id=original_request.path_id,
                        category=NAME,
                        request=evil_request,
                        parameter=xss_param,
                        info=message,
                        wstg=WSTG_CODE)

                    if xss_param == "QUERY_STRING":
                        injection_msg = Messages.MSG_QS_INJECT
                    else:
                        injection_msg = Messages.MSG_PARAM_INJECT

                    log_red("---")
                    log_red(injection_msg, self.MSG_VULN, page, xss_param)

                    if has_strong_csp(response):
                        log_red(
                            _("Warning: Content-Security-Policy is present!"))

                    log_red(Messages.MSG_EVIL_REQUEST)
                    log_red(evil_request.http_repr())
                    log_red("---")

                    # stop trying payloads and jump to the next parameter
                    break

                if response.status == 500 and not saw_internal_error:
                    if xss_param == "QUERY_STRING":
                        anom_msg = Messages.MSG_QS_500
                    else:
                        anom_msg = Messages.MSG_PARAM_500.format(xss_param)

                    await self.add_anom_high(
                        request_id=original_request.path_id,
                        category=Messages.ERROR_500,
                        request=evil_request,
                        info=anom_msg,
                        parameter=xss_param,
                        wstg=INTERNAL_ERROR_WSTG_CODE)

                    log_orange("---")
                    log_orange(Messages.MSG_500, page)
                    log_orange(Messages.MSG_EVIL_REQUEST)
                    log_orange(evil_request.http_repr())
                    log_orange("---")
                    saw_internal_error = True
Пример #14
0
    def attack(self, http_resources, forms):
        methods = ""
        if self.do_get:
            methods += "G"
        if self.do_post:
            methods += "PF"

        mutator = Mutator(
            methods=methods,
            payloads=self.random_string,
            qs_inject=self.must_attack_query_string,
            skip=self.options.get("skipped_parameters")
        )

        for original_request in chain(http_resources, forms):
            timeouted = False
            page = original_request.path
            saw_internal_error = False

            if self.verbose >= 1:
                print("[+] {}".format(original_request))

            for mutated_request, parameter, taint, flags in mutator.mutate(original_request):
                try:
                    # We don't display the mutated request here as the payload is not interesting
                    try:
                        response = self.crawler.send(mutated_request)
                    except ReadTimeout:
                        # We just inserted harmless characters, if we get a timeout here, it's not interesting
                        continue
                    else:
                        if taint in response.content:
                            # Simple text injection worked, let's try with JS code
                            payloads = [(js_code, set()) for js_code in self.generate_payloads(response.content, taint)]

                            # TODO: check that and make it better
                            if PayloadType.get in flags:
                                method = "G"
                            elif PayloadType.file in flags:
                                method = "F"
                            else:
                                method = "P"

                            # We keep a history of taint values we sent because in case of stored value, the taint code
                            # may be found in another webpage by the permanentxss module.
                            if mutated_request.method == "GET":
                                self.GET_XSS[taint] = (mutated_request, parameter)
                            else:
                                self.POST_XSS[taint] = (mutated_request, parameter)

                            attack_mutator = Mutator(
                                methods=method,
                                payloads=payloads,
                                qs_inject=self.must_attack_query_string,
                                parameters=[parameter],
                                skip=self.options.get("skipped_parameters")
                            )

                            for evil_request, xss_param, xss_payload, xss_flags in attack_mutator.mutate(original_request):
                                if self.verbose == 2:
                                    print("[¨] {0}".format(evil_request))

                                try:
                                    response = self.crawler.send(evil_request)
                                    data = response.content
                                except ReadTimeout:
                                    if timeouted:
                                        continue

                                    self.log_orange("---")
                                    self.log_orange(Anomaly.MSG_TIMEOUT, page)
                                    self.log_orange(Anomaly.MSG_EVIL_REQUEST)
                                    self.log_orange(evil_request.http_repr())
                                    self.log_orange("---")

                                    if xss_param == "QUERY_STRING":
                                        anom_msg = Anomaly.MSG_QS_TIMEOUT
                                    else:
                                        anom_msg = Anomaly.MSG_PARAM_TIMEOUT.format(xss_param)

                                    self.add_anom(
                                        request_id=original_request.path_id,
                                        category=Anomaly.RES_CONSUMPTION,
                                        level=Anomaly.MEDIUM_LEVEL,
                                        request=evil_request,
                                        info=anom_msg,
                                        parameter=xss_param
                                    )
                                    timeouted = True

                                else:
                                    # TODO: call _valid_xss_content_type sooner ?
                                    if self._valid_xss_content_type(evil_request) and data:
                                        if taint.lower() in data.lower():
                                            self.SUCCESSFUL_XSS[taint] = xss_payload
                                            self.add_vuln(
                                                request_id=original_request.path_id,
                                                category=Vulnerability.XSS,
                                                level=Vulnerability.HIGH_LEVEL,
                                                request=evil_request,
                                                parameter=xss_param,
                                                info=_("XSS vulnerability found via injection"
                                                       " in the parameter {0}").format(xss_param)
                                            )

                                            if xss_param == "QUERY_STRING":
                                                injection_msg = Vulnerability.MSG_QS_INJECT
                                            else:
                                                injection_msg = Vulnerability.MSG_PARAM_INJECT

                                            self.log_red("---")
                                            self.log_red(
                                                injection_msg,
                                                self.MSG_VULN,
                                                page,
                                                xss_param
                                            )
                                            self.log_red(Vulnerability.MSG_EVIL_REQUEST)
                                            self.log_red(evil_request.http_repr())
                                            self.log_red("---")

                                            # stop trying payloads and jump to the next parameter
                                            break
                                    elif response.status == 500 and not saw_internal_error:
                                        if xss_param == "QUERY_STRING":
                                            anom_msg = Anomaly.MSG_QS_500
                                        else:
                                            anom_msg = Anomaly.MSG_PARAM_500.format(xss_param)

                                        self.add_anom(
                                            request_id=original_request.path_id,
                                            category=Anomaly.ERROR_500,
                                            level=Anomaly.HIGH_LEVEL,
                                            request=evil_request,
                                            info=anom_msg,
                                            parameter=xss_param
                                        )

                                        self.log_orange("---")
                                        self.log_orange(Anomaly.MSG_500, page)
                                        self.log_orange(Anomaly.MSG_EVIL_REQUEST)
                                        self.log_orange(evil_request.http_repr())
                                        self.log_orange("---")
                                        saw_internal_error = True
                except KeyboardInterrupt as exception:
                    yield exception

            yield original_request
Пример #15
0
    async def boolean_based_attack(self, request: Request, parameters_to_skip: set):
        try:
            good_response = await self.crawler.async_send(request)
            good_status = good_response.status
            good_redirect = good_response.redirection_url
            # good_title = response.title
            good_hash = good_response.text_only_md5
        except ReadTimeout:
            self.network_errors += 1
            return

        methods = ""
        if self.do_get:
            methods += "G"
        if self.do_post:
            methods += "PF"

        mutator = Mutator(
            methods=methods,
            payloads=generate_boolean_payloads(),
            qs_inject=self.must_attack_query_string,
            skip=self.options.get("skipped_parameters", set()) | parameters_to_skip
        )

        page = request.path

        current_parameter = None
        skip_till_next_parameter = False
        current_session = None
        test_results = []
        last_mutated_request = None

        for mutated_request, parameter, __, flags in mutator.mutate(request):
            # Make sure we always pass through the following block to see changes of payloads formats
            if current_session != flags.platform:
                # We start a new set of payloads, let's analyse results for previous ones
                if test_results and all(test_results):
                    # We got a winner
                    skip_till_next_parameter = True
                    vuln_info = _("SQL Injection")

                    if current_parameter == "QUERY_STRING":
                        vuln_message = Messages.MSG_QS_INJECT.format(vuln_info, page)
                    else:
                        vuln_message = _("{0} via injection in the parameter {1}").format(vuln_info, current_parameter)

                    await self.add_vuln_critical(
                        request_id=request.path_id,
                        category=NAME,
                        request=last_mutated_request,
                        info=vuln_message,
                        parameter=current_parameter,
                        wstg=WSTG_CODE
                    )

                    log_red("---")
                    log_red(
                        Messages.MSG_QS_INJECT if current_parameter == "QUERY_STRING" else Messages.MSG_PARAM_INJECT,
                        vuln_info,
                        page,
                        current_parameter
                    )
                    log_red(Messages.MSG_EVIL_REQUEST)
                    log_red(last_mutated_request.http_repr())
                    log_red("---")

                # Don't forget to reset session and results
                current_session = flags.platform
                test_results = []

            if current_parameter != parameter:
                # Start attacking a new parameter, forget every state we kept
                current_parameter = parameter
                skip_till_next_parameter = False
            elif skip_till_next_parameter:
                # If parameter is vulnerable, just skip till next parameter
                continue

            if test_results and not all(test_results):
                # No need to go further: one of the tests was wrong
                continue

            log_verbose(f"[¨] {mutated_request}")

            try:
                response = await self.crawler.async_send(mutated_request)
            except RequestError:
                self.network_errors += 1
                # We need all cases to make sure SQLi is there
                test_results.append(False)
                continue

            comparison = (
                response.status == good_status and
                response.redirection_url == good_redirect and
                response.text_only_md5 == good_hash
            )

            test_results.append(comparison == (flags.section == "True"))
            last_mutated_request = mutated_request
Пример #16
0
    async def attempt_exploit(self, method, payloads, injection_request,
                              parameter, taint, output_request):
        timeouted = False
        page = injection_request.path
        saw_internal_error = False
        output_url = output_request.url

        attack_mutator = Mutator(methods=method,
                                 payloads=payloads,
                                 qs_inject=self.must_attack_query_string,
                                 parameters=[parameter],
                                 skip=self.options.get("skipped_parameters"))

        for evil_request, xss_param, _xss_payload, xss_flags in attack_mutator.mutate(
                injection_request):
            if self.verbose == 2:
                logging.info("[¨] {0}".format(evil_request))

            try:
                await self.crawler.async_send(evil_request)
            except ReadTimeout:
                self.network_errors += 1
                if timeouted:
                    continue

                self.log_orange("---")
                self.log_orange(Messages.MSG_TIMEOUT, page)
                self.log_orange(Messages.MSG_EVIL_REQUEST)
                self.log_orange(evil_request.http_repr())
                self.log_orange("---")

                if xss_param == "QUERY_STRING":
                    anom_msg = Messages.MSG_QS_TIMEOUT
                else:
                    anom_msg = Messages.MSG_PARAM_TIMEOUT.format(xss_param)

                await self.add_anom_medium(
                    request_id=injection_request.path_id,
                    category=Messages.RES_CONSUMPTION,
                    request=evil_request,
                    info=anom_msg,
                    parameter=xss_param)
                timeouted = True
            except RequestError:
                self.network_errors += 1
                continue
            else:
                try:
                    response = await self.crawler.async_send(output_request)
                except RequestError:
                    self.network_errors += 1
                    continue

                if (response.status not in (301, 302, 303)
                        and valid_xss_content_type(evil_request)
                        and self.check_payload(response, xss_flags, taint)):

                    if page == output_request.path:
                        description = _(
                            "Permanent XSS vulnerability found via injection in the parameter {0}"
                        ).format(xss_param)
                    else:
                        description = _(
                            "Permanent XSS vulnerability found in {0} by injecting"
                            " the parameter {1} of {2}").format(
                                output_request.url, parameter, page)

                    if has_strong_csp(response):
                        description += ".\n" + _(
                            "Warning: Content-Security-Policy is present!")

                    await self.add_vuln_high(
                        request_id=injection_request.path_id,
                        category=NAME,
                        request=evil_request,
                        parameter=xss_param,
                        info=description)

                    if xss_param == "QUERY_STRING":
                        injection_msg = Messages.MSG_QS_INJECT
                    else:
                        injection_msg = Messages.MSG_PARAM_INJECT

                    self.log_red("---")
                    # TODO: a last parameter should give URL used to pass the vulnerable parameter
                    self.log_red(injection_msg, self.MSG_VULN, output_url,
                                 xss_param)

                    if has_strong_csp(response):
                        self.log_red(
                            _("Warning: Content-Security-Policy is present!"))

                    self.log_red(Messages.MSG_EVIL_REQUEST)
                    self.log_red(evil_request.http_repr())
                    self.log_red("---")

                    # stop trying payloads and jump to the next parameter
                    break
                elif response.status == 500 and not saw_internal_error:
                    if xss_param == "QUERY_STRING":
                        anom_msg = Messages.MSG_QS_500
                    else:
                        anom_msg = Messages.MSG_PARAM_500.format(xss_param)

                    await self.add_anom_high(
                        request_id=injection_request.path_id,
                        category=Messages.ERROR_500,
                        request=evil_request,
                        info=anom_msg,
                        parameter=xss_param)

                    self.log_orange("---")
                    self.log_orange(Messages.MSG_500, page)
                    self.log_orange(Messages.MSG_EVIL_REQUEST)
                    self.log_orange(evil_request.http_repr())
                    self.log_orange("---")
                    saw_internal_error = True
Пример #17
0
    def attempt_exploit(self, method, payloads, injection_request, parameter,
                        taint, output_request):
        timeouted = False
        page = injection_request.path
        saw_internal_error = False
        output_url = output_request.url

        attack_mutator = Mutator(methods=method,
                                 payloads=payloads,
                                 qs_inject=self.must_attack_query_string,
                                 parameters=[parameter],
                                 skip=self.options.get("skipped_parameters"))

        for evil_request, xss_param, xss_payload, xss_flags in attack_mutator.mutate(
                injection_request):
            if self.verbose == 2:
                print("[¨] {0}".format(evil_request))

            try:
                self.crawler.send(evil_request)
            except ReadTimeout:
                if timeouted:
                    continue

                self.log_orange("---")
                self.log_orange(Anomaly.MSG_TIMEOUT, page)
                self.log_orange(Anomaly.MSG_EVIL_REQUEST)
                self.log_orange(evil_request.http_repr())
                self.log_orange("---")

                if xss_param == "QUERY_STRING":
                    anom_msg = Anomaly.MSG_QS_TIMEOUT
                else:
                    anom_msg = Anomaly.MSG_PARAM_TIMEOUT.format(xss_param)

                self.add_anom(request_id=injection_request.path_id,
                              category=Anomaly.RES_CONSUMPTION,
                              level=Anomaly.MEDIUM_LEVEL,
                              request=evil_request,
                              info=anom_msg,
                              parameter=xss_param)
                timeouted = True

            else:
                try:
                    response = self.crawler.send(output_request)
                except ReadTimeout:
                    continue

                if self.check_payload(response, xss_flags, taint):

                    if page == output_request.path:
                        description = _(
                            "Permanent XSS vulnerability found via injection in the parameter {0}"
                        ).format(xss_param)
                    else:
                        description = _(
                            "Permanent XSS vulnerability found in {0} by injecting"
                            " the parameter {1} of {2}").format(
                                output_request.url, parameter, page)

                    self.add_vuln(request_id=injection_request.path_id,
                                  category=Vulnerability.XSS,
                                  level=Vulnerability.HIGH_LEVEL,
                                  request=evil_request,
                                  parameter=xss_param,
                                  info=description)

                    if xss_param == "QUERY_STRING":
                        injection_msg = Vulnerability.MSG_QS_INJECT
                    else:
                        injection_msg = Vulnerability.MSG_PARAM_INJECT

                    self.log_red("---")
                    # TODO: a last parameter should give URL used to pass the vulnerable parameter
                    self.log_red(injection_msg, self.MSG_VULN, output_url,
                                 xss_param)
                    self.log_red(Vulnerability.MSG_EVIL_REQUEST)
                    self.log_red(evil_request.http_repr())
                    self.log_red("---")

                    # stop trying payloads and jump to the next parameter
                    break
                elif response.status == 500 and not saw_internal_error:
                    if xss_param == "QUERY_STRING":
                        anom_msg = Anomaly.MSG_QS_500
                    else:
                        anom_msg = Anomaly.MSG_PARAM_500.format(xss_param)

                    self.add_anom(request_id=injection_request.path_id,
                                  category=Anomaly.ERROR_500,
                                  level=Anomaly.HIGH_LEVEL,
                                  request=evil_request,
                                  info=anom_msg,
                                  parameter=xss_param)

                    self.log_orange("---")
                    self.log_orange(Anomaly.MSG_500, page)
                    self.log_orange(Anomaly.MSG_EVIL_REQUEST)
                    self.log_orange(evil_request.http_repr())
                    self.log_orange("---")
                    saw_internal_error = True