Example #1
0
def apply_tagname_context(context, payloads, code):
    # we control the tag name
    # ex: <our_string name="column" />
    result = []

    if context["value"].startswith(code):
        for payload_infos in payloads:
            if not payload_infos["close_tag"]:
                # do new stuff
                pass
            else:
                js_code = ""
                if context["non_exec_parent"]:
                    js_code += "</" + context["non_exec_parent"] + ">"
                js_code += payload_infos["payload"].replace("__XSS__", code)

                js_code = js_code[1:]  # use independent payloads, just remove the first character (<)
                result.append((js_code, Flags(type=PayloadType.xss_closing_tag, section=payload_infos["name"])))
    else:
        for payload_infos in payloads:
            if not payload_infos["close_tag"]:
                # do new stuff
                pass
            else:
                js_code = "/>"
                if context["non_exec_parent"]:
                    js_code += "</" + context["non_exec_parent"] + ">"
                js_code += payload_infos["payload"].replace("__XSS__", code)
                result.append((js_code, Flags(type=PayloadType.xss_closing_tag, section=payload_infos["name"])))

    return result
Example #2
0
def generate_boolean_test_values(separator: str, parenthesis: bool):
    fmt_string = (
        "[VALUE]{sep} AND {left_value}={right_value} AND {sep}{padding_value}{sep}={sep}{padding_value}",
        "[VALUE]{sep}) AND {left_value}={right_value} AND ({sep}{padding_value}{sep}={sep}{padding_value}"
    )[parenthesis]

    # Generate two couple of payloads, first couple to test, second one to check for false-positives
    for __ in range(2):
        value1 = randint(10, 99)
        value2 = randint(10, 99) + value1
        padding_value = randint(10, 99)

        # First payload of the couple gives negative test
        # Due to Mutator limitations we leverage some Flags attributes to put our indicators
        yield (fmt_string.format(left_value=value1,
                                 right_value=value2,
                                 padding_value=padding_value,
                                 sep=separator),
               Flags(section="False",
                     platform="{}_{}".format("p" if parenthesis else "",
                                             separator)))

        # Second payload of the couple gives positive test
        yield (fmt_string.format(left_value=value1,
                                 right_value=value1,
                                 padding_value=padding_value,
                                 sep=separator),
               Flags(section="True",
                     platform="{}_{}".format("p" if parenthesis else "",
                                             separator)))
Example #3
0
def generate_boolean_test_values(separator: str, parenthesis: bool):
    fmt_string = (
        "[VALUE]{sep} AND {left_value}={right_value} AND {sep}{padding_value}{sep}={sep}{padding_value}",
        "[VALUE]{sep}) AND {left_value}={right_value} AND ({sep}{padding_value}{sep}={sep}{padding_value}"
    )[parenthesis]

    for __ in range(2):
        value1 = randint(10, 99)
        value2 = randint(10, 99) + value1
        padding_value = randint(10, 99)

        # First two payloads give negative tests
        # Due to Mutator limitations we leverage some Flags attributes to put our indicators
        yield (
            fmt_string.format(left_value=value1, right_value=value2, padding_value=padding_value, sep=separator),
            Flags(section="False", platform=f"{'p' if parenthesis else ''}_{separator}")
        )

    for __ in range(2):
        value1 = randint(10, 99)
        padding_value = randint(10, 99)

        # Last two payloads give positive tests
        yield (
            fmt_string.format(left_value=value1, right_value=value1, padding_value=padding_value, sep=separator),
            Flags(section="True", platform=f"{'p' if parenthesis else ''}_{separator}")
        )
Example #4
0
def apply_attrval_context(context, payloads, code):
    # Our string is in the value of a tag attribute
    # ex: <a href="our_string"></a>
    result = []

    for payload_infos in payloads:
        if not payload_infos["close_tag"]:
            # Payload keeping the tag open
            if context["tag"] in payload_infos["tag"] and payload_infos["attribute"] not in context["events"]:
                if not context["separator"]:
                    attr_separator = " "
                    value_separator = ""
                else:
                    attr_separator = value_separator = context["separator"]

                if payload_infos["tag"] == ["frame"] and payload_infos["attribute"] == "src":
                    # This is a special case... Maybe we should improve that kind of behavior by having something
                    # similar to the match_type (from xssPayloads.ini) in the context
                    js_code = payload_infos["payload"].replace("__XSS__", code)
                else:
                    try:
                        js_code = "y"  # Not empty value to force non-fuzzy HTML interpretation
                        js_code += meet_requirements(
                            payload_infos.get("requirements", []),
                            context.get("special_attributes", [])
                        )
                        js_code += payload_infos["payload"].replace("__XSS__", code)
                        js_code = js_code.replace("[ATTR_SEP]", attr_separator)
                        js_code = js_code.replace("[VALUE_SEP]", value_separator)
                    except RuntimeError:
                        continue

                result.append(
                    (js_code, Flags(payload_type=PayloadType.xss_non_closing_tag, section=payload_infos["name"]))
                )

        else:
            js_code = context["separator"]
            # we must deal differently with self-closing tags
            # see https://developer.mozilla.org/en-US/docs/Glossary/empty_element for reference
            if context["tag"].lower() in [
                    "area", "base", "br", "col", "embed", "hr", "img", "input", "keygen", "link", "meta", "param",
                    "source", "track", "wbr",
                    "frame"  # Not in Mozilla list but I guess it is because it is deprecated
            ]:
                # We don't even need a slash to mark the end of the tag
                js_code += ">"
            else:
                js_code += "></" + context["tag"] + ">"

            if context["non_exec_parent"] == "frameset":
                if payload_infos["tag"] != ["frame"]:
                    continue
            elif context["non_exec_parent"]:
                js_code += "</" + context["non_exec_parent"] + ">"

            js_code += payload_infos["payload"].replace("__XSS__", code)
            result.append((js_code, Flags(payload_type=PayloadType.xss_closing_tag, section=payload_infos["name"])))

    return result
Example #5
0
def apply_comment_context(context, payloads, code):
    # Injection occurred in a comment tag
    # ex: <!-- <div> whatever our_string blablah </div> -->
    result = []

    prefix = "-->"
    if context["parent"] in ["script", "title", "textarea"]:
        # we can't execute javascript under title or textarea tags and it's too hard to be sure our payload
        # will be executed if we have partial control over a script tag content, so let's escape them
        if context["non_exec_parent"] != "":
            prefix += "</" + context["non_exec_parent"] + ">"
        else:
            prefix += "</{0}>".format(context["parent"])

    for payload_infos in payloads:
        if not payload_infos["close_tag"]:
            # do new stuff
            pass
        else:
            js_code = prefix + payload_infos["payload"].replace(
                "__XSS__", code)
            result.append((js_code,
                           Flags(type=PayloadType.xss_closing_tag,
                                 section=payload_infos["name"])))

    return result
Example #6
0
def apply_text_context(context, payloads, code):
    # we control the text of the tag
    # ex: <textarea>our_string</textarea>
    result = []
    prefix = ""

    if context["parent"] in ["script", "title", "textarea", "style"]:
        # we can't execute javascript under title or textarea tags and it's too hard to be sure our payload
        # will be executed if we have partial control over a script tag content, so let's escape them
        if context["non_exec_parent"] != "":
            prefix = "</" + context["non_exec_parent"] + ">"
        else:
            prefix = f"</{context['parent']}>"

    for payload_infos in payloads:
        if not payload_infos["close_tag"]:
            # do new stuff
            pass
        else:
            js_code = prefix + payload_infos["payload"].replace(
                "__XSS__", code)
            result.append((js_code,
                           Flags(payload_type=PayloadType.xss_closing_tag,
                                 section=payload_infos["name"])))

    return result
Example #7
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
Example #8
0
 def random_string():
     """Create a random unique ID that will be used to test injection."""
     # doesn't uppercase letters as BeautifulSoup make some data lowercase
     code = "w" + "".join([
         random.choice("0123456789abcdefghjijklmnopqrstuvwxyz")
         for __ in range(0, 9)
     ])
     return code, Flags()
Example #9
0
async def test_whole_stuff():
    # Test attacking all kind of parameter without crashing
    respx.get("http://perdu.com/").mock(
        return_value=httpx.Response(200, text="Default page"))
    respx.get("http://perdu.com/admin").mock(return_value=httpx.Response(
        301, text="Hello there", headers={"Location": "/admin/"}))
    respx.get("http://perdu.com/admin/").mock(
        return_value=httpx.Response(200, text="Hello there"))
    respx.get("http://perdu.com/config.inc").mock(
        return_value=httpx.Response(200, text="pass = 123456"))
    respx.get("http://perdu.com/admin/authconfig.php").mock(
        return_value=httpx.Response(200, text="Hello there"))
    respx.get(url__regex=r"http://perdu\.com/.*").mock(
        return_value=httpx.Response(404))

    persister = Mock()

    request = Request("http://perdu.com/")
    request.path_id = 1
    request.set_headers({"content-type": "text/html"})
    # Buster module will get requests from the persister
    persister.get_links.return_value = AsyncIterator([request])

    crawler = AsyncCrawler("http://perdu.com/", timeout=1)
    options = {"timeout": 10, "level": 2}
    logger = Mock()

    with patch("wapitiCore.attack.mod_buster.mod_buster.payloads",
               [("nawak", Flags()), ("admin", Flags()),
                ("config.inc", Flags()), ("authconfig.php", Flags())]):
        module = mod_buster(crawler, persister, logger, options, Event())
        module.verbose = 2
        module.do_get = True
        await module.attack(request)

        assert module.known_dirs == [
            "http://perdu.com/", "http://perdu.com/admin/"
        ]
        assert module.known_pages == [
            "http://perdu.com/config.inc",
            "http://perdu.com/admin/authconfig.php"
        ]

    await crawler.close()
Example #10
0
class mod_redirect(Attack):
    """This class implements an open-redirect attack"""
    # Won't work with PHP >= 4.4.2

    name = "redirect"
    MSG_VULN = _("Open Redirect")
    do_get = True
    do_post = False
    payloads = ("https://openbugbounty.org/", Flags())

    def attack(self):
        mutator = self.get_mutator()

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

        for http_res in http_resources:
            page = http_res.path

            for mutated_request, parameter, payload, flags in mutator.mutate(
                    http_res):
                try:
                    if self.verbose == 2:
                        print("+ {0}".format(mutated_request.url))

                    response = self.crawler.send(mutated_request)

                    if any([
                            url.startswith("https://openbugbounty.org/")
                            for url in response.all_redirections
                    ]):
                        self.add_vuln(
                            request_id=http_res.path_id,
                            category=Vulnerability.REDIRECT,
                            level=Vulnerability.MEDIUM_LEVEL,
                            request=mutated_request,
                            parameter=parameter,
                            info=_("{0} via injection in the parameter {1}"
                                   ).format(self.MSG_VULN, parameter))

                        if parameter == "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,
                                     parameter)
                        self.log_red(Vulnerability.MSG_EVIL_REQUEST)
                        self.log_red(mutated_request.http_repr())
                        self.log_red("---")

                except (RequestException, KeyboardInterrupt) as exception:
                    yield exception

            yield http_res
Example #11
0
class mod_redirect(Attack):
    """Detect Open Redirect vulnerabilities."""
    # Won't work with PHP >= 4.4.2

    name = "redirect"
    MSG_VULN = _("Open Redirect")
    do_get = True
    do_post = False
    payloads = ("https://openbugbounty.org/", Flags())

    def __init__(self, crawler, persister, attack_options, stop_event):
        super().__init__(crawler, persister, attack_options, stop_event)
        self.mutator = self.get_mutator()

    async def attack(self, request: Request):
        page = request.path

        for mutated_request, parameter, __, __ in self.mutator.mutate(request):
            if self.verbose == 2:
                logging.info("[¨] {0}".format(mutated_request.url))

            try:
                response = await self.crawler.async_send(mutated_request)
            except RequestError:
                self.network_errors += 1
                continue

            if any([url.startswith("https://openbugbounty.org/") for url in response.all_redirections]):
                await self.add_vuln_low(
                    request_id=request.path_id,
                    category=NAME,
                    request=mutated_request,
                    parameter=parameter,
                    info=_("{0} via injection in the parameter {1}").format(self.MSG_VULN, parameter)
                )

                if parameter == "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,
                    parameter
                )
                self.log_red(Messages.MSG_EVIL_REQUEST)
                self.log_red(mutated_request.http_repr())
                self.log_red("---")
Example #12
0
def apply_attrname_context(context, payloads, code):
    # we control an attribute name
    # ex: <a our_string="/index.html">
    result = []

    if code == context["name"]:
        for payload_infos in payloads:
            if not payload_infos["close_tag"]:
                # do new stuff
                pass
            else:
                js_code = '>'
                if context["non_exec_parent"]:
                    js_code += "</" + context["non_exec_parent"] + ">"
                js_code += payload_infos["payload"].replace("__XSS__", code)

                result.append((js_code, Flags(type=PayloadType.xss_closing_tag, section=payload_infos["name"])))

    return result
Example #13
0
class mod_sql(Attack):
    """
    This class implements an error-based SQL Injection attack
    """

    time_to_sleep = 6
    name = "sql"
    payloads = ("\xBF'\"(", Flags())
    filename_payload = "'\"("  # TODO: wait for https://github.com/shazow/urllib3/pull/856 then use that for files upld

    @staticmethod
    def _find_pattern_in_response(data):
        if "You have an error in your SQL syntax" in data:
            return _("MySQL Injection")
        if "supplied argument is not a valid MySQL" in data:
            return _("MySQL Injection")
        if "Warning: mysql_fetch_array()" in data:
            return _("MySQL Injection")
        if "mysqli_fetch_assoc() expects parameter 1 to be" in data:
            return _("MySQL Injection")
        if "com.mysql.jdbc.exceptions" in data:
            return _("MySQL Injection")
        if "MySqlException (0x" in data:
            return _("MySQL Injection")
        if ("[Microsoft][ODBC Microsoft Access Driver]" in data
                or "Syntax error in string in query expression " in data):
            return _("MSAccess-Based SQL Injection")
        if "[Microsoft][ODBC SQL Server Driver]" in data:
            return _("MSSQL-Based Injection")
        if 'Microsoft OLE DB Provider for ODBC Drivers</font> <font size="2" face="Arial">error' in data:
            return _("MSSQL-Based Injection")
        if "Microsoft OLE DB Provider for ODBC Drivers" in data:
            return _("MSSQL-Based Injection")
        if "java.sql.SQLException: Syntax error or access violation" in data:
            return _("Java.SQL Injection")
        if "java.sql.SQLException: Unexpected end of command" in data:
            return _("Java.SQL Injection")
        if "PostgreSQL query failed: ERROR: parser:" in data:
            return _("PostgreSQL Injection")
        if "Warning: pg_query()" in data:
            return _("PostgreSQL Injection")
        if "XPathException" in data:
            return _("XPath Injection")
        if "Warning: SimpleXMLElement::xpath():" in data:
            return _("XPath Injection")
        if "supplied argument is not a valid ldap" in data or "javax.naming.NameNotFoundException" in data:
            return _("LDAP Injection")
        if "DB2 SQL error:" in data:
            return _("DB2 Injection")
        if "Dynamic SQL Error" in data:
            return _("Interbase Injection")
        if "Sybase message:" in data:
            return _("Sybase Injection")
        if "Unclosed quotation mark after the character string" in data:
            return _(".NET SQL Injection")
        if "error '80040e14'" in data and "Incorrect syntax near" in data:
            return _("MSSQL-Based Injection")
        if "StatementCallback; bad SQL grammar" in data:
            return _("Spring JDBC Injection")

        ora_test = re.search(r"ORA-[0-9]{4,}", data)
        if ora_test is not None:
            return _("Oracle Injection") + " " + ora_test.group(0)

        return ""

    def is_false_positive(self, request):
        try:
            response = self.crawler.send(request)
        except RequestException:
            pass
        else:
            if self._find_pattern_in_response(response.content):
                return True
        return False

    def set_timeout(self, timeout):
        self.time_to_sleep = str(1 + int(timeout))

    def attack(self):
        mutator = self.get_mutator()

        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))

            timeouted = False
            page = original_request.path
            saw_internal_error = False
            current_parameter = None
            vulnerable_parameter = False

            for mutated_request, parameter, payload, flags in mutator.mutate(
                    original_request):
                try:
                    if current_parameter != parameter:
                        # Forget what we know about current parameter
                        current_parameter = parameter
                        vulnerable_parameter = False
                    elif vulnerable_parameter:
                        # If parameter is vulnerable, just skip till next parameter
                        continue

                    if self.verbose == 2:
                        print("[¨] {0}".format(mutated_request))

                    try:
                        response = self.crawler.send(mutated_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(mutated_request.http_repr())
                        self.log_orange("---")

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

                        self.add_anom(request_id=original_request.path_id,
                                      category=Anomaly.RES_CONSUMPTION,
                                      level=Anomaly.MEDIUM_LEVEL,
                                      request=mutated_request,
                                      info=anom_msg,
                                      parameter=parameter)
                        timeouted = True
                    else:
                        vuln_info = self._find_pattern_in_response(
                            response.content)
                        if vuln_info and not self.is_false_positive(
                                original_request):
                            # An error message implies that a vulnerability may exists

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

                            self.add_vuln(request_id=original_request.path_id,
                                          category=Vulnerability.SQL_INJECTION,
                                          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, vuln_info,
                                page, parameter)
                            self.log_red(Vulnerability.MSG_EVIL_REQUEST)
                            self.log_red(mutated_request.http_repr())
                            self.log_red("---")

                            # We reached maximum exploitation for this parameter, don't send more payloads
                            vulnerable_parameter = True
                            continue

                        elif response.status == 500 and not saw_internal_error:
                            saw_internal_error = True
                            if parameter == "QUERY_STRING":
                                anom_msg = Anomaly.MSG_QS_500
                            else:
                                anom_msg = Anomaly.MSG_PARAM_500.format(
                                    parameter)

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

                            self.log_orange("---")
                            self.log_orange(Anomaly.MSG_500, page)
                            self.log_orange(Anomaly.MSG_EVIL_REQUEST)
                            self.log_orange(mutated_request.http_repr())
                            self.log_orange("---")
                except (KeyboardInterrupt, RequestException) as exception:
                    yield exception

            yield original_request
Example #14
0
def test_whole_stuff():
    # Test attacking all kind of parameter without crashing
    responses.add(
        responses.GET,
        url="http://perdu.com/",
        body="Default page"
    )

    responses.add(
        responses.GET,
        url="http://perdu.com/admin",
        body="Hello there",
        headers={"Location": "/admin/"},
        status=301
    )

    responses.add(
        responses.GET,
        url="http://perdu.com/admin/",
        body="Hello there"
    )

    responses.add(
        responses.GET,
        url="http://perdu.com/config.inc",
        body="pass = 123456"
    )

    responses.add(
        responses.GET,
        url="http://perdu.com/admin/authconfig.php",
        body="Hello there"
    )

    responses.add(
        responses.GET,
        url=re.compile(r"http://perdu.com/.*"),
        status=404
    )

    persister = FakePersister()

    request = Request("http://perdu.com/")
    request.path_id = 1
    request.set_headers({"content-type": "text/html"})
    persister.requests.append(request)

    crawler = Crawler("http://perdu.com/", timeout=1)
    options = {"timeout": 10, "level": 2}
    logger = Mock()

    with patch(
            "wapitiCore.attack.mod_buster.mod_buster.payloads",
            [("nawak", Flags()), ("admin", Flags()), ("config.inc", Flags()), ("authconfig.php", Flags())]
    ):
        module = mod_buster(crawler, persister, logger, options)
        module.verbose = 2
        module.do_get = True
        for __ in module.attack():
            pass

        assert module.known_dirs == ["http://perdu.com/", "http://perdu.com/admin/"]
        assert module.known_pages == ["http://perdu.com/config.inc", "http://perdu.com/admin/authconfig.php"]
Example #15
0
    def mutate(self, request: Request):
        get_params = request.get_params
        post_params = request.post_params
        file_params = request.file_params
        referer = request.referer

        # estimation = self.estimate_requests_count(request)
        #
        # if self._attacks_per_url_pattern[request.hash_params] + estimation > self._max_queries_per_pattern:
        #     # Otherwise (pattern already attacked), make sure we don't exceed maximum allowed
        #     return
        #
        # self._attacks_per_url_pattern[request.hash_params] += estimation

        for params_list in [get_params, post_params, file_params]:
            for i, __ in enumerate(params_list):
                param_name = quote(params_list[i][0])

                if self._skip_list and param_name in self._skip_list:
                    continue

                if self._parameters and param_name not in self._parameters:
                    continue

                saved_value = params_list[i][1]
                if saved_value is None:
                    saved_value = ""

                if params_list is file_params:
                    params_list[i][1] = ("__PAYLOAD__", saved_value[1], saved_value[2])
                else:
                    params_list[i][1] = "__PAYLOAD__"

                attack_pattern = Request(
                    request.path,
                    method=request.method,
                    get_params=get_params,
                    post_params=post_params,
                    file_params=file_params
                )

                if hash(attack_pattern) not in self._attack_hashes:
                    self._attack_hashes.add(hash(attack_pattern))

                    payload = SSRF_PAYLOAD.format(
                        external_endpoint=self._endpoint,
                        random_id=self._session_id,
                        path_id=request.path_id,
                        hex_param=hexlify(param_name.encode("utf-8", errors="replace")).decode()
                    )

                    if params_list is file_params:
                        params_list[i][1] = (payload, saved_value[1], saved_value[2])
                        method = PayloadType.file
                    else:
                        params_list[i][1] = payload
                        if params_list is get_params:
                            method = PayloadType.get
                        else:
                            method = PayloadType.post

                    evil_req = Request(
                        request.path,
                        method=request.method,
                        get_params=get_params,
                        post_params=post_params,
                        file_params=file_params,
                        referer=referer,
                        link_depth=request.link_depth
                    )
                    yield evil_req, param_name, payload, Flags(method=method)

                params_list[i][1] = saved_value

        if not get_params and request.method == "GET" and self._qs_inject:
            attack_pattern = Request(
                f"{request.path}?__PAYLOAD__",
                method=request.method,
                referer=referer,
                link_depth=request.link_depth
            )

            if hash(attack_pattern) not in self._attack_hashes:
                self._attack_hashes.add(hash(attack_pattern))

                payload = SSRF_PAYLOAD.format(
                    external_endpoint=self._endpoint,
                    random_id=self._session_id,
                    path_id=request.path_id,
                    hex_param=hexlify(b"QUERY_STRING").decode()
                )

                evil_req = Request(
                    f"{request.path}?{quote(payload)}",
                    method=request.method,
                    referer=referer,
                    link_depth=request.link_depth
                )

                yield evil_req, "QUERY_STRING", payload, Flags(method=PayloadType.get)
Example #16
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("---")
Example #17
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("---")
Example #18
0
class ModuleCrlf(Attack):
    """Detect Carriage Return Line Feed (CRLF) injection vulnerabilities."""
    # Won't work with PHP >= 4.4.2

    name = "crlf"
    MSG_VULN = _("CRLF Injection")
    do_get = True
    do_post = True
    payloads = (quote("http://www.google.fr\r\nwapiti: 3.1.1 version"),
                Flags())

    def __init__(self, crawler, persister, attack_options, stop_event):
        super().__init__(crawler, persister, attack_options, stop_event)
        self.mutator = self.get_mutator()

    async def attack(self, request: Request):
        page = request.path

        for mutated_request, parameter, _payload, _flags in self.mutator.mutate(
                request):
            log_verbose(f"[¨] {mutated_request.url}")

            try:
                response = await self.crawler.async_send(mutated_request)
            except ReadTimeout:
                self.network_errors += 1
                await self.add_anom_medium(request_id=request.path_id,
                                           category=Messages.RES_CONSUMPTION,
                                           request=mutated_request,
                                           parameter=parameter,
                                           info="Timeout (" + parameter + ")",
                                           wstg=RESOURCE_CONSUMPTION_WSTG_CODE)

                log_orange("---")
                log_orange(Messages.MSG_TIMEOUT, page)
                log_orange(Messages.MSG_EVIL_REQUEST)
                log_orange(mutated_request.http_repr())
                log_orange("---")
            except HTTPStatusError:
                self.network_errors += 1
                logging.error(
                    _("Error: The server did not understand this request"))
            except RequestError:
                self.network_errors += 1
            else:
                if "wapiti" in response.headers:
                    await self.add_vuln_low(
                        request_id=request.path_id,
                        category=NAME,
                        request=mutated_request,
                        parameter=parameter,
                        info=_(
                            "{0} via injection in the parameter {1}").format(
                                self.MSG_VULN, parameter),
                        wstg=WSTG_CODE)

                    if parameter == "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, parameter)
                    log_red(Messages.MSG_EVIL_REQUEST)
                    log_red(mutated_request.http_repr())
                    log_red("---")
Example #19
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", b"GIF89a", "image/gif")]]
    )
    mutator = Mutator(payloads=[("INJECT", Flags())])
    count = 0
    for __ in mutator.mutate(req):
        count += 1
    assert count == 4

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

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

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

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

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

    mutator = Mutator(
        payloads=[("PAYLOAD_1", Flags()), ("PAYLOAD_2", Flags()), ("PAYLOAD_3", Flags())],
        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", b"GIF89a", "image/gif")]]
    )
    mutator = Mutator(payloads=[("INJECT", Flags())])
    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", Flags()), ("PAYLOAD_2", Flags())], qs_inject=True)
    count = 0
    for __, __, __, __ in mutator.mutate(req3):
        count += 1
    assert count == 2
Example #20
0
class mod_crlf(Attack):
    """Detect Carriage Return Line Feed (CRLF) injection vulnerabilities."""
    # Won't work with PHP >= 4.4.2

    name = "crlf"
    MSG_VULN = _("CRLF Injection")
    do_get = True
    do_post = True
    payloads = (quote("http://www.google.fr\r\nwapiti: 3.0.4 version"), Flags())

    def __init__(self, crawler, persister, logger, attack_options):
        super().__init__(crawler, persister, logger, attack_options)
        self.mutator = self.get_mutator()

    def attack(self, request: Request):
        page = request.path

        for mutated_request, parameter, payload, flags in self.mutator.mutate(request):
            if self.verbose == 2:
                print("[¨] {0}".format(mutated_request.url))

            try:
                response = self.crawler.send(mutated_request)
            except ReadTimeout:
                self.network_errors += 1
                self.add_anom(
                    request_id=request.path_id,
                    category=Messages.RES_CONSUMPTION,
                    level=MEDIUM_LEVEL,
                    request=mutated_request,
                    parameter=parameter,
                    info="Timeout (" + parameter + ")"
                )

                self.log_orange("---")
                self.log_orange(Messages.MSG_TIMEOUT, page)
                self.log_orange(Messages.MSG_EVIL_REQUEST)
                self.log_orange(mutated_request.http_repr())
                self.log_orange("---")
            except HTTPError:
                self.network_errors += 1
                self.log(_("Error: The server did not understand this request"))
            except RequestException:
                self.network_errors += 1
            else:
                if "wapiti" in response.headers:
                    self.add_vuln(
                        request_id=request.path_id,
                        category=NAME,
                        level=LOW_LEVEL,
                        request=mutated_request,
                        parameter=parameter,
                        info=_("{0} via injection in the parameter {1}").format(self.MSG_VULN, parameter)
                    )

                    if parameter == "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,
                        parameter
                    )
                    self.log_red(Messages.MSG_EVIL_REQUEST)
                    self.log_red(mutated_request.http_repr())
                    self.log_red("---")
Example #21
0
class mod_sql(Attack):
    """
    Detect SQL (but also LDAP and XPath) injection vulnerabilities by triggering errors (error-based technique).
    """

    time_to_sleep = 6
    name = "sql"
    payloads = ("[VALUE]\xBF'\"(", Flags())
    filename_payload = "'\"("  # TODO: wait for https://github.com/shazow/urllib3/pull/856 then use that for files upld

    @staticmethod
    def _find_pattern_in_response(data):
        for dbms, regex_list in DBMS_ERROR_PATTERNS.items():
            for regex in regex_list:
                if regex.search(data):
                    return _("SQL Injection") + " (DMBS: {})".format(dbms)

        # Can't guess the DBMS but may be useful
        if "Unclosed quotation mark after the character string" in data:
            return _(".NET SQL Injection")
        if "StatementCallback; bad SQL grammar" in data:
            return _("Spring JDBC Injection")

        if "XPathException" in data:
            return _("XPath Injection")
        if "Warning: SimpleXMLElement::xpath():" in data:
            return _("XPath Injection")
        if "supplied argument is not a valid ldap" in data or "javax.naming.NameNotFoundException" in data:
            return _("LDAP Injection")

        return ""

    def is_false_positive(self, request):
        try:
            response = self.crawler.send(request)
        except RequestException:
            pass
        else:
            if self._find_pattern_in_response(response.content):
                return True
        return False

    def set_timeout(self, timeout):
        self.time_to_sleep = str(1 + int(timeout))

    def attack(self):
        mutator = self.get_mutator()

        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))

            timeouted = False
            page = original_request.path
            saw_internal_error = False
            current_parameter = None
            vulnerable_parameter = False

            for mutated_request, parameter, payload, flags in mutator.mutate(
                    original_request):
                try:
                    if current_parameter != parameter:
                        # Forget what we know about current parameter
                        current_parameter = parameter
                        vulnerable_parameter = False
                    elif vulnerable_parameter:
                        # If parameter is vulnerable, just skip till next parameter
                        continue

                    if self.verbose == 2:
                        print("[¨] {0}".format(mutated_request))

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

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

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

                        self.add_anom(request_id=original_request.path_id,
                                      category=Messages.RES_CONSUMPTION,
                                      level=MEDIUM_LEVEL,
                                      request=mutated_request,
                                      info=anom_msg,
                                      parameter=parameter)
                        timeouted = True
                    else:
                        vuln_info = self._find_pattern_in_response(
                            response.content)
                        if vuln_info and not self.is_false_positive(
                                original_request):
                            # An error message implies that a vulnerability may exists

                            if 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, parameter)

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

                            self.log_red("---")
                            self.log_red(
                                Messages.MSG_QS_INJECT
                                if parameter == "QUERY_STRING" else
                                Messages.MSG_PARAM_INJECT, vuln_info, page,
                                parameter)
                            self.log_red(Messages.MSG_EVIL_REQUEST)
                            self.log_red(mutated_request.http_repr())
                            self.log_red("---")

                            # We reached maximum exploitation for this parameter, don't send more payloads
                            vulnerable_parameter = True
                            continue

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

                            self.add_anom(request_id=original_request.path_id,
                                          category=Messages.ERROR_500,
                                          level=HIGH_LEVEL,
                                          request=mutated_request,
                                          info=anom_msg,
                                          parameter=parameter)

                            self.log_orange("---")
                            self.log_orange(Messages.MSG_500, page)
                            self.log_orange(Messages.MSG_EVIL_REQUEST)
                            self.log_orange(mutated_request.http_repr())
                            self.log_orange("---")
                except (KeyboardInterrupt, RequestException) as exception:
                    yield exception

            yield original_request
Example #22
0
class ModuleSql(Attack):
    """
    Detect SQL (also LDAP and XPath) injection vulnerabilities using error-based or boolean-based (blind) techniques.
    """

    time_to_sleep = 6
    name = "sql"
    payloads = ("[VALUE]\xBF'\"(", Flags())
    filename_payload = "'\"("  # TODO: wait for https://github.com/shazow/urllib3/pull/856 then use that for files upld

    def __init__(self, crawler, persister, attack_options, stop_event):
        super().__init__(crawler, persister, attack_options, stop_event)
        self.mutator = self.get_mutator()

    @staticmethod
    def _find_pattern_in_response(data):
        for dbms, regex_list in DBMS_ERROR_PATTERNS.items():
            for regex in regex_list:
                if regex.search(data):
                    return f"{_('SQL Injection')} (DMBS: {dbms}"

        # Can't guess the DBMS but may be useful
        if "Unclosed quotation mark after the character string" in data:
            return _(".NET SQL Injection")
        if "StatementCallback; bad SQL grammar" in data:
            return _("Spring JDBC Injection")

        if "XPathException" in data:
            return _("XPath Injection")
        if "Warning: SimpleXMLElement::xpath():" in data:
            return _("XPath Injection")
        if "supplied argument is not a valid ldap" in data or "javax.naming.NameNotFoundException" in data:
            return _("LDAP Injection")

        return ""

    async def is_false_positive(self, request):
        try:
            response = await self.crawler.async_send(request)
        except RequestError:
            self.network_errors += 1
        else:
            if self._find_pattern_in_response(response.content):
                return True
        return False

    def set_timeout(self, timeout):
        self.time_to_sleep = str(1 + int(timeout))

    async def attack(self, request: Request):
        vulnerable_parameters = await self.error_based_attack(request)
        await self.boolean_based_attack(request, vulnerable_parameters)

    async def error_based_attack(self, request: Request):
        page = request.path
        saw_internal_error = False
        current_parameter = None
        vulnerable_parameter = False
        vulnerable_parameters = set()

        for mutated_request, parameter, __, __ in self.mutator.mutate(request):
            if current_parameter != parameter:
                # Forget what we know about current parameter
                current_parameter = parameter
                vulnerable_parameter = False
            elif vulnerable_parameter:
                # If parameter is vulnerable, just skip till next parameter
                continue

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

            try:
                response = await self.crawler.async_send(mutated_request)
            except RequestError:
                self.network_errors += 1
            else:
                vuln_info = self._find_pattern_in_response(response.content)
                if vuln_info and not await self.is_false_positive(request):
                    # An error message implies that a vulnerability may exists

                    if 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, parameter)

                    await self.add_vuln_critical(
                        request_id=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,
                        vuln_info,
                        page,
                        parameter
                    )
                    log_red(Messages.MSG_EVIL_REQUEST)
                    log_red(mutated_request.http_repr())
                    log_red("---")

                    # We reached maximum exploitation for this parameter, don't send more payloads
                    vulnerable_parameter = True
                    vulnerable_parameters.add(parameter)

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

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

                    log_orange("---")
                    log_orange(Messages.MSG_500, page)
                    log_orange(Messages.MSG_EVIL_REQUEST)
                    log_orange(mutated_request.http_repr())
                    log_orange("---")

        return vulnerable_parameters

    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
Example #23
0
class mod_crlf(Attack):
    """Detect Carriage Return Line Feed (CRLF) injection vulnerabilities."""
    # Won't work with PHP >= 4.4.2

    name = "crlf"
    MSG_VULN = _("CRLF Injection")
    do_get = False
    do_post = False
    payloads = (quote("http://www.google.fr\r\nwapiti: 3.0.3 version"),
                Flags())

    def attack(self):
        mutator = self.get_mutator()

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

        for http_res in http_resources:
            page = http_res.path

            for mutated_request, parameter, payload, flags in mutator.mutate(
                    http_res):
                try:
                    if self.verbose == 2:
                        print("+ {0}".format(mutated_request.url))
                    try:
                        response = self.crawler.send(mutated_request)

                    except ReadTimeout:
                        self.add_anom(request_id=http_res.path_id,
                                      category=Messages.RES_CONSUMPTION,
                                      level=MEDIUM_LEVEL,
                                      request=mutated_request,
                                      parameter=parameter,
                                      info="Timeout (" + parameter + ")")

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

                    except HTTPError:
                        self.log(
                            _("Error: The server did not understand this request"
                              ))
                    else:
                        if "wapiti" in response.headers:
                            self.add_vuln(
                                request_id=http_res.path_id,
                                category=NAME,
                                level=HIGH_LEVEL,
                                request=mutated_request,
                                parameter=parameter,
                                info=_("{0} via injection in the parameter {1}"
                                       ).format(self.MSG_VULN, parameter))

                            if parameter == "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,
                                         parameter)
                            self.log_red(Messages.MSG_EVIL_REQUEST)
                            self.log_red(mutated_request.http_repr())
                            self.log_red("---")

                except (RequestException, KeyboardInterrupt) as exception:
                    yield exception

            yield http_res