Example #1
0
def test_valid_content_type():
    url = "http://perdu.com/"
    responses.add(
        responses.GET,
        url,
        status=200,
        adding_headers={
            "Content-Type": "text/html"
        }
    )

    resp = requests.get(url)
    page = Page(resp)
    assert valid_xss_content_type(page)

    url = "http://perdu.com/picture.png"
    responses.add(
        responses.GET,
        url,
        status=200,
        adding_headers={
            "Content-Type": "image/png"
        }
    )

    resp = requests.get(url)
    page = Page(resp)
    assert not valid_xss_content_type(page)
Example #2
0
    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)
Example #3
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
Example #4
0
    async def must_attack(self, request: Request):
        if not valid_xss_content_type(request) or request.status in (301, 302, 303):
            # If that content-type can't be interpreted as HTML by browsers then it is useless
            # Same goes for redirections
            return False

        return True
Example #5
0
def test_valid_content_type():
    url = "http://perdu.com/"
    respx.get(url).mock(return_value=httpx.Response(
        200, headers={"Content-Type": "text/html"}))

    resp = httpx.get(url)
    page = Page(resp)
    assert valid_xss_content_type(page)

    url = "http://perdu.com/picture.png"
    respx.get(url).mock(return_value=httpx.Response(
        200, headers={"Content-Type": "image/png"}))

    resp = httpx.get(url)
    page = Page(resp)
    assert not valid_xss_content_type(page)
    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
Example #7
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
Example #8
0
    def attack(self):
        """This method searches XSS which could be permanently stored in the web application"""
        get_resources = self.persister.get_links(attack_module=self.name) if self.do_get else []

        for original_request in get_resources:
            if not valid_xss_content_type(original_request) or original_request.status in (301, 302, 303):
                # If that content-type can't be interpreted as HTML by browsers then it is useless
                # Same goes for redirections
                continue

            url = original_request.url
            target_req = web.Request(url)
            referer = original_request.referer
            headers = {}

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

            try:
                response = self.crawler.send(target_req, headers=headers)
                data = response.content
            except Timeout:
                continue
            except OSError as exception:
                # TODO: those error messages are useless, don't give any valuable information
                print(_("error: {0} while attacking {1}").format(exception.strerror, url))
                continue
            except Exception as exception:
                print(_("error: {0} while attacking {1}").format(exception, url))
                continue

            # Should we look for taint codes sent with GET in the webpages?
            # Exploiting those may imply sending more GET requests

            # Search in the page source for every taint code used by mod_xss
            for taint in self.TRIED_XSS:
                input_request = self.TRIED_XSS[taint][0]

                # Such situations should not occur as it would be stupid to block POST (or GET) requests for mod_xss
                # and not mod_permanentxss, but it is possible so let's filter that.
                if not self.do_get and input_request.method == "GET":
                    continue

                if not self.do_post and input_request.method == "POST":
                    continue

                if taint.lower() in data.lower():
                    # Code found in the webpage !
                    # Did mod_xss saw this as a reflected XSS ?
                    if taint in self.SUCCESSFUL_XSS:
                        # Yes, it means XSS payloads were injected, not just tainted code.
                        payload, flags = self.SUCCESSFUL_XSS[taint]

                        if self.check_payload(response, flags, taint):
                            # If we can find the payload again, this is in fact a stored XSS
                            get_params = input_request.get_params
                            post_params = input_request.post_params
                            file_params = input_request.file_params
                            referer = input_request.referer

                            # The following trick may seems dirty but it allows to treat GET and POST requests
                            # the same way.
                            for params_list in [get_params, post_params, file_params]:
                                for i in range(len(params_list)):
                                    parameter, value = params_list[i]
                                    parameter = quote(parameter)
                                    if value != taint:
                                        continue

                                    if params_list is file_params:
                                        params_list[i][1][0] = payload
                                    else:
                                        params_list[i][1] = payload

                                    # we found the xss payload again -> stored xss vuln
                                    evil_request = web.Request(
                                        input_request.path,
                                        method=input_request.method,
                                        get_params=get_params,
                                        post_params=post_params,
                                        file_params=file_params,
                                        referer=referer
                                    )

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

                                    if has_csp(response):
                                        description += ".\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=parameter,
                                        info=description
                                    )

                                    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,
                                        original_request.path,
                                        parameter
                                    )

                                    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("---")
                                    # FIX: search for the next code in the webpage

                    # Ok the content is stored, but will we be able to inject javascript?
                    else:
                        parameter = self.TRIED_XSS[taint][1]
                        payloads = generate_payloads(response.content, taint, self.independant_payloads)
                        flags = self.TRIED_XSS[taint][2]

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

                        self.attempt_exploit(method, payloads, input_request, parameter, taint, original_request)

            yield original_request
Example #9
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