コード例 #1
0
ファイル: cors.py プロジェクト: vinaypotluri/syntribos
class CorsHeader(base.BaseTestCase):
    """Test to check if CORS header variables are set to wild characters."""

    test_name = "CORS_HEADER"
    test_type = "headers"
    client = client()
    failures = []

    @classmethod
    def get_test_cases(cls, filename, file_content):

        request_obj = parser.create_request(
            file_content, os.environ.get("SYNTRIBOS_ENDPOINT"))
        request_obj.headers['Origin'] = 'http://example.com'
        cls.resp = cls.client.send_request(request_obj)
        yield cls

    def test_case(self):

        if 'Access-Control-Allow-Origin' in self.resp.headers:
            if self.resp.headers['Access-Control-Allow-Origin'] == "*":
                self.register_issue(
                    Issue(
                        test="CORS_HEADER",
                        severity="Medium",
                        confidence="High",
                        text=("CORS header `Access-Control-Allow-Origin` set"
                              " to a wild character, this header should"
                              " always be set to a white listed set of URIs")))

        if 'Access-Control-Allow-Methods' in self.resp.headers:
            if self.resp.headers['Access-Control-Allow-Methods'] == "*":
                self.register_issue(
                    Issue(test="CORS_HEADER",
                          severity="Low",
                          confidence="High",
                          text=("CORS header `Access-Control-Allow-Methods`"
                                " set to a wild character,it is a good"
                                " practice to give a white list of allowed"
                                " methods.")))

        if 'Access-Control-Allow-Headers' in self.resp.headers:
            if self.resp.headers['Access-Control-Allow-Headers'] == "*":
                self.register_issue(
                    Issue(test="CORS_HEADER",
                          severity="Low",
                          confidence="High",
                          text=("CORS header `Access-Control-Allow-Headers`"
                                " set to a wild character,it is a good"
                                " practice to give a white list of allowed"
                                " headers")))
コード例 #2
0
class XstHeader(base.BaseTestCase):
    """Test for Cross Site Tracing vulnerabilities.

    A TRACE request with a fake request is sent to the server,
    if the server responds back with the entire request flow
    as a reponse, this can be termed a vulnerability. All TRACE
    requests should be vetted and filtered by the server to
    prevent accidental leakage of cookies, etc. If an app is
    already vulnerable to XSS attacks, then this can enable an
    attacker to steal session cookies.

    :more: https://www.owasp.org/index.php/Cross_Site_Tracing
    """

    test_name = "XST_HEADERS"
    test_type = "headers"
    client = client()
    failures = []

    @classmethod
    def get_test_cases(cls, filename, file_content):
        xst_header = {"TRACE_THIS": "XST_Vuln"}
        request_obj = parser.create_request(file_content,
                                            CONF.syntribos.endpoint,
                                            meta_vars=None)
        prepared_copy = request_obj.get_prepared_copy()
        prepared_copy.method = "TRACE"
        prepared_copy.headers.update(xst_header)
        cls.test_resp, cls.test_signals = cls.client.send_request(
            prepared_copy)
        yield cls

    def test_case(self):
        self.test_signals.register(xst(self))

        xst_slugs = [
            slugs for slugs in self.test_signals.all_slugs
            if "HEADER_XST" in slugs
        ]
        for i in xst_slugs:  # noqa
            test_severity = syntribos.LOW
            self.register_issue(defect_type="XST_HEADER",
                                severity=test_severity,
                                confidence=syntribos.HIGH,
                                description=(_("XST vulnerability found.\n"
                                               "Make sure that response to a "
                                               "TRACE request is filtered.")))
コード例 #3
0
class CorsHeader(base.BaseTestCase):
    """Test for CORS wild character vulnerabilities in HTTP header."""

    test_name = "CORS_WILDCARD_HEADERS"
    parameter_location = "headers"
    client = client()
    failures = []

    @classmethod
    def get_test_cases(cls, filename, file_content, meta_vars):
        request_obj = parser.create_request(
            file_content, CONF.syntribos.endpoint, meta_vars
        )
        prepared_copy = request_obj.get_prepared_copy()
        cls.test_resp, cls.test_signals = cls.client.send_request(
            prepared_copy)
        cls.test_req = request_obj.get_prepared_copy()
        yield cls

    def test_case(self):
        self.test_signals.register(cors(self))

        cors_slugs = [
            slugs for slugs in self.test_signals.all_slugs
            if "HEADER_CORS" in slugs]
        for slug in cors_slugs:
            if "ORIGIN" in slug:
                test_severity = syntribos.HIGH
            else:
                test_severity = syntribos.MEDIUM
            self.register_issue(
                defect_type="CORS_HEADER",
                severity=test_severity,
                confidence=syntribos.HIGH,
                description=(
                    _("CORS header vulnerability found.\n"
                      "Make sure that the header is not assigned "
                      "a wildcard character.")))
コード例 #4
0
ファイル: base.py プロジェクト: appknox/syntribos
class BaseTestCase(unittest.TestCase):
    """Base class for building new tests

    :attribute str test_name: A name like ``XML_EXTERNAL_ENTITY_BODY``,
        containing the test type and the portion of the request template being
        tested
    :attribute list failures: A collection of "failures" raised by tests
    :attribute bool dead: Flip this if one of the requests doesn't return a
        response object
    :attribute client: HTTP client to be used by the test
    :attribute init_req: Initial request (loaded from request template)
    :attribute init_resp: Response to the initial request
    :attribute test_req: Request sent by the test for analysis
    :attribute test_resp: Response to the test request
    :attribute init_signals: Holder for signals on `init_req`
    :attribute test_signals: Holder for signals on `test_req`
    :attribute diff_signals: Holder for signals between `init_req` and
        `test_req`
    """

    test_name = None
    failures = []
    errors = []
    dead = False
    client = client()

    init_req = None
    init_resp = None
    test_req = None
    test_resp = None

    init_signals = SignalHolder()
    test_signals = SignalHolder()
    diff_signals = SignalHolder()

    @classmethod
    def register_opts(cls):
        pass

    @classmethod
    def get_test_cases(cls, filename, file_content, meta_vars):
        """Returns tests for given TestCase class (overwritten by children)."""
        yield cls

    @classmethod
    def create_init_request(cls, filename, file_content, meta_vars):
        """Parses template and creates init request object

        This method does not send the initial request, instead, it only creates
        the object for use in the debug test

        :param str filename: name of template file
        :param str file_content: content of template file as string
        """
        request_obj = parser.create_request(file_content,
                                            CONF.syntribos.endpoint, meta_vars)
        cls.init_req = request_obj
        cls.init_resp = None
        cls.init_signals = None
        cls.template_path = filename

    @classmethod
    def send_init_request(cls, filename, file_content, meta_vars):
        """Parses template, creates init request object, and sends init request

        This method sends the initial request, which is the request created
        after parsing the template file. This request will not be modified
        any further by the test cases themselves.

        :param str filename: name of template file
        :param str file_content: content of template file as string
        """
        if not cls.init_req:
            cls.init_req = parser.create_request(file_content,
                                                 CONF.syntribos.endpoint,
                                                 meta_vars)
        prepared_copy = cls.init_req.get_prepared_copy()
        cls.prepared_init_req = prepared_copy
        cls.init_resp, cls.init_signals = cls.client.send_request(
            prepared_copy)
        if cls.init_resp is not None:
            # Get the computed body and add it to our RequestObject
            # TODO(cneill): Figure out a better way to handle this discrepancy
            cls.init_req.body = cls.init_resp.request.body
        else:
            cls.dead = True

    @classmethod
    def extend_class(cls, new_name, kwargs):
        """Creates an extension for the class

        Each TestCase class created is added to the `test_table`, which is then
        read in by the test runner as the master list of tests to be run.

        :param str new_name: Name of new class to be created
        :param dict kwargs: Keyword arguments to pass to the new class
        :rtype: class
        :returns: A TestCase class extending :class:`BaseTestCase`
        """

        new_name = replace_invalid_characters(new_name)
        if not isinstance(kwargs, dict):
            raise Exception("kwargs must be a dictionary")
        new_cls = type(new_name, (cls, ), kwargs)
        new_cls.__module__ = cls.__module__
        return new_cls

    @classmethod
    def tearDownClass(cls):
        super(BaseTestCase, cls).tearDownClass()
        if not cls.failures:
            if "EXCEPTION_RAISED" in cls.test_signals:
                sig = cls.test_signals.find(tags="EXCEPTION_RAISED")[0]
                exc_name = type(sig.data["exception"]).__name__
                if ("CONNECTION_FAIL" in sig.tags):
                    six.raise_from(
                        FatalHTTPError(
                            "The remote target has forcibly closed the connection "
                            "with Syntribos and resulted in exception '{}'. This "
                            "could potentially mean that a fatal error was "
                            "encountered within the target application or server"
                            " itself.".format(exc_name)),
                        sig.data["exception"])
                else:
                    raise sig.data["exception"]

    @classmethod
    def tearDown(cls):
        get_slugs = [sig.slug for sig in cls.test_signals]
        get_checks = [sig.check_name for sig in cls.test_signals]
        test_signals_used = "Signals: " + str(get_slugs)
        LOG.debug(test_signals_used)
        test_checks_used = "Checks used: " + str(get_checks)
        LOG.debug(test_checks_used)

    def run_test_case(self):
        """This kicks off the test(s) for a given TestCase class

        After running the tests, an `AssertionError` is raised if any tests
        were added to self.failures.

        :raises: :exc:`AssertionError`
        """
        if not self.dead:
            try:
                self.test_case()
            except Exception as e:
                self.errors += e
                raise
            if self.failures:
                raise AssertionError

    def test_case(self):
        """This method is overwritten by individual TestCase classes

        It represents the actual test that is called in :func:`run_test_case`,
        and handles populating `self.failures`
        """
        pass

    def register_issue(self, defect_type, severity, confidence, description):
        """Adds an issue to the test's list of issues

        Creates a :class:`syntribos.issue.Issue` object, with given function
        parameters as instances variables, and registers the issue as a
        failure and associates the test's metadata to it.

        :param defect_type: The type of vulnerability that Syntribos believes
        it has found. This may be something like 500 error or DoS, regardless
        tof whathe Test Type is.
        :param severity: "Low", "Medium", or "High", depending on the defect
        :param description: Description of the defect
        :param confidence: The confidence of the defect
        :returns: new issue object with metadata associated
        :rtype: Issue
        """

        issue = syntribos.Issue(defect_type=defect_type,
                                severity=severity,
                                confidence=confidence,
                                description=description)

        issue.request = self.test_req if self.test_req else self.init_req
        issue.response = self.test_resp if self.test_resp else self.init_resp
        issue.template_path = self.template_path
        issue.parameter_location = self.parameter_location
        issue.test_type = self.test_name
        url_components = urlparse(self.init_resp.url)
        issue.target = url_components.netloc
        issue.path = url_components.path
        issue.init_signals = self.init_signals
        issue.test_signals = self.test_signals
        issue.diff_signals = self.diff_signals

        self.failures.append(issue)

        return issue
コード例 #5
0
ファイル: test_http_checks.py プロジェクト: knangia/syntribos
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# import requests
import requests.exceptions as rex
import requests_mock
import testtools

import syntribos.checks.http as http_checks
import syntribos.clients.http.client as client
import syntribos.signal

client = client()


class HTTPCheckUnittest(testtools.TestCase):

    def _get_one_signal(self, signals, slug=None, tags=None):
        to_search = None

        if isinstance(signals, syntribos.signal.SynSignal):
            to_search = signals

        elif isinstance(signals, syntribos.signal.SignalHolder):
            slugs = [slug] if slug else None
            matching = signals.find(slugs=slugs, tags=tags)
            self.assertEqual(1, len(matching))
            to_search = matching[0]
コード例 #6
0
ファイル: base_fuzz.py プロジェクト: varapreddy/syntribos
class BaseFuzzTestCase(base.BaseTestCase):
    config = syntribos.tests.fuzz.config.BaseFuzzConfig()
    client = client()
    failure_keys = None
    success_keys = None

    @classmethod
    def validate_length(cls):
        if getattr(cls, "init_response", False) is False:
            raise NotImplemented
        init_req_len = len(cls.init_response.request.body or "")
        init_resp_len = len(cls.init_response.content or "")
        req_len = len(cls.resp.request.body or "")
        resp_len = len(cls.resp.content or "")
        request_diff = req_len - init_req_len
        response_diff = resp_len - init_resp_len
        percent_diff = abs(float(response_diff) / (init_resp_len + 1)) * 100
        msg = ("Validate Length:\n"
               "\tInitial request length: {0}\n"
               "\tInitial response length: {1}\n"
               "\tRequest length: {2}\n"
               "\tResponse length: {3}\n"
               "\tRequest difference: {4}\n"
               "\tResponse difference: {5}\n"
               "\tPercent difference: {6}\n"
               "\tConfig percent: {7}\n").format(init_req_len, init_resp_len,
                                                 req_len, resp_len,
                                                 request_diff, response_diff,
                                                 percent_diff,
                                                 cls.config.percent)
        cls.fixture_log.debug(msg)
        if request_diff == response_diff:
            return True
        elif resp_len == init_resp_len:
            return True
        elif cls.config.percent:
            if percent_diff <= cls.config.percent:
                return True
        return False

    @classmethod
    def _get_strings(cls, file_name=None):
        path = os.path.join(data_dir, file_name or cls.data_key)
        with open(path, "rb") as fp:
            return fp.read().splitlines()

    @classmethod
    def data_driven_failure_cases(cls):
        failure_assertions = []
        if cls.failure_keys is None:
            return []
        for line in cls.failure_keys:
            failure_assertions.append([(cls.assertNotIn, line,
                                        cls.resp.content)])
        return failure_assertions

    @classmethod
    def data_driven_pass_cases(cls):
        if cls.success_keys is None:
            return True
        for s in cls.success_keys:
            if s in cls.resp.content:
                return True
        return False

    @classmethod
    def setUpClass(cls):
        """being used as a setup test not."""
        super(BaseFuzzTestCase, cls).setUpClass()
        cls.issues = []
        cls.failures = []
        cls.resp = cls.client.request(method=cls.request.method,
                                      url=cls.request.url,
                                      headers=cls.request.headers,
                                      params=cls.request.params,
                                      data=cls.request.data)

    @classmethod
    def tearDownClass(cls):
        super(BaseFuzzTestCase, cls).tearDownClass()
        for issue in cls.issues:
            if issue.failure:
                cls.failures.append(issue.as_dict())

    def test_case(self):
        self.register_issue(
            Issue(test="500_errors",
                  severity="Low",
                  text="This request generates a 500 error",
                  assertions=[(self.assertTrue, self.resp.status_code < 500)]))
        self.register_issue(
            Issue(test="length_diff",
                  severity="Low",
                  text=("The difference in length between the response to the"
                        "baseline request and the request returned when"
                        "sending an attack string exceeds {0} percent, which"
                        "could indicate a vulnerability to injection attacks"
                        ).format(self.config.percent),
                  assertions=[(self.assertTrue, self.validate_length())]))
        self.register_issue(
            Issue(test="injection_strings",
                  severity="Medium",
                  text=("A known attack string was included in the response."
                        "This could indicate a vulnerability to injection"
                        "attacks."),
                  assertions=self.data_driven_failure_cases()))
        self.register_issue(
            Issue(test="success_strings",
                  severity="Low",
                  text=("None of the expected strings in [{0}] can be found in"
                        "the response").format(self.success_keys),
                  assertions=[(self.assertTrue, self.data_driven_pass_cases())
                              ]))
        self.test_issues()

    def register_issue(self, issue=None):
        """Adds an issue to the test's list of issues

        Creates a new issue object, and associates the test's request
        and response to it. In addition, adds the issue to the test's
        list of issues
        """

        if not issue:
            issue = Issue()
        issue.request = self.resp.request
        issue.response = self.resp

        self.issues.append(issue)

        return issue

    def test_issues(self):
        for issue in self.issues:
            issue.run_tests()

    @classmethod
    def get_test_cases(cls, filename, file_content):
        # maybe move this block to base.py
        request_obj = syntribos.tests.fuzz.datagen.FuzzParser.create_request(
            file_content, os.environ.get("SYNTRIBOS_ENDPOINT"))
        prepared_copy = request_obj.get_prepared_copy()
        cls.init_response = cls.client.send_request(prepared_copy)
        # end block

        prefix_name = "{filename}_{test_name}_{fuzz_file}_".format(
            filename=filename, test_name=cls.test_name, fuzz_file=cls.data_key)
        for fuzz_name, request in request_obj.fuzz_request(
                cls._get_strings(), cls.test_type, prefix_name):
            yield cls.extend_class(fuzz_name, {"request": request})
コード例 #7
0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# import requests
import requests.exceptions as rex
import requests_mock
import testtools

import syntribos.checks.http as http_checks
import syntribos.clients.http.client as client
import syntribos.signal

client = client()


class HTTPCheckUnittest(testtools.TestCase):
    def _get_one_signal(self, signals, slug=None, tags=None):
        to_search = None

        if isinstance(signals, syntribos.signal.SynSignal):
            to_search = signals

        elif isinstance(signals, syntribos.signal.SignalHolder):
            slugs = [slug] if slug else None
            matching = signals.find(slugs=slugs, tags=tags)
            self.assertEqual(1, len(matching))
            to_search = matching[0]
コード例 #8
0
ファイル: base_fuzz.py プロジェクト: vinaypotluri/syntribos
class BaseFuzzTestCase(base.BaseTestCase):
    config = syntribos.tests.fuzz.config.BaseFuzzConfig()
    client = client()
    failure_keys = None
    success_keys = None

    @classmethod
    def validate_length(cls):
        """Validates length of response

        Compares the length of a fuzzed response with a response to the
        baseline request. If the response is longer than expected, returns
        false

        :returns: boolean - whether the response is longer than expected
        """
        if getattr(cls, "init_response", False) is False:
            raise NotImplemented
        init_req_len = len(cls.init_response.request.body or "")
        init_resp_len = len(cls.init_response.content or "")
        req_len = len(cls.resp.request.body or "")
        resp_len = len(cls.resp.content or "")
        request_diff = req_len - init_req_len
        response_diff = resp_len - init_resp_len
        percent_diff = abs(float(response_diff) / (init_resp_len + 1)) * 100
        msg = (
            "Validate Length:\n"
            "\tInitial request length: {0}\n"
            "\tInitial response length: {1}\n"
            "\tRequest length: {2}\n"
            "\tResponse length: {3}\n"
            "\tRequest difference: {4}\n"
            "\tResponse difference: {5}\n"
            "\tPercent difference: {6}\n"
            "\tConfig percent: {7}\n").format(
            init_req_len, init_resp_len, req_len, resp_len, request_diff,
            response_diff, percent_diff, cls.config.percent)
        cls.fixture_log.debug(msg)
        if request_diff == response_diff:
            return True
        elif resp_len == init_resp_len:
            return True
        elif cls.config.percent:
            if percent_diff <= cls.config.percent:
                return True
        return False

    @classmethod
    def _get_strings(cls, file_name=None):
        path = os.path.join(data_dir, file_name or cls.data_key)
        with open(path, "rb") as fp:
            return fp.read().splitlines()

    @classmethod
    def data_driven_failure_cases(cls):
        """Checks if response contains known bad strings

        :returns: a list of strings that show up in the response that are also
        defined in cls.failure_strings.
        failed_strings = []
        """
        failed_strings = []
        if cls.failure_keys is None:
            return []
        for line in cls.failure_keys:
            if line in cls.resp.content:
                failed_strings.append(line)
        return failed_strings

    @classmethod
    def data_driven_pass_cases(cls):
        """Checks if response contains expected strings

        :returns: a list of assertions that fail if the response doesn't
        contain a string defined in cls.success_keys as a string expected in
        the response.
        """
        if cls.success_keys is None:
            return True
        for s in cls.success_keys:
            if s in cls.resp.content:
                return True
        return False

    @classmethod
    def setUpClass(cls):
        """being used as a setup test not."""
        super(BaseFuzzTestCase, cls).setUpClass()
        cls.failures = []
        cls.resp = cls.client.request(
            method=cls.request.method, url=cls.request.url,
            headers=cls.request.headers, params=cls.request.params,
            data=cls.request.data)

    @classmethod
    def tearDownClass(cls):
        super(BaseFuzzTestCase, cls).tearDownClass()

    def test_default_issues(self):
        """Tests for some default issues

        These issues are not specific to any test type, and can be raised as a
        result of many different types of attacks. Therefore, they're defined
        separately from the test_case method so that they are not overwritten
        by test cases that inherit from BaseFuzzTestCase.

        Any extension to this class should call
        self.test_default_issues() in order to test for the Issues
        defined here
        """

        target = self.init_request.url
        domain = urlparse(target).hostname
        regex = r"\bhttp://{0}".format(domain)
        response_text = self.resp.text

        if re.search(regex, response_text):
            self.register_issue(
                Issue(test="SSL_ERROR",
                      severity="Medium",
                      confidence="High",
                      text=("Make sure that all the returned endpoint URIs"
                            " use 'https://' and not 'http://'"
                            )
                      )
            )

        if self.resp.status_code >= 500:
            self.register_issue(
                Issue(test="500_errors",
                      severity="Low",
                      confidence="High",
                      text=("This request returns an error with status code "
                            "{0}, which might indicate some server-side fault "
                            "that could lead to further vulnerabilities"
                            ).format(self.resp.status_code)
                      )
            )

        if (not self.validate_length() and
                self.resp.status_code == self.init_response.status_code):
            self.register_issue(
                Issue(test="length_diff",
                      severity="Low",
                      confidence="Low",
                      text=("The difference in length between the response to "
                            "the baseline request and the request returned "
                            "when sending an attack string exceeds {0} "
                            "percent, which could indicate a vulnerability "
                            "to injection attacks")
                      .format(self.config.percent)
                      )
            )

    def test_case(self):
        """Performs the test

        The test runner will call test_case on every TestCase class, and will
        report any AssertionError raised by this method to the results.
        """
        self.test_default_issues()

    @classmethod
    def get_test_cases(cls, filename, file_content):
        """Generates new TestCases for each fuzz string

        First, sends a baseline (non-fuzzed) request, storing it in
        cls.init_response.

        For each string returned by cls._get_strings(), yield a TestCase class
        for the string as an extension to the current TestCase class. Every
        string used as a fuzz test payload entails the generation of a new
        subclass for each parameter fuzzed. See :func:`base.extend_class`.
        """
        # maybe move this block to base.py
        request_obj = syntribos.tests.fuzz.datagen.FuzzParser.create_request(
            file_content, os.environ.get("SYNTRIBOS_ENDPOINT"))
        prepared_copy = request_obj.get_prepared_copy()
        cls.init_response = cls.client.send_request(prepared_copy)
        cls.init_request = cls.init_response.request
        # end block

        prefix_name = "{filename}_{test_name}_{fuzz_file}_".format(
            filename=filename, test_name=cls.test_name, fuzz_file=cls.data_key)
        fr = request_obj.fuzz_request(
            cls._get_strings(), cls.test_type, prefix_name)
        for fuzz_name, request, fuzz_string, param_path in fr:
            yield cls.extend_class(fuzz_name, fuzz_string, param_path,
                                   {"request": request})

    @classmethod
    def extend_class(cls, new_name, fuzz_string, param_path, kwargs):
        """Creates an extension for the class

        Each TestCase class created is added to the `test_table`, which is then
        read in by the test runner as the master list of tests to be run.

        :param str new_name: Name of new class to be created
        :param str fuzz_string: Fuzz string to insert
        :param str param_path: String tracing location of the ImpactedParameter
        :param dict kwargs: Keyword arguments to pass to the new class
        :rtype: class
        :returns: A TestCase class extending :class:`BaseTestCase`
        """

        new_cls = super(BaseFuzzTestCase, cls).extend_class(new_name, kwargs)
        new_cls.fuzz_string = fuzz_string
        new_cls.param_path = param_path
        return new_cls

    def register_issue(self, issue):
        """Adds an issue to the test's list of issues

        Registers a :class:`syntribos.issue.Issue` object as a failure and
        associates the test's metadata to it, including the
        :class:`syntribos.tests.fuzz.base_fuzz.ImpactedParameter` object that
        encapsulates the details of the fuzz test.

        :param Issue issue: issue object to update
        :returns: new issue object with metadata associated
        :rtype: Issue
        """

        # Still associating request and response objects with issue in event of
        # debug log
        req = self.resp.request
        issue.request = req
        issue.response = self.resp

        issue.test_type = self.test_name
        url_components = urlparse(self.init_response.url)
        issue.target = url_components.netloc
        issue.path = url_components.path
        if 'content-type' in self.init_request.headers:
            issue.content_type = self.init_request.headers['content-type']
        else:
            issue.content_type = None

        issue.impacted_parameter = ImpactedParameter(method=req.method,
                                                     location=self.test_type,
                                                     name=self.param_path,
                                                     value=self.fuzz_string)

        self.failures.append(issue)

        return issue