Пример #1
0
class directory_indexing(GrepPlugin):
    """
    Grep every response for directory indexing problems.

    :author: Andres Riancho ([email protected])
    """

    DIR_INDEXING = (
        "<title>Index of /",
        '<a href="?C=N;O=D">Name</a>',
        '<A HREF="?M=A">Last modified</A>',
        "Last modified</a>",
        "Parent Directory</a>",
        "Directory Listing for",
        "<TITLE>Folder Listing.",
        '<table summary="Directory Listing" ',
        "- Browsing directory ",
        # IIS 6.0 and 7.0
        '">[To Parent Directory]</a><br><br>',
        # IIS 5.0
        '<A HREF=".*?">.*?</A><br></pre><hr></body></html>')
    _multi_in = multi_in(DIR_INDEXING)

    def __init__(self):
        GrepPlugin.__init__(self)

        self._already_visited = ScalableBloomFilter()

    def grep(self, request, response):
        """
        Plugin entry point, search for directory indexing.
        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None
        """
        if not response.is_text_or_html():
            return

        if response.get_url().get_domain_path() in self._already_visited:
            return

        self._already_visited.add(response.get_url().get_domain_path())

        html_string = response.get_body()
        if self._multi_in.query(html_string):

            desc = 'The URL: "%s" has a directory indexing vulnerability.'
            desc = desc % response.get_url()

            v = Vuln('Directory indexing', desc, severity.LOW, response.id,
                     self.get_name())
            v.set_url(response.get_url())

            self.kb_append_uniq(self, 'directory', v, 'URL')

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Пример #2
0
    def _analyze_persistent(self, freq, response):
        """
        Analyze the response of sending each fuzzable request found by the
        framework, trying to identify any locations where we might have injected
        a payload.

        :param freq: The fuzzable request
        :param response: The HTTP response
        :return: None, vulns are stored in KB
        """
        multi_in_inst = multi_in(self._expected_mutant_dict.keys())

        for matched_expected_result in multi_in_inst.query(
                response.get_body()):
            # We found one of the expected results, now we search the
            # self._expected_mutant_dict to find which of the mutants sent it
            # and create the vulnerability
            mutant = self._expected_mutant_dict[matched_expected_result]

            desc = ('Server side include (SSI) was found at: %s'
                    ' The result of that injection is shown by browsing'
                    ' to "%s".')
            desc %= (mutant.found_at(), freq.get_url())

            v = Vuln.from_mutant(
                'Persistent server side include vulnerability', desc,
                severity.HIGH, response.id, self.get_name(), mutant)

            v.add_to_highlight(matched_expected_result)
            self.kb_append(self, 'ssi', v)
Пример #3
0
    def end(self):
        """
        This method is called when the plugin wont be used anymore and is used
        to find persistent SSI vulnerabilities.

        Example where a persistent SSI can be found:

        Say you have a "guestbook" (a CGI application that allows visitors
        to leave messages for everyone to see) on a server that has SSI
        enabled. Most such guestbooks around the Net actually allow visitors
        to enter HTML code as part of their comments. Now, what happens if a
        malicious visitor decides to do some damage by entering the following:

        <!--#exec cmd="ls" -->

        If the guestbook CGI program was designed carefully, to strip SSI
        commands from the input, then there is no problem. But, if it was not,
        there exists the potential for a major headache!

        For a working example please see moth VM.
        """
        multi_in_inst = multi_in(self._expected_res_mutant.keys())

        def filtered_freq_generator(freq_list):
            already_tested = ScalableBloomFilter()

            for freq in freq_list:
                if freq not in already_tested:
                    already_tested.add(freq)
                    yield freq

        def analyze_persistent(freq, response):

            for matched_expected_result in multi_in_inst.query(
                    response.get_body()):
                # We found one of the expected results, now we search the
                # self._persistent_data to find which of the mutants sent it
                # and create the vulnerability
                mutant = self._expected_res_mutant[matched_expected_result]

                desc = 'Server side include (SSI) was found at: %s' \
                       ' The result of that injection is shown by browsing'\
                       ' to "%s".'
                desc = desc % (mutant.found_at(), freq.get_url())

                v = Vuln.from_mutant(
                    'Persistent server side include vulnerability', desc,
                    severity.HIGH, response.id, self.get_name(), mutant)

                v.add_to_highlight(matched_expected_result)
                self.kb_append(self, 'ssi', v)

        self._send_mutants_in_threads(self._uri_opener.send_mutant,
                                      filtered_freq_generator(self._freq_list),
                                      analyze_persistent,
                                      cache=False)

        self._expected_res_mutant.cleanup()
        self._freq_list.cleanup()
Пример #4
0
    def end(self):
        """
        This method is called when the plugin wont be used anymore and is used
        to find persistent SSI vulnerabilities.

        Example where a persistent SSI can be found:

        Say you have a "guestbook" (a CGI application that allows visitors
        to leave messages for everyone to see) on a server that has SSI
        enabled. Most such guestbooks around the Net actually allow visitors
        to enter HTML code as part of their comments. Now, what happens if a
        malicious visitor decides to do some damage by entering the following:

        <!--#exec cmd="ls" -->

        If the guestbook CGI program was designed carefully, to strip SSI
        commands from the input, then there is no problem. But, if it was not,
        there exists the potential for a major headache!

        For a working example please see moth VM.
        """
        multi_in_inst = multi_in(self._expected_res_mutant.keys())

        def filtered_freq_generator(freq_list):
            already_tested = ScalableBloomFilter()

            for freq in freq_list:
                if freq not in already_tested:
                    already_tested.add(freq)
                    yield freq

        def analyze_persistent(freq, response):

            for matched_expected_result in multi_in_inst.query(response.get_body()):
                # We found one of the expected results, now we search the
                # self._persistent_data to find which of the mutants sent it
                # and create the vulnerability
                mutant = self._expected_res_mutant[matched_expected_result]
                
                desc = 'Server side include (SSI) was found at: %s' \
                       ' The result of that injection is shown by browsing'\
                       ' to "%s".' 
                desc = desc % (mutant.found_at(), freq.get_url())
                
                v = Vuln.from_mutant('Persistent server side include vulnerability',
                                     desc, severity.HIGH, response.id,
                                     self.get_name(), mutant)
                
                v.add_to_highlight(matched_expected_result)
                self.kb_append(self, 'ssi', v)

        self._send_mutants_in_threads(self._uri_opener.send_mutant,
                                      filtered_freq_generator(self._freq_list),
                                      analyze_persistent,
                                      cache=False)
        
        self._expected_res_mutant.cleanup()
        self._freq_list.cleanup()
Пример #5
0
    def _analyze_persistent(self, freq, response):
        """
        Analyze the response of sending each fuzzable request found by the
        framework, trying to identify any locations where we might have injected
        a payload.

        :param freq: The fuzzable request
        :param response: The HTTP response
        :return: None, vulns are stored in KB
        """
        multi_in_inst = multi_in(self._expected_mutant_dict.keys())

        for matched_expected_result in multi_in_inst.query(response.get_body()):
            # We found one of the expected results, now we search the
            # self._expected_mutant_dict to find which of the mutants sent it
            # and create the vulnerability
            mutant = self._expected_mutant_dict[matched_expected_result]

            desc = (
                "Server side include (SSI) was found at: %s"
                " The result of that injection is shown by browsing"
                ' to "%s".'
            )
            desc %= (mutant.found_at(), freq.get_url())

            v = Vuln.from_mutant(
                "Persistent server side include vulnerability",
                desc,
                severity.HIGH,
                response.id,
                self.get_name(),
                mutant,
            )

            v.add_to_highlight(matched_expected_result)
            self.kb_append(self, "ssi", v)
Пример #6
0
class preg_replace(AuditPlugin):
    """
    Find unsafe usage of PHPs preg_replace.
    :author: Andres Riancho ([email protected])
    """
    PREG_PAYLOAD = ['a' + ')/' * 100, ]
    PREG_ERRORS = ('Compilation failed: unmatched parentheses at offset',
                   '<b>Warning</b>:  preg_replace() [<a',
                   'Warning: preg_replace(): ')

    _multi_in = multi_in(PREG_ERRORS)

    def __init__(self):
        AuditPlugin.__init__(self)

    def audit(self, freq, orig_response):
        """
        Tests an URL for unsafe usage of PHP's preg_replace.

        :param freq: A FuzzableRequest
        """
        # First I check If I get the error message from php
        mutants = create_mutants(freq, self.PREG_PAYLOAD,
                                 orig_resp=orig_response)

        self._send_mutants_in_threads(self._uri_opener.send_mutant,
                                      mutants,
                                      self._analyze_result)

    def _analyze_result(self, mutant, response):
        """
        Analyze results of the _send_mutant method.
        """
        #
        #   I will only report the vulnerability once.
        #
        if self._has_no_bug(mutant):

            for preg_error_string in self._find_preg_error(response):
                if preg_error_string not in mutant.get_original_response_body():
                    desc = 'Unsafe usage of preg_replace was found at: %s'
                    desc = desc % mutant.found_at()

                    v = Vuln.from_mutant('Unsafe preg_replace usage', desc,
                                         severity.HIGH, response.id,
                                         self.get_name(), mutant)

                    v.add_to_highlight(preg_error_string)
                    self.kb_append_uniq(self, 'preg_replace', v)
                    break

    def _find_preg_error(self, response):
        """
        This method searches for preg_replace errors in html's.

        :param response: The HTTP response object
        :return: A list of errors found on the page
        """
        res = []
        for error_match in self._multi_in.query(response.body):
            msg = 'An unsafe usage of preg_replace() function was found,' \
                  ' the error that was sent by the web application is (only' \
                  ' a fragment is shown): "%s", and was found in the' \
                  ' response with id %s.'

            om.out.information(msg % (error_match, response.id))
            res.append(error_match)
        return res

    def get_plugin_deps(self):
        """
        :return: A list with the names of the plugins that should be run before
                 the current one.
        """
        return ['grep.error_500']

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Пример #7
0
class wsdl_greper(GrepPlugin):
    """
    Grep every page for web service definition files.

    :author: Andres Riancho ([email protected])
    """
    WSDL_STRINGS = ('xs:int', 'target_namespace', 'soap:body',
                    '/s:sequence', 'wsdl:', 'soapAction=',
                    # This isn't WSDL... but well...
                    'xmlns="urn:uddi"', '<p>Hi there, this is an AXIS service!</p>')
    _multi_in = multi_in(WSDL_STRINGS)

    def __init__(self):
        GrepPlugin.__init__(self)

        self._disco_strings = ['disco:discovery ']

    def grep(self, request, response):
        """
        Plugin entry point.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None, all results are saved in the kb.
        """
        if response.get_code() == 200:
            self.analyze_wsdl(request, response)
            self.analyze_disco(request, response)

    def analyze_wsdl(self, request, response):
        match_list = self._multi_in.query(response.body)
        if len(match_list):
            desc = 'The URL: "%s" is a Web Services Description Language' \
                   ' page. This requires manual analysis to determine the' \
                   ' security of the web service.'
            desc = desc % response.get_url()

            i = Info('WSDL resource', desc, response.id,
                     self.get_name())
            i.set_url(response.get_url())
            i.add_to_highlight(*match_list)

            self.kb_append_uniq(self, 'wsdl', i, 'URL')

    def analyze_disco(self, request, response):
        for disco_string in self._disco_strings:
            if disco_string in response:
                desc = 'The URL: "%s" is a DISCO file that contains references' \
                       ' to WSDL URLs.'
                desc = desc % response.get_url()
                i = Info('DISCO resource', desc, response.id,
                         self.get_name())
                i.set_url(response.get_url())
                i.add_to_highlight(disco_string)

                self.kb_append_uniq(self, 'disco', i, 'URL')
                break

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Пример #8
0
class error_pages(GrepPlugin):
    """
    Grep every page for error pages.

    :author: Andres Riancho ([email protected])
    """

    ERROR_PAGES = (
        '<H1>Error page exception</H1>',

        # This signature fires up also in default 404 pages of aspx which
        # generates a lot of noise, so ... disabling it
        # '<span><H1>Server Error in ',
        '<h2> <i>Runtime Error</i> </h2></span>',
        '<h2> <i>Access is denied</i> </h2></span>',
        '<H3>Original Exception: </H3>',
        'Server object error',
        'invalid literal for int()',
        'exceptions.ValueError',
        '<font face="Arial" size=2>Type mismatch: ',
        '[an error occurred while processing this directive]',
        '<HTML><HEAD><TITLE>Error Occurred While Processing Request</TITLE>'
        '</HEAD><BODY><HR><H3>Error Occurred While Processing Request</H3><P>',

        # VBScript
        '<p>Microsoft VBScript runtime </font>',
        "<font face=\"Arial\" size=2>error '800a000d'</font>",

        # nwwcgi errors
        '<TITLE>nwwcgi Error',

        # ASP error I found during a pentest, the ASP used a foxpro db, not a
        # SQL injection
        '<font face="Arial" size=2>error \'800a0005\'</font>',
        '<h2> <i>Runtime Error</i> </h2></span>',
        # Some error in ASP when using COM objects.
        'Operation is not allowed when the object is closed.',
        # An error when ASP tries to include something and it fails
        '<p>Active Server Pages</font> <font face="Arial" size=2>error \'ASP 0126\'</font>',

        # ASPX
        '<b> Description: </b>An unhandled exception occurred during the execution of the'
        ' current web request',

        # Struts
        '] does not contain handler parameter named',

        # PHP
        '<b>Warning</b>: ',
        'No row with the given identifier',
        'open_basedir restriction in effect',
        "eval()'d code</b> on line <b>",
        "Cannot execute a blank command in",
        "Fatal error</b>:  preg_replace",
        "thrown in <b>",
        "#0 {main}",
        "Stack trace:",
        "</b> on line <b>",

        # python
        "PythonHandler django.core.handlers.modpython",
        "t = loader.get_template(template_name) # You need to create a 404.html template.",
        '<h2>Traceback <span>(innermost last)</span></h2>',

        # Java
        '[java.lang.',
        'class java.lang.',
        'java.lang.NullPointerException',
        'java.rmi.ServerException',
        'at java.lang.',
        'onclick="toggle(\'full exception chain stacktrace\')"',
        'at org.apache.catalina',
        'at org.apache.coyote.',
        'at org.apache.tomcat.',
        'at org.apache.jasper.',

        # https://github.com/andresriancho/w3af/issues/4001
        '<html><head><title>Application Exception</title>',

        # ruby
        '<h1 class="error_title">Ruby on Rails application could not be started</h1>',

        # Coldfusion
        '<title>Error Occurred While Processing Request</title></head><body><p></p>',
        '<HTML><HEAD><TITLE>Error Occurred While Processing Request</TITLE></HEAD><BODY><HR><H3>',
        '<TR><TD><H4>Error Diagnostic Information</H4><P><P>',
        '<li>Search the <a href="http://www.macromedia.com/support/coldfusion/" '
        'target="new">Knowledge Base</a> to find a solution to your problem.</li>',

        # http://www.programacion.net/asp/articulo/kbr_execute/
        'Server.Execute Error',

        # IIS
        '<h2 style="font:8pt/11pt verdana; color:000000">HTTP 403.6 - Forbidden: IP address rejected<br>',
        '<TITLE>500 Internal Server Error</TITLE>',
    )
    _multi_in = multi_in(ERROR_PAGES)

    VERSION_REGEX = (
        ('<address>(.*?)</address>', 'Apache'),
        ('<HR size="1" noshade="noshade"><h3>(.*?)</h3></body>',
         'Apache Tomcat'),
        ('<a href="http://www.microsoft.com/ContentRedirect.asp\?prd=iis&sbp=&pver=(.*?)&pid=&ID',
         'IIS'),

        # <b>Version Information:</b>&nbsp;Microsoft .NET Framework Version:1.1.4322.2300; ASP.NET Version:1.1.4322.2300
        ('<b>Version Information:</b>&nbsp;(.*?)\n', 'ASP .NET'))
    _multi_re = multi_re(VERSION_REGEX)

    MAX_REPORTED_PER_MSG = 10

    def __init__(self):
        GrepPlugin.__init__(self)

        #   Internal variables
        self._potential_vulns = DiskList(table_prefix='error_pages')

        self._already_reported_max_msg_exceeded = []
        self._already_reported_versions = []
        self._compiled_regex = []

    def grep(self, request, response):
        """
        Plugin entry point, find the error pages and report them.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None
        """
        if not response.is_text_or_html():
            return

        self.find_error_page(request, response)
        self.find_version_numbers(request, response)

    def find_error_page(self, request, response):
        # There is no need to report more than one info for the
        # same result, the user will read the info object and
        # analyze it even if we report it only once. If we report
        # it twice, he'll get mad ;)
        for _, _, _, url, _ in self._potential_vulns:
            if url == response.get_url():
                return

        for msg in self._multi_in.query(response.body):
            if self._avoid_report(request, response, msg):
                continue

            # We found a new error in a response!
            desc = 'The URL: "%s" contains the descriptive error: "%s".'
            desc %= (response.get_url(), msg)

            title = 'Descriptive error page'

            data = (title, desc, response.id, response.get_url(), msg)
            self._potential_vulns.append(data)

            # Just report one instance for each HTTP response, no
            # matter if multiple strings match
            break

    def _avoid_report(self, request, response, msg):
        # We should avoid multiple reports for the same error message
        # the idea here is that the root cause for the same error
        # message might be the same, and fixing one will fix all.
        #
        # So the user receives the first report with MAX_REPORTED_PER_MSG
        # vulnerabilities, fixes the root cause, scans again and then
        # all those instances go away.
        #
        # Without this code, the scanner will potentially report
        # thousands of issues for the same error message. Which will
        # overwhelm the user.
        count = 0

        for title, desc, _id, url, highlight in self._potential_vulns:
            if highlight == msg:
                count += 1

        if count < self.MAX_REPORTED_PER_MSG:
            return False

        if msg not in self._already_reported_max_msg_exceeded:
            self._already_reported_max_msg_exceeded.append(msg)

            desc = ('The application returned multiple HTTP responses'
                    ' containing detailed error pages containing exceptions'
                    ' and internal information. The maximum number of'
                    ' vulnerabilities for this issue type was reached'
                    ' and no more issues will be reported.')

            i = Info('Multiple descriptive error pages', desc, [],
                     self.get_name())
            self.kb_append_uniq(self, 'error_page', i)

        return True

    def end(self):
        """
        This method is called when the plugin wont be used anymore.
        """
        all_findings = kb.kb.get_all_findings()

        for title, desc, _id, url, highlight in self._potential_vulns:
            for info in all_findings:
                # This makes sure that if the sqli plugin found a vulnerability
                # in the same URL as we found a detailed error, we won't report
                # the detailed error.
                #
                # If the user fixes the sqli vulnerability and runs the scan again
                # most likely the detailed error will disappear too. If the sqli
                # vulnerability disappears and this one remains, it will appear
                # as a new vulnerability in the second scan.
                if info.get_url() == url:
                    break
            else:
                i = Info(title, desc, _id, self.get_name())
                i.set_url(url)
                i.add_to_highlight(highlight)

                self.kb_append_uniq(self, 'error_page', i)

        self._potential_vulns.cleanup()

    def find_version_numbers(self, request, response):
        """
        Now i'll check if I can get a version number from the error page
        This is common in apache, tomcat, etc...
        """
        if 400 < response.get_code() < 600:

            for match, _, _, server in self._multi_re.query(response.body):
                match_string = match.group(0)
                if match_string not in self._already_reported_versions:
                    # Save the info obj
                    desc = 'An error page sent this %s version: "%s".'
                    desc %= (server, match_string)

                    i = Info('Error page with information disclosure', desc,
                             response.id, self.get_name())
                    i.set_url(response.get_url())
                    i.add_to_highlight(server)
                    i.add_to_highlight(match_string)

                    kb.kb.append(self, 'server', i)
                    kb.kb.raw_write(self, 'server', match_string)

                    self._already_reported_versions.append(match_string)

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Пример #9
0
class buffer_overflow(AuditPlugin):
    """
    Find buffer overflow vulnerabilities.
    :author: Andres Riancho ([email protected])
    """

    OVERFLOW_ERRORS = (
        '*** stack smashing detected ***:',
        'Backtrace:',
        'Memory map:',

        # Note that the lack of commas after the strings is intentional
        '<html><head>\n<title>500 Internal Server Error</title>\n'
        '</head><body>\n<h1>'
        'Internal Server Error</h1>')

    _multi_in = multi_in(OVERFLOW_ERRORS)

    # TODO: if lengths = [ 65 , 257 , 513 , 1025, 2049, 4097, 8000 ]
    # then i get a BadStatusLine exception from urllib2, is seems to be an
    # internal error. Tested against tomcat 5.5.7
    BUFFER_TESTS = [rand_alpha(l) for l in [65, 257, 513, 1025, 2049]]

    def __init__(self):
        """
        Some notes:
            On Apache, when an overflow happends on a cgic script, this is written
            to the log:
                *** stack smashing detected ***: /var/www/.../buffer_overflow.cgi terminated,
                referer: http://localhost/w3af/bufferOverflow/buffer_overflow.cgi
                Premature end of script headers: buffer_overflow.cgi, referer: ...

            On Apache, when an overflow happends on a cgic script, this is
            returned to the user:
                <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
                <html><head>
                <title>500 Internal Server Error</title>
                </head><body>
                <h1>Internal Server Error</h1>
                <p>The server encountered an internal error or
                misconfiguration and was unable to complete
                your request.</p>
                <p>Please contact the server administrator,
                 webmaster@localhost and inform them of the time the error occurred,
                and anything you might have done that may have
                caused the error.</p>
                <p>More information about this error may be available
                in the server error log.</p>
                <hr>
                <address>Apache/2.0.55 (Ubuntu) mod_python/3.2.8 Python/2.4.4c1
                PHP/5.1.6 Server at localhost Port 80</address>
                </body></html>

            Note that this is an Apache error 500, not the more common PHP error 500.
        """
        AuditPlugin.__init__(self)

    def audit(self, freq, orig_response):
        """
        Tests an URL for buffer overflow vulnerabilities.

        :param freq: A FuzzableRequest
        """
        mutants = create_mutants(freq,
                                 self.BUFFER_TESTS,
                                 orig_resp=orig_response)
        args = zip(repeat(self._send_request), mutants)

        for result in self.worker_pool.imap_unordered(apply_with_return_error,
                                                      args):
            # re-raise the thread exception in the main thread with this method
            # so we get a nice traceback instead of things like the ones we see
            # in https://github.com/andresriancho/w3af/issues/7287
            if isinstance(result, Error):
                result.reraise()

    def _send_request(self, mutant):
        """
        Sends a mutant to the remote web server. I wrap urllib's _send_mutant
        just to handle errors in a different way.
        """
        try:
            response = self._uri_opener.send_mutant(mutant)
        except (BaseFrameworkException, ScanMustStopException):
            desc = 'A potential (most probably a false positive than a bug)' \
                   ' buffer-overflow was found when requesting: "%s", using' \
                   ' HTTP method %s. The data sent was: "%s".'
            desc = desc % (mutant.get_url(), mutant.get_method(),
                           mutant.get_dc())

            i = Info.from_mutant('Potential buffer overflow vulnerability',
                                 desc, [], self.get_name(), mutant)

            self.kb_append_uniq(self, 'buffer_overflow', i)
        else:
            self._analyze_result(mutant, response)

    def _analyze_result(self, mutant, response):
        """
        Analyze results of the _send_mutant method.
        """
        for error_str in self._multi_in.query(response.body):
            # And not in the original response
            if error_str not in mutant.get_original_response_body() and \
                    self._has_no_bug(mutant):
                desc = 'A potential buffer overflow (accurate detection is' \
                       ' hard...) was found at: %s' % mutant.found_at()

                v = Vuln.from_mutant('Buffer overflow vulnerability', desc,
                                     severity.MEDIUM, response.id,
                                     self.get_name(), mutant)
                v.add_to_highlight(error_str)

                self.kb_append_uniq(self, 'buffer_overflow', v)

    def get_plugin_deps(self):
        """
        :return: A list with the names of the plugins that should be run before
                 the current one.
        """
        return ['grep.error_500']

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Пример #10
0
Файл: lfi.py Проект: zcr214/w3af
class lfi(AuditPlugin):
    """
    Find local file inclusion vulnerabilities.
    :author: Andres Riancho ([email protected])
    """

    file_pattern_multi_in = multi_in(FILE_PATTERNS)
    file_read_error_multi_re = multi_re(FILE_OPEN_ERRORS)

    def audit(self, freq, orig_response):
        """
        Tests an URL for local file inclusion vulnerabilities.

        :param freq: A FuzzableRequest
        """
        mutants = create_mutants(freq,
                                 self.get_lfi_tests(freq),
                                 orig_resp=orig_response)

        self._send_mutants_in_threads(self._uri_opener.send_mutant,
                                      mutants,
                                      self._analyze_result,
                                      grep=False)

    def get_lfi_tests(self, freq):
        """
        :param freq: The fuzzable request we're analyzing
        :return: The paths to test
        """
        #
        #   Add some tests which try to read "self"
        #   http://host.tld/show_user.php?id=show_user.php
        #
        lfi_tests = [
            freq.get_url().get_file_name(),
            '/%s' % freq.get_url().get_file_name()
        ]

        #
        #   Add some tests which try to read common/known files
        #
        lfi_tests.extend(self._get_common_file_list(freq.get_url()))

        return lfi_tests

    def _get_common_file_list(self, orig_url):
        """
        This method returns a list of local files to try to include.

        :return: A string list, see above.
        """
        local_files = []

        extension = orig_url.get_extension()

        # I will only try to open these files, they are easy to identify of they
        # echoed by a vulnerable web app and they are on all unix or windows
        # default installs. Feel free to mail me (Andres Riancho) if you know
        # about other default files that could be installed on AIX ? Solaris ?
        # and are not /etc/passwd
        if cf.cf.get('target_os') in {'unix', 'unknown'}:
            local_files.append('/../' * 15 + 'etc/passwd')
            local_files.append('../' * 15 + 'etc/passwd')

            local_files.append('/../' * 15 + 'etc/passwd\0')
            local_files.append('/../' * 15 + 'etc/passwd\0.html')
            local_files.append('/etc/passwd')

            # This test adds support for finding vulnerabilities like this one
            # http://website/zen-cart/extras/curltest.php?url=file:///etc/passwd
            local_files.append('file:///etc/passwd')

            local_files.append('/etc/passwd\0')
            local_files.append('/etc/passwd\0.html')

            if extension != '':
                local_files.append('/etc/passwd%00.' + extension)
                local_files.append('/../' * 15 + 'etc/passwd%00.' + extension)

        if cf.cf.get('target_os') in {'windows', 'unknown'}:
            local_files.append('/../' * 15 + 'boot.ini')
            local_files.append('../' * 15 + 'boot.ini')

            local_files.append('/../' * 15 + 'boot.ini\0')
            local_files.append('/../' * 15 + 'boot.ini\0.html')

            local_files.append('C:\\boot.ini')
            local_files.append('C:\\boot.ini\0')
            local_files.append('C:\\boot.ini\0.html')

            local_files.append('%SYSTEMROOT%\\win.ini')
            local_files.append('%SYSTEMROOT%\\win.ini\0')
            local_files.append('%SYSTEMROOT%\\win.ini\0.html')

            # file:// URIs for windows , docs here: http://goo.gl/A9Mvux
            local_files.append('file:///C:/boot.ini')
            local_files.append('file:///C:/win.ini')

            if extension != '':
                local_files.append('C:\\boot.ini%00.' + extension)
                local_files.append('%SYSTEMROOT%\\win.ini%00.' + extension)

        return local_files

    def _analyze_result(self, mutant, response):
        """
        Analyze results of the _send_mutant method.
        Try to find the local file inclusions.
        """
        #
        #   I will only report the vulnerability once.
        #
        if self._has_bug(mutant):
            return

        #
        #   Identify the vulnerability
        #
        for file_pattern_match in self._find_common_file_fragments(response):
            if file_pattern_match not in mutant.get_original_response_body():

                desc = 'Local File Inclusion was found at: %s'
                desc %= mutant.found_at()

                v = Vuln.from_mutant('Local file inclusion vulnerability',
                                     desc, severity.MEDIUM, response.id,
                                     self.get_name(), mutant)

                v['file_pattern'] = file_pattern_match

                v.add_to_highlight(file_pattern_match)
                self.kb_append_uniq(self, 'lfi', v)
                return

        #
        # If the vulnerability could not be identified by matching strings that
        # commonly appear in "/etc/passwd", then I'll check one more thing...
        # (note that this is run if no vulns were identified)
        #
        # http://host.tld/show_user.php?id=show_user.php
        if mutant.get_url().get_file_name() in mutant.get_token_value():
            match, lang = contains_source_code(response)
            if match:
                # We were able to read the source code of the file that is
                # vulnerable to local file read
                desc = ('An arbitrary local file read vulnerability was'
                        ' found at: %s')
                desc %= mutant.found_at()

                v = Vuln.from_mutant('Local file inclusion vulnerability',
                                     desc, severity.MEDIUM, response.id,
                                     self.get_name(), mutant)

                #
                #    Set which part of the source code to match
                #
                match_source_code = match.group(0)
                v['file_pattern'] = match_source_code

                self.kb_append_uniq(self, 'lfi', v)
                return

        #
        #   Check for interesting errors (note that this is run if no vulns were
        #   identified)
        #
        body = response.get_body()
        for _, error_str, _ in self.file_read_error_multi_re.query(body):
            if error_str not in mutant.get_original_response_body():
                desc = 'A file read error was found at: %s'
                desc %= mutant.found_at()

                i = Info.from_mutant('File read error', desc, response.id,
                                     self.get_name(), mutant)
                i.add_to_highlight(error_str)

                self.kb_append_uniq(self, 'error', i)

    def _find_common_file_fragments(self, response):
        """
        This method finds out if the local file has been successfully included
        in the resulting HTML.

        :param response: The HTTP response object
        :return: A list of errors found on the page
        """
        res = set()
        matches = self.file_pattern_multi_in.query(response.get_body())

        for file_pattern_match in matches:
            res.add(file_pattern_match)

        if len(res) == 1:
            msg = (
                'A file fragment was found. The section where the file is'
                ' included is (only a fragment is shown): "%s". This is'
                ' just an informational message, which might be related'
                '  to a vulnerability and was found on response with id %s.')
            om.out.debug(msg % (list(res)[0], response.id))

        if len(res) > 1:
            msg = ('File fragments have been found. The following is a list'
                   ' of file fragments that were returned by the web'
                   ' application while testing for local file inclusion: \n')

            for file_pattern_match in res:
                msg += '- "%s" \n' % file_pattern_match

            msg += ('This is just an informational message, which might be'
                    ' related to a vulnerability and was found in response'
                    ' with id %s.' % response.id)

            om.out.debug(msg)

        return res

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Пример #11
0
class xpath(AuditPlugin):
    """
    Find XPATH injection vulnerabilities.
    :author: Andres Riancho ([email protected])
    """

    XPATH_PATTERNS = (
        'System.Xml.XPath.XPathException:',
        'MS.Internal.Xml.',
        'Unknown error in XPath',
        'org.apache.xpath.XPath',
        'A closing bracket expected in',
        'An operand in Union Expression does not produce a node-set',
        'Cannot convert expression to a number',
        'Document Axis does not allow any context Location Steps',
        'Empty Path Expression',
        'DOMXPath::'
        'Empty Relative Location Path',
        'Empty Union Expression',
        "Expected ')' in",
        'Expected node test or name specification after axis operator',
        'Incompatible XPath key',
        'Incorrect Variable Binding',
        'libxml2 library function failed',
        'libxml2',
        'Invalid predicate',
        'Invalid expression',
        'xmlsec library function',
        'xmlsec',
        "error '80004005'",
        "A document must contain exactly one root element.",
        '<font face="Arial" size=2>Expression must evaluate to a node-set.',
        "Expected token ']'",
        "<p>msxml4.dll</font>",
        "<p>msxml3.dll</font>",

        # Put this here cause i did not know if it was a sql injection
        # This error appears when you put wierd chars in a lotus notes document
        # search ( nsf files ).
        '4005 Notes error: Query is not understandable',
    )
    _multi_in = multi_in(XPATH_PATTERNS)

    XPATH_TEST_PAYLOADS = [
        "d'z\"0",
        # http://www.owasp.org/index.php/Testing_for_XML_Injection
        "<!--"
    ]

    def audit(self, freq, orig_response):
        """
        Tests an URL for xpath injection vulnerabilities.

        :param freq: A FuzzableRequest
        """
        mutants = create_mutants(freq,
                                 self.XPATH_TEST_PAYLOADS,
                                 orig_resp=orig_response)

        self._send_mutants_in_threads(self._uri_opener.send_mutant, mutants,
                                      self._analyze_result)

    def _analyze_result(self, mutant, response):
        """
        Analyze results of the _send_mutant method.
        """
        #
        #   I will only report the vulnerability once.
        #
        if self._has_bug(mutant):
            return

        xpath_error_list = self._find_xpath_error(response)
        for xpath_error in xpath_error_list:
            if xpath_error not in mutant.get_original_response_body():
                desc = 'XPATH injection was found at: %s' % mutant.found_at()

                v = Vuln.from_mutant('XPATH injection vulnerability', desc,
                                     severity.MEDIUM, response.id,
                                     self.get_name(), mutant)

                v.add_to_highlight(xpath_error)
                self.kb_append_uniq(self, 'xpath', v)
                break

    def _find_xpath_error(self, response):
        """
        This method searches for xpath errors in html's.

        :param response: The HTTP response object
        :return: A list of errors found on the page
        """
        res = []
        for xpath_error_match in self._multi_in.query(response.body):
            msg = 'Found XPATH injection. The error showed by the web' \
                  ' application is (only a fragment is shown): "%s".' \
                  ' The error was found on response with id %s.'
            om.out.information(msg % (xpath_error_match, response.id))
            res.append(xpath_error_match)
        return res

    def get_plugin_deps(self):
        """
        :return: A list with the names of the plugins that should be run before
                 the current one.
        """
        return ['grep.error_500']

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Пример #12
0
class error_pages(GrepPlugin):
    """
    Grep every page for error pages.

    :author: Andres Riancho ([email protected])
    """

    ERROR_PAGES = (
        '<H1>Error page exception</H1>',
        # This signature fires up also in default 404 pages of aspx which
        # generates a lot of noise, so ... disabling it
        #mesg.append('<span><H1>Server Error in ',
        '<h2> <i>Runtime Error</i> </h2></span>',
        '<h2> <i>Access is denied</i> </h2></span>',
        '<H3>Original Exception: </H3>',
        'Server object error',
        'invalid literal for int()',
        'exceptions.ValueError',
        '<font face="Arial" size=2>Type mismatch: ',
        '[an error occurred while processing this directive]',
        '<HTML><HEAD><TITLE>Error Occurred While Processing Request</TITLE>'
        '</HEAD><BODY><HR><H3>Error Occurred While Processing Request</H3><P>',

        # VBScript
        '<p>Microsoft VBScript runtime </font>',
        "<font face=\"Arial\" size=2>error '800a000d'</font>",

        # nwwcgi errors
        '<TITLE>nwwcgi Error',

        # ASP error I found during a pentest, the ASP used a foxpro db, not a
        # SQL injection
        '<font face="Arial" size=2>error \'800a0005\'</font>',
        '<h2> <i>Runtime Error</i> </h2></span>',
        # Some error in ASP when using COM objects.
        'Operation is not allowed when the object is closed.',
        # An error when ASP tries to include something and it fails
        '<p>Active Server Pages</font> <font face="Arial" size=2>error \'ASP 0126\'</font>',

        # ASPX
        '<b> Description: </b>An unhandled exception occurred during the execution of the'
        ' current web request',

        # Struts
        '] does not contain handler parameter named',

        # PHP
        '<b>Warning</b>: ',
        'No row with the given identifier',
        'open_basedir restriction in effect',
        "eval()'d code</b> on line <b>",
        "Cannot execute a blank command in",
        "Fatal error</b>:  preg_replace",
        "thrown in <b>",
        "#0 {main}",
        "Stack trace:",
        "</b> on line <b>",

        # python
        "PythonHandler django.core.handlers.modpython",
        "t = loader.get_template(template_name) # You need to create a 404.html template.",
        '<h2>Traceback <span>(innermost last)</span></h2>',

        # Java
        '[java.lang.',
        'class java.lang.',
        'java.lang.NullPointerException',
        'java.rmi.ServerException',
        'at java.lang.',
        'onclick="toggle(\'full exception chain stacktrace\')"',
        'at org.apache.catalina',
        'at org.apache.coyote.',
        'at org.apache.tomcat.',
        'at org.apache.jasper.',

        # ruby
        '<h1 class="error_title">Ruby on Rails application could not be started</h1>',

        # Coldfusion
        '<title>Error Occurred While Processing Request</title></head><body><p></p>',
        '<HTML><HEAD><TITLE>Error Occurred While Processing Request</TITLE></HEAD><BODY><HR><H3>',
        '<TR><TD><H4>Error Diagnostic Information</H4><P><P>',
        '<li>Search the <a href="http://www.macromedia.com/support/coldfusion/" '
        'target="new">Knowledge Base</a> to find a solution to your problem.</li>',

        # http://www.programacion.net/asp/articulo/kbr_execute/
        'Server.Execute Error',

        # IIS
        '<h2 style="font:8pt/11pt verdana; color:000000">HTTP 403.6 - Forbidden: IP address rejected<br>',
        '<TITLE>500 Internal Server Error</TITLE>',
    )
    _multi_in = multi_in(ERROR_PAGES)

    VERSION_REGEX = (
        ('<address>(.*?)</address>', 'Apache'),
        ('<HR size="1" noshade="noshade"><h3>(.*?)</h3></body>',
         'Apache Tomcat'),
        ('<a href="http://www.microsoft.com/ContentRedirect.asp\?prd=iis&sbp=&pver=(.*?)&pid=&ID',
         'IIS'),

        # <b>Version Information:</b>&nbsp;Microsoft .NET Framework Version:1.1.4322.2300; ASP.NET Version:1.1.4322.2300
        ('<b>Version Information:</b>&nbsp;(.*?)\n', 'ASP .NET'))
    _multi_re = multi_re(VERSION_REGEX)

    def __init__(self):
        GrepPlugin.__init__(self)

        self._already_reported_versions = []
        self._compiled_regex = []

    def grep(self, request, response):
        """
        Plugin entry point, find the error pages and report them.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None
        """
        if not response.is_text_or_html():
            return

        self.find_error_page(request, response)
        self.find_version_numbers(request, response)

    def find_error_page(self, request, response):
        for msg in self._multi_in.query(response.body):

            desc = 'The URL: "%s" contains the descriptive error: "%s".'
            desc = desc % (response.get_url(), msg)
            i = Info('Descriptive error page', desc, response.id,
                     self.get_name())
            i.set_url(response.get_url())
            i.add_to_highlight(msg)

            self.kb_append_uniq(self, 'error_page', i, 'URL')

            # There is no need to report more than one info for the same result,
            # the user will read the info object and analyze it even if we
            # report it only once. If we report it twice, he'll get mad ;)
            break

    def find_version_numbers(self, request, response):
        """
        Now i'll check if I can get a version number from the error page
        This is common in apache, tomcat, etc...
        """
        if 400 < response.get_code() < 600:

            for match, _, _, server in self._multi_re.query(response.body):
                match_string = match.group(0)
                if match_string not in self._already_reported_versions:
                    # Save the info obj
                    desc = 'An error page sent this %s version: "%s".'
                    desc %= (server, match_string)

                    i = Info('Error page with information disclosure', desc,
                             response.id, self.get_name())
                    i.set_url(response.get_url())
                    i.add_to_highlight(server)
                    i.add_to_highlight(match_string)

                    kb.kb.append(self, 'server', i)
                    kb.kb.raw_write(self, 'server', match_string)

                    self._already_reported_versions.append(match_string)

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Пример #13
0
class html_comments(GrepPlugin):
    """
    Extract and analyze HTML comments.

    :author: Andres Riancho ([email protected])
    """

    HTML_RE = re.compile('<[a-zA-Z]*.*?>.*?</[a-zA-Z]>')

    INTERESTING_WORDS = (
        # In English
        'user',
        'pass',
        'xxx',
        'fix',
        'bug',
        'broken',
        'oops',
        'hack',
        'caution',
        'todo',
        'note',
        'warning',
        '!!!',
        '???',
        'shit',
        'pass',
        'password',
        'passwd',
        'pwd',
        'secret',
        'stupid',

        # In Spanish
        'tonto',
        'porqueria',
        'cuidado',
        'usuario',
        u'contraseña',
        'puta',
        'email',
        'security',
        'captcha',
        'pinga',
        'cojones',

        # some in Portuguese
        'banco',
        'bradesco',
        'itau',
        'visa',
        'bancoreal',
        u'transfêrencia',
        u'depósito',
        u'cartão',
        u'crédito',
        'dados pessoais')

    _multi_in = multi_in([' %s ' % w for w in INTERESTING_WORDS])

    def __init__(self):
        GrepPlugin.__init__(self)

        # Internal variables
        self._comments = DiskDict(table_prefix='html_comments')
        self._already_reported = ScalableBloomFilter()
        self._end_was_called = False

    def grep(self, request, response):
        """
        Plugin entry point, parse those comments!

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None
        """
        if not response.is_text_or_html():
            return

        try:
            dp = parser_cache.dpc.get_document_parser_for(response)
        except BaseFrameworkException:
            return

        for comment in dp.get_comments():
            # These next two lines fix this issue:
            # audit.ssi + grep.html_comments + web app with XSS = false positive
            if request.sent(comment):
                continue

            if self._is_new(comment, response):
                self._interesting_word(comment, request, response)
                self._html_in_comment(comment, request, response)

    def _interesting_word(self, comment, request, response):
        """
        Find interesting words in HTML comments
        """
        comment = comment.lower()

        for word in self._multi_in.query(comment):
            if (word, response.get_url()) in self._already_reported:
                continue

            desc = ('A comment with the string "%s" was found in: "%s".'
                    ' This could be interesting.')
            desc %= (word, response.get_url())

            i = Info.from_fr('Interesting HTML comment', desc, response.id,
                             self.get_name(), request)
            i.add_to_highlight(word)

            kb.kb.append(self, 'interesting_comments', i)
            om.out.information(i.get_desc())

            self._already_reported.add((word, response.get_url()))

    def _html_in_comment(self, comment, request, response):
        """
        Find HTML code in HTML comments
        """
        html_in_comment = self.HTML_RE.search(comment)

        if html_in_comment is None:
            return

        if (comment, response.get_url()) in self._already_reported:
            return

        # There is HTML code in the comment.
        comment = comment.strip()
        comment = comment.replace('\n', '')
        comment = comment.replace('\r', '')
        comment = comment[:40]

        desc = ('A comment with the string "%s" was found in: "%s".'
                ' This could be interesting.')
        desc %= (comment, response.get_url())

        i = Info.from_fr('HTML comment contains HTML code', desc, response.id,
                         self.get_name(), request)
        i.set_uri(response.get_uri())
        i.add_to_highlight(html_in_comment.group(0))

        kb.kb.append(self, 'html_comment_hides_html', i)
        om.out.information(i.get_desc())
        self._already_reported.add((comment, response.get_url()))

    def _handle_no_such_table(self, comment, response, nste):
        """
        I had a lot of issues trying to reproduce [0], so this code is just
        a helper for me to identify the root cause.

        [0] https://github.com/andresriancho/w3af/issues/10849

        :param nste: The original exception
        :param comment: The comment we're analyzing
        :param response: The HTTP response
        :return: None, an exception with more information is re-raised
        """
        msg = ('A NoSuchTableException was raised by the DBMS. This issue is'
               ' related with #10849 , but since I was unable to reproduce'
               ' it, extra debug information is added to the exception:'
               '\n'
               '\n - Grep plugin end() was called: %s'
               '\n - Response ID is: %s'
               '\n - HTML comment is: "%s"'
               '\n - Original exception: "%s"'
               '\n\n'
               'https://github.com/andresriancho/w3af/issues/10849\n')
        args = (self._end_was_called, response.get_id(), comment, nste)
        raise NoSuchTableException(msg % args)

    def _is_new(self, comment, response):
        """
        Make sure that we perform a thread safe check on the self._comments
        dict, in order to avoid duplicates.
        """
        with self._plugin_lock:

            # pylint: disable=E1103
            try:
                comment_data = self._comments.get(comment, None)
            except NoSuchTableException, nste:
                self._handle_no_such_table(comment, response, nste)

            response_url = response.get_url()

            if comment_data is None:
                self._comments[comment] = [(response_url, response.id)]
                return True
            else:
                for saved_url, response_id in comment_data:
                    if response_url == saved_url:
                        return False
                else:
                    comment_data.append((response_url, response.id))
                    self._comments[comment] = comment_data
                    return True
Пример #14
0
class ldapi(AuditPlugin):
    """
    Find LDAP injection bugs.
    :author: Andres Riancho ([email protected])
    """

    LDAP_ERRORS = (
        # Not sure which lang or LDAP engine
        'supplied argument is not a valid ldap',

        # Java
        'javax.naming.NameNotFoundException',
        'LDAPException',
        'com.sun.jndi.ldap',

        # PHP
        'Bad search filter',

        # http://support.microsoft.com/kb/218185
        'Protocol error occurred',
        'Size limit has exceeded',
        'An inappropriate matching occurred',
        'A constraint violation occurred',
        'The syntax is invalid',
        'Object does not exist',
        'The alias is invalid',
        'The distinguished name has an invalid syntax',
        'The server does not handle directory requests',
        'There was a naming violation',
        'There was an object class violation',
        'Results returned are too large',
        'Unknown error occurred',
        'Local error occurred',
        'The search filter is incorrect',
        'The search filter is invalid',
        'The search filter cannot be recognized',

        # OpenLDAP
        'Invalid DN syntax',
        'No Such Object',

        # IPWorks LDAP
        # http://www.tisc-insight.com/newsletters/58.html
        'IPWorksASP.LDAP',

        # https://entrack.enfoldsystems.com/browse/SERVERPUB-350
        'Module Products.LDAPMultiPlugins')

    _multi_in = multi_in(LDAP_ERRORS)

    LDAPI_STRINGS = [
        "^(#$!@#$)(()))******",
    ]

    def __init__(self):
        AuditPlugin.__init__(self)

    def audit(self, freq, orig_response):
        """
        Tests an URL for LDAP injection vulnerabilities.

        :param freq: A FuzzableRequest
        """
        mutants = create_mutants(freq,
                                 self.LDAPI_STRINGS,
                                 orig_resp=orig_response)

        self._send_mutants_in_threads(self._uri_opener.send_mutant, mutants,
                                      self._analyze_result)

    def _analyze_result(self, mutant, response):
        """
        Analyze results of the _send_mutant method.
        """
        #
        #   I will only report the vulnerability once.
        #
        if self._has_no_bug(mutant):

            ldap_error_list = self._find_ldap_error(response)
            for ldap_error_string in ldap_error_list:
                if ldap_error_string not in mutant.get_original_response_body(
                ):

                    desc = 'LDAP injection was found at: %s' % mutant.found_at(
                    )

                    v = Vuln.from_mutant('LDAP injection vulnerability', desc,
                                         severity.HIGH, response.id,
                                         self.get_name(), mutant)

                    v.add_to_highlight(ldap_error_string)

                    self.kb_append_uniq(self, 'ldapi', v)
                    break

    def _find_ldap_error(self, response):
        """
        This method searches for LDAP errors in html's.

        :param response: The HTTP response object
        :return: A list of errors found on the page
        """
        res = []
        for match_string in self._multi_in.query(response.body):
            msg = 'Found LDAP error string. The error returned by the web'
            msg += ' application is (only a fragment is shown): "'
            msg += match_string + '". The error was found on '
            msg += 'response with id ' + str(response.id) + '.'
            om.out.information(msg)
            res.append(match_string)
        return res

    def get_plugin_deps(self):
        """
        :return: A list with the names of the plugins that should be run before the
        current one.
        """
        return ['grep.error_500']

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Пример #15
0
class os_commanding(AuditPlugin):
    """
    Find OS Commanding vulnerabilities.
    :author: Andres Riancho ([email protected])
    """

    FILE_PATTERNS = FILE_PATTERNS
    _multi_in = multi_in(FILE_PATTERNS)

    def __init__(self):
        AuditPlugin.__init__(self)

        #
        #   Some internal variables
        #
        self._special_chars = ['', '&&', '|', ';']
        self._file_compiled_regex = []

    def audit(self, freq, orig_response):
        """
        Tests an URL for OS Commanding vulnerabilities.

        :param freq: A FuzzableRequest
        """
        # We are implementing two different ways of detecting OS Commanding
        # vulnerabilities:
        #       - Time delays
        #       - Writing a known file to the HTML output
        # The basic idea is to be able to detect ANY vulnerability, so we use
        # ALL of the known techniques
        #
        # Please note that I'm running the echo ones first in order to get them
        # into the KB before the ones with time delays so that the os_commanding
        # exploit can (with a higher degree of confidence) exploit the
        # vulnerability
        #
        # This also speeds-up the detection process a little bit in the cases
        # where there IS a vulnerability present and can be found with both
        # methods.
        self._with_echo(freq, orig_response)
        self._with_time_delay(freq)

    def _with_echo(self, freq, orig_response):
        """
        Tests an URL for OS Commanding vulnerabilities using cat/type to write
        the content of a known file (i.e. /etc/passwd) to the HTML.

        :param freq: A FuzzableRequest
        """
        # Prepare the strings to create the mutants
        command_list = self._get_echo_commands()
        only_command_strings = [v.get_command() for v in command_list]
        mutants = create_mutants(freq,
                                 only_command_strings,
                                 orig_resp=orig_response)

        self._send_mutants_in_threads(self._uri_opener.send_mutant, mutants,
                                      self._analyze_echo)

    def _analyze_echo(self, mutant, response):
        """
        Analyze results of the _send_mutant method that was sent in the
        _with_echo method.
        """
        #
        #   I will only report the vulnerability once.
        #
        if self._has_bug(mutant):
            return

        for file_pattern_match in self._multi_in.query(response.get_body()):

            if file_pattern_match not in mutant.get_original_response_body():
                # Search for the correct command and separator
                sentOs, sentSeparator = self._get_os_separator(mutant)

                desc = 'OS Commanding was found at: %s' % mutant.found_at()
                # Create the vuln obj
                v = Vuln.from_mutant('OS commanding vulnerability',
                                     desc, severity.HIGH, response.id,
                                     self.get_name(), mutant)

                v['os'] = sentOs
                v['separator'] = sentSeparator
                v.add_to_highlight(file_pattern_match)

                self.kb_append_uniq(self, 'os_commanding', v)
                break

    def _get_os_separator(self, mutant):
        """
        :param mutant: The mutant that is being analyzed.
        :return: A tuple with the OS and the command separator
        that was used to generate the mutant.
        """
        # Retrieve the data I need to create the vuln and the info objects
        command_list = self._get_echo_commands()

        ### BUGBUG: Are you sure that this works as expected?!?!?!
        for comm in command_list:
            if comm.get_command() in mutant.get_mod_value():
                os = comm.get_OS()
                separator = comm.get_separator()

        return os, separator

    def _with_time_delay(self, freq):
        """
        Tests an URL for OS Commanding vulnerabilities using time delays.

        :param freq: A FuzzableRequest
        """
        fake_mutants = create_mutants(freq, [
            '',
        ])

        for mutant in fake_mutants:

            if self._has_bug(mutant):
                continue

            for delay_obj in self._get_wait_commands():

                ed = ExactDelayController(mutant, delay_obj, self._uri_opener)
                success, responses = ed.delay_is_controlled()

                if success:
                    desc = 'OS Commanding was found at: %s' % mutant.found_at()

                    v = Vuln.from_mutant('OS commanding vulnerability', desc,
                                         severity.HIGH,
                                         [r.id for r in responses],
                                         self.get_name(), mutant)

                    v['os'] = delay_obj.get_OS()
                    v['separator'] = delay_obj.get_separator()

                    self.kb_append_uniq(self, 'os_commanding', v)
                    break

    def _get_echo_commands(self):
        """
        :return: This method returns a list of commands to try to execute in
                 order to print the content of a known file.
        """
        commands = []
        for special_char in self._special_chars:
            # Unix
            cmd_string = special_char + "/bin/cat /etc/passwd"
            commands.append(Command(cmd_string, 'unix', special_char))
            # Windows
            cmd_string = special_char + "type %SYSTEMROOT%\\win.ini"
            commands.append(Command(cmd_string, 'windows', special_char))

        # Execution quotes
        commands.append(Command("`/bin/cat /etc/passwd`", 'unix', '`'))
        # FoxPro uses run to run os commands. I found one of this vulns !!
        commands.append(
            Command("run type %SYSTEMROOT%\\win.ini", 'windows', 'run'))

        # Now I filter the commands based on the target_os:
        target_os = cf.cf.get('target_os').lower()
        commands = [
            c for c in commands
            if c.get_OS() == target_os or target_os == 'unknown'
        ]

        return commands

    def _get_wait_commands(self):
        """
        :return: This method returns a list of commands to try to execute in
                 order to introduce a time delay.
        """
        commands = []
        for special_char in self._special_chars:
            # Windows
            cmd_fmt = special_char + 'ping -n %s localhost'
            delay_cmd = PingDelay(cmd_fmt, 'windows', special_char)
            commands.append(delay_cmd)

            # Unix
            cmd_fmt = special_char + 'ping -c %s localhost'
            delay_cmd = PingDelay(cmd_fmt, 'unix', special_char)
            commands.append(delay_cmd)

            # This is needed for solaris 10
            cmd_fmt = special_char + '/usr/sbin/ping -s localhost %s'
            delay_cmd = PingDelay(cmd_fmt, 'unix', special_char)
            commands.append(delay_cmd)

        # Using execution quotes
        commands.append(PingDelay('`ping -n %s localhost`', 'windows', '`'))
        commands.append(PingDelay('`ping -c %s localhost`', 'unix', '`'))

        # FoxPro uses the "run" macro to exec os commands. I found one of this
        # vulns !!
        commands.append(
            PingDelay('run ping -n %s localhost', 'windows', 'run '))

        # Now I filter the commands based on the target_os:
        target_os = cf.cf.get('target_os').lower()
        commands = [
            c for c in commands
            if c.get_OS() == target_os or target_os == 'unknown'
        ]

        return commands

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Пример #16
0
class html_comments(GrepPlugin):
    """
    Extract and analyze HTML comments.

    :author: Andres Riancho ([email protected])
    """

    HTML_RE = re.compile('<[a-zA-Z]*.*?>.*?</[a-zA-Z]>')

    INTERESTING_WORDS = (
        # In English
        'user',
        'pass',
        'xxx',
        'fix',
        'bug',
        'broken',
        'oops',
        'hack',
        'caution',
        'todo',
        'note',
        'warning',
        '!!!',
        '???',
        'shit',
        'pass',
        'password',
        'passwd',
        'pwd',
        'secret',
        'stupid',

        # In Spanish
        'tonto',
        'porqueria',
        'cuidado',
        'usuario',
        u'contraseña',
        'puta',
        'email',
        'security',
        'captcha',
        'pinga',
        'cojones',

        # some in Portuguese
        'banco',
        'bradesco',
        'itau',
        'visa',
        'bancoreal',
        u'transfêrencia',
        u'depósito',
        u'cartão',
        u'crédito',
        'dados pessoais')

    _multi_in = multi_in([' %s ' % w for w in INTERESTING_WORDS])

    def __init__(self):
        GrepPlugin.__init__(self)

        # Internal variables
        self._comments = DiskDict(table_prefix='html_comments')
        self._already_reported = ScalableBloomFilter()

    def grep(self, request, response):
        """
        Plugin entry point, parse those comments!

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None
        """
        if not response.is_text_or_html():
            return

        try:
            dp = parser_cache.dpc.get_document_parser_for(response)
        except BaseFrameworkException:
            return

        for comment in dp.get_comments():
            # These next two lines fix this issue:
            # audit.ssi + grep.html_comments + web app with XSS = false positive
            if request.sent(comment):
                continue

            if self._is_new(comment, response):

                self._interesting_word(comment, request, response)
                self._html_in_comment(comment, request, response)

    def _interesting_word(self, comment, request, response):
        """
        Find interesting words in HTML comments
        """
        comment = comment.lower()

        for word in self._multi_in.query(comment):
            if (word, response.get_url()) in self._already_reported:
                continue

            desc = ('A comment with the string "%s" was found in: "%s".'
                    ' This could be interesting.')
            desc %= (word, response.get_url())

            v = Vuln.from_fr('Interesting HTML comment',
                             desc, severity.INFORMATION, response.id,
                             self.get_name(), request)
            v.add_to_highlight(word)

            kb.kb.append(self, 'interesting_comments', v)

            self._already_reported.add((word, response.get_url()))

    def _html_in_comment(self, comment, request, response):
        """
        Find HTML code in HTML comments
        """
        html_in_comment = self.HTML_RE.search(comment)

        if html_in_comment is None:
            return

        if (comment, response.get_url()) in self._already_reported:
            return

        # There is HTML code in the comment.
        comment = comment.strip()
        comment = comment.replace('\n', '')
        comment = comment.replace('\r', '')
        comment = comment[:40]

        desc = ('A comment with the string "%s" was found in: "%s".'
                ' This could be interesting.')
        desc %= (comment, response.get_url())

        v = Vuln.from_fr('HTML comment contains HTML code',
                         desc, severity.INFORMATION, response.id,
                         self.get_name(), request)
        v.set_uri(response.get_uri())
        v.add_to_highlight(html_in_comment.group(0))

        om.out.vulnerability(v.get_desc(), severity=severity.INFORMATION)
        kb.kb.append(self, 'html_comment_hides_html', v)
        self._already_reported.add((comment, response.get_url()))

    def _is_new(self, comment, response):
        """
        Make sure that we perform a thread safe check on the self._comments
        dict, in order to avoid duplicates.
        """
        with self._plugin_lock:

            #pylint: disable=E1103
            comment_data = self._comments.get(comment, None)
            response_url = response.get_url()

            if comment_data is None:
                self._comments[comment] = [(response_url, response.id)]
                return True
            else:
                for saved_url, response_id in comment_data:
                    if response_url == saved_url:
                        return False
                else:
                    comment_data.append((response_url, response.id))
                    self._comments[comment] = comment_data
                    return True
            #pylint: enable=E1103

    def end(self):
        """
        This method is called when the plugin wont be used anymore.
        :return: None
        """
        for comment, url_request_id_lst in self._comments.iteritems():

            stick_comment = ' '.join(comment.split())

            if len(stick_comment) > 40:
                msg = ('A comment with the string "%s..." (and %s more bytes)'
                       ' was found on these URL(s):')
                args = (stick_comment[:40], str(len(stick_comment) - 40))
                om.out.vulnerability(msg % args, severity=severity.INFORMATION)
            else:
                msg = 'A comment containing "%s" was found on these URL(s):'
                om.out.vulnerability(msg % stick_comment,
                                     severity=severity.INFORMATION)

            inform = []

            for url, request_id in url_request_id_lst:
                msg = '- %s (request with id: %s)'
                inform.append(msg % (url, request_id))

            for i in sorted(inform):
                om.out.vulnerability(i, severity=severity.INFORMATION)

        self._comments.cleanup()

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Пример #17
0
class lfi(AuditPlugin):
    """
    Find local file inclusion vulnerabilities.
    :author: Andres Riancho ([email protected])
    """

    FILE_PATTERNS = FILE_PATTERNS
    _multi_in = multi_in(FILE_PATTERNS)

    def __init__(self):
        AuditPlugin.__init__(self)

        # Internal variables
        self._file_compiled_regex = []
        self._error_compiled_regex = []
        self._open_basedir = False

    def audit(self, freq, orig_response):
        """
        Tests an URL for local file inclusion vulnerabilities.

        :param freq: A FuzzableRequest
        """
        # Which payloads do I want to send to the remote end?
        local_files = [freq.get_url().get_file_name()]
        if not self._open_basedir:
            local_files.extend(self._get_local_file_list(freq.get_url()))

        mutants = create_mutants(freq, local_files, orig_resp=orig_response)

        self._send_mutants_in_threads(self._uri_opener.send_mutant,
                                      mutants,
                                      self._analyze_result,
                                      grep=False)

    def _get_local_file_list(self, orig_url):
        """
        This method returns a list of local files to try to include.

        :return: A string list, see above.
        """
        local_files = []

        extension = orig_url.get_extension()

        # I will only try to open these files, they are easy to identify of they
        # echoed by a vulnerable web app and they are on all unix or windows
        # default installs. Feel free to mail me ( Andres Riancho ) if you know
        # about other default files that could be installed on AIX ? Solaris ?
        # and are not /etc/passwd
        if cf.cf.get('target_os') in ['unix', 'unknown']:
            local_files.append("../" * 15 + "etc/passwd")
            local_files.append("../" * 15 + "etc/passwd\0")
            local_files.append("../" * 15 + "etc/passwd\0.html")
            local_files.append("/etc/passwd")

            # This test adds support for finding vulnerabilities like this one
            # http://website/zen-cart/extras/curltest.php?url=file:///etc/passwd
            # local_files.append("file:///etc/passwd")

            local_files.append("/etc/passwd\0")
            local_files.append("/etc/passwd\0.html")
            if extension != '':
                local_files.append("/etc/passwd%00." + extension)
                local_files.append("../" * 15 + "etc/passwd%00." + extension)

        if cf.cf.get('target_os') in ['windows', 'unknown']:
            local_files.append("../" * 15 + "boot.ini\0")
            local_files.append("../" * 15 + "boot.ini\0.html")
            local_files.append("C:\\boot.ini")
            local_files.append("C:\\boot.ini\0")
            local_files.append("C:\\boot.ini\0.html")
            local_files.append("%SYSTEMROOT%\\win.ini")
            local_files.append("%SYSTEMROOT%\\win.ini\0")
            local_files.append("%SYSTEMROOT%\\win.ini\0.html")
            if extension != '':
                local_files.append("C:\\boot.ini%00." + extension)
                local_files.append("%SYSTEMROOT%\\win.ini%00." + extension)

        return local_files

    def _analyze_result(self, mutant, response):
        """
        Analyze results of the _send_mutant method.
        Try to find the local file inclusions.
        """
        # I analyze the response searching for a specific PHP error string
        # that tells me that open_basedir is enabled, and our request triggered
        # the restriction. If open_basedir is in use, it makes no sense to keep
        # trying to read "/etc/passwd", that is why this variable is used to
        # determine which tests to send if it was possible to detect the usage
        # of this security feature.
        if not self._open_basedir:

            basedir_warning = 'open_basedir restriction in effect'

            if basedir_warning in response and \
                            basedir_warning not in mutant.get_original_response_body():
                self._open_basedir = True

        #
        #   I will only report the vulnerability once.
        #
        if self._has_bug(mutant):
            return

        #
        #   Identify the vulnerability
        #
        file_content_list = self._find_file(response)
        for file_pattern_match in file_content_list:
            if file_pattern_match not in mutant.get_original_response_body():
                desc = 'Local File Inclusion was found at: %s'
                desc = desc % mutant.found_at()

                v = Vuln.from_mutant('Local file inclusion vulnerability',
                                     desc, severity.MEDIUM, response.id,
                                     self.get_name(), mutant)

                v['file_pattern'] = file_pattern_match

                v.add_to_highlight(file_pattern_match)
                self.kb_append_uniq(self, 'lfi', v)
                return

        #
        # If the vulnerability could not be identified by matching strings that
        # commonly appear in "/etc/passwd", then I'll check one more thing...
        # (note that this is run if no vulns were identified)
        #
        # http://host.tld/show_user.php?id=show_user.php
        if mutant.get_token_value() == mutant.get_url().get_file_name():
            match, lang = is_source_file(response.get_body())
            if match:
                # We were able to read the source code of the file that is
                # vulnerable to local file read
                desc = 'An arbitrary local file read vulnerability was' \
                       ' found at: %s' % mutant.found_at()

                v = Vuln.from_mutant('Local file inclusion vulnerability',
                                     desc, severity.MEDIUM, response.id,
                                     self.get_name(), mutant)

                #
                #    Set which part of the source code to match
                #
                match_source_code = match.group(0)
                v['file_pattern'] = match_source_code

                self.kb_append_uniq(self, 'lfi', v)
                return

        #
        #   Check for interesting errors (note that this is run if no vulns were
        #   identified)
        #
        for regex in self.get_include_errors():

            match = regex.search(response.get_body())

            if match and not regex.search(mutant.get_original_response_body()):
                desc = 'A file read error was found at: %s'
                desc = desc % mutant.found_at()

                i = Info.from_mutant('File read error', desc, response.id,
                                     self.get_name(), mutant)

                self.kb_append_uniq(self, 'error', i)

    def _find_file(self, response):
        """
        This method finds out if the local file has been successfully included
        in the resulting HTML.

        :param response: The HTTP response object
        :return: A list of errors found on the page
        """
        res = set()
        for file_pattern_match in self._multi_in.query(response.get_body()):
            res.add(file_pattern_match)

        if len(res) == 1:
            msg = 'A file fragment was found. The section where the file is' \
                  ' included is (only a fragment is shown): "%s". This is' \
                  ' just an informational message, which might be related' \
                  '  to a vulnerability and was found on response with id %s.'
            om.out.debug(msg % (list(res)[0], response.id))

        if len(res) > 1:
            msg = 'File fragments have been found. The following is a list' \
                  ' of file fragments that were returned by the web' \
                  ' application while testing for local file inclusion: \n'

            for file_pattern_match in res:
                msg += '- "%s" \n' % file_pattern_match

            msg += 'This is just an informational message, which might be' \
                   ' related to a vulnerability and was found in response' \
                   ' with id %s.' % response.id

            om.out.debug(msg)

        return res

    def get_include_errors(self):
        """
        :return: A list of file inclusion / file read errors generated by the
                 web application.
        """
        #
        # In previous versions of the plugin the "Inclusion errors" listed in
        # the _get_file_patterns method made sense... but... it seems that they
        # trigger false positives... So I moved them here and report them as
        # something "interesting" if the actual file inclusion is not possible
        #
        if self._error_compiled_regex:
            return self._error_compiled_regex
        else:
            read_errors = [
                "java.io.FileNotFoundException:", 'java.lang.Exception:',
                'java.lang.IllegalArgumentException:',
                'java.net.MalformedURLException:',
                'The server encountered an internal error \\(.*\\) that prevented it from fulfilling this request.',
                'The requested resource \\(.*\\) is not available.',
                "fread\\(\\):", "for inclusion '\\(include_path=",
                "Failed opening required", "<b>Warning</b>:  file\\(",
                "<b>Warning</b>:  file_get_contents\\("
            ]

            self._error_compiled_regex = [
                re.compile(i, re.IGNORECASE) for i in read_errors
            ]
            return self._error_compiled_regex

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Пример #18
0
class mx_injection(AuditPlugin):
    """
    Find MX injection vulnerabilities.
    :author: Andres Riancho ([email protected])
    """

    MX_ERRORS = (
        'Unexpected extra arguments to Select', 'Bad or malformed request',
        'Could not access the following folders', 'A000', 'A001',
        'Invalid mailbox name',
        'To check for outside changes to the folder list go to the folders page'
    )
    _multi_in = multi_in(MX_ERRORS)

    def __init__(self):
        """
        Plugin added just for completeness... I dont really expect to find one
        of this bugs in my life... but well.... if someone , somewhere in the
        planet ever finds a bug of using this plugin... THEN my job has been
        done :P
        """
        AuditPlugin.__init__(self)

    def audit(self, freq, orig_response):
        """
        Tests an URL for mx injection vulnerabilities.

        :param freq: A FuzzableRequest
        """
        mx_injection_strings = self._get_MX_injection_strings()
        mutants = create_mutants(freq,
                                 mx_injection_strings,
                                 orig_resp=orig_response)

        self._send_mutants_in_threads(self._uri_opener.send_mutant, mutants,
                                      self._analyze_result)

    def _analyze_result(self, mutant, response):
        """
        Analyze results of the _send_mutant method.
        """
        # I will only report the vulnerability once.
        if self._has_no_bug(mutant):

            mx_error_list = self._multi_in.query(response.body)
            for mx_error in mx_error_list:
                if mx_error not in mutant.get_original_response_body():
                    desc = 'MX injection was found at: %s' % mutant.found_at()

                    v = Vuln.from_mutant('MX injection vulnerability', desc,
                                         severity.MEDIUM, response.id,
                                         self.get_name(), mutant)

                    v.add_to_highlight(mx_error)
                    self.kb_append_uniq(self, 'mx_injection', v)
                    break

    def _get_MX_injection_strings(self):
        """
        Gets a list of strings to test against the web app.

        :return: A list with all mx_injection strings to test. Example: [ '\"','f00000']
        """
        mx_injection_strings = []
        mx_injection_strings.append('"')
        mx_injection_strings.append('iDontExist')
        mx_injection_strings.append('')
        return mx_injection_strings

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Пример #19
0
class sqli(AuditPlugin):
    """
    Find SQL injection bugs.
    :author: Andres Riancho ([email protected])
    """
    SQL_ERRORS_STR = (
        # ASP / MSSQL
        (r'System.Data.OleDb.OleDbException', dbms.MSSQL),
        (r'[SQL Server]', dbms.MSSQL),
        (r'[Microsoft][ODBC SQL Server Driver]', dbms.MSSQL),
        (r'[SQLServer JDBC Driver]', dbms.MSSQL),
        (r'[SqlException', dbms.MSSQL),
        (r'System.Data.SqlClient.SqlException', dbms.MSSQL),
        (r'Unclosed quotation mark after the character string', dbms.MSSQL),
        (r"'80040e14'", dbms.MSSQL),
        (r'mssql_query()', dbms.MSSQL),
        (r'odbc_exec()', dbms.MSSQL),
        (r'Microsoft OLE DB Provider for ODBC Drivers', dbms.MSSQL),
        (r'Microsoft OLE DB Provider for SQL Server', dbms.MSSQL),
        (r'Incorrect syntax near', dbms.MSSQL),
        (r'Sintaxis incorrecta cerca de', dbms.MSSQL),
        (r'Syntax error in string in query expression', dbms.MSSQL),
        (r'ADODB.Field (0x800A0BCD)<br>', dbms.MSSQL),
        (r"ADODB.Recordset'", dbms.MSSQL),
        (r"Unclosed quotation mark before the character string", dbms.MSSQL),
        (r"'80040e07'", dbms.MSSQL),
        (r'Microsoft SQL Native Client error', dbms.MSSQL),
        # DB2
        (r'SQLCODE', dbms.DB2),
        (r'DB2 SQL error:', dbms.DB2),
        (r'SQLSTATE', dbms.DB2),
        (r'[CLI Driver]', dbms.DB2),
        (r'[DB2/6000]', dbms.DB2),
        # Sybase
        (r"Sybase message:", dbms.SYBASE),
        (r"Sybase Driver", dbms.SYBASE),
        (r"[SYBASE]", dbms.SYBASE),
        # Access
        (r'Syntax error in query expression', dbms.ACCESS),
        (r'Data type mismatch in criteria expression.', dbms.ACCESS),
        (r'Microsoft JET Database Engine', dbms.ACCESS),
        (r'[Microsoft][ODBC Microsoft Access Driver]', dbms.ACCESS),
        # ORACLE
        (r'Microsoft OLE DB Provider for Oracle', dbms.ORACLE),
        (r'wrong number or types', dbms.ORACLE),
        # POSTGRE
        (r'PostgreSQL query failed:', dbms.POSTGRE),
        (r'supplied argument is not a valid PostgreSQL result', dbms.POSTGRE),
        (r'unterminated quoted string at or near', dbms.POSTGRE),
        (r'pg_query() [:', dbms.POSTGRE),
        (r'pg_exec() [:', dbms.POSTGRE),
        # MYSQL
        (r'supplied argument is not a valid MySQL', dbms.MYSQL),
        (r'Column count doesn\'t match value count at row', dbms.MYSQL),
        (r'mysql_fetch_array()', dbms.MYSQL),
        (r'mysql_', dbms.MYSQL),
        (r'on MySQL result index', dbms.MYSQL),
        (r'You have an error in your SQL syntax;', dbms.MYSQL),
        (r'You have an error in your SQL syntax near', dbms.MYSQL),
        (r'MySQL server version for the right syntax to use', dbms.MYSQL),
        (r'Division by zero in', dbms.MYSQL),
        (r'not a valid MySQL result', dbms.MYSQL),
        (r'[MySQL][ODBC', dbms.MYSQL),
        (r"Column count doesn't match", dbms.MYSQL),
        (r"the used select statements have different number of columns",
         dbms.MYSQL),
        (r"DBD::mysql::st execute failed", dbms.MYSQL),
        (r"DBD::mysql::db do failed:", dbms.MYSQL),
        # Informix
        (r'com.informix.jdbc', dbms.INFORMIX),
        (r'Dynamic Page Generation Error:', dbms.INFORMIX),
        (r'An illegal character has been found in the statement',
         dbms.INFORMIX),
        (r'[Informix]', dbms.INFORMIX),
        (r'<b>Warning</b>:  ibase_', dbms.INTERBASE),
        (r'Dynamic SQL Error', dbms.INTERBASE),
        # DML
        (r'[DM_QUERY_E_SYNTAX]', dbms.DMLDATABASE),
        (r'has occurred in the vicinity of:', dbms.DMLDATABASE),
        (r'A Parser Error (syntax error)', dbms.DMLDATABASE),
        # Java
        (r'java.sql.SQLException', dbms.JAVA),
        (r'Unexpected end of command in statement', dbms.JAVA),
        # Coldfusion
        (r'[Macromedia][SQLServer JDBC Driver]', dbms.MSSQL),
        # SQLite
        (r'could not prepare statement', dbms.SQLITE),
        # Generic errors..
        (r'Unknown column', dbms.UNKNOWN),
        (r'where clause', dbms.UNKNOWN),
        (r'SqlServer', dbms.UNKNOWN),
        (r'syntax error', dbms.UNKNOWN))
    _multi_in = multi_in(x[0] for x in SQL_ERRORS_STR)

    SQL_ERRORS_RE = (
        # ASP / MSSQL
        (r"Procedure '[^']+' requires parameter '[^']+'", dbms.MSSQL),
        # ORACLE
        (r'PLS-[0-9][0-9][0-9][0-9]', dbms.ORACLE),
        (r'ORA-[0-9][0-9][0-9][0-9]', dbms.ORACLE),
        # MYSQL
        (r"Table '[^']+' doesn't exist", dbms.MYSQL),
        # Generic errors..
        (r'SELECT .*? FROM .*?', dbms.UNKNOWN),
        (r'UPDATE .*? SET .*?', dbms.UNKNOWN),
        (r'INSERT INTO .*?', dbms.UNKNOWN),
    )
    _multi_re = multi_re(SQL_ERRORS_RE)

    # Note that these payloads are similar but they do generate different errors
    # depending on the SQL query context they are used. Removing one or the
    # other will lower our SQLMap testenv coverage
    SQLI_STRINGS = (u"a'b\"c'd\"", u"1'2\"3")

    SQLI_MESSAGE = (
        u'A SQL error was found in the response supplied by '
        u'the web application, the error is (only a fragment is '
        u'shown): "%s". The error was found on response with id %s.')

    def __init__(self):
        AuditPlugin.__init__(self)

    def audit(self, freq, orig_response):
        """
        Tests an URL for SQL injection vulnerabilities.

        :param freq: A FuzzableRequest
        """
        mutants = create_mutants(freq,
                                 self.SQLI_STRINGS,
                                 orig_resp=orig_response)

        self._send_mutants_in_threads(self._uri_opener.send_mutant, mutants,
                                      self._analyze_result)

    def _analyze_result(self, mutant, response):
        """
        Analyze results of the _send_mutant method.
        """
        sql_error_list = self._findsql_error(response)
        orig_resp_body = mutant.get_original_response_body()

        for sql_error_string, dbms_type in sql_error_list:
            if sql_error_string not in orig_resp_body:
                if self._has_no_bug(mutant):
                    # Create the vuln,
                    desc = 'SQL injection in a %s was found at: %s'
                    desc = desc % (dbms_type, mutant.found_at())

                    v = Vuln.from_mutant('SQL injection', desc, severity.HIGH,
                                         response.id, self.get_name(), mutant)

                    v.add_to_highlight(sql_error_string)
                    v['error'] = sql_error_string
                    v['db'] = dbms_type

                    self.kb_append_uniq(self, 'sqli', v)
                    break

    def _findsql_error(self, response):
        """
        This method searches for SQL errors in html's.

        :param response: The HTTP response object
        :return: A list of errors found on the page
        """
        res = []

        for match in self._multi_in.query(response.body):
            om.out.information(self.SQLI_MESSAGE % (match, response.id))
            dbms_type = [x[1] for x in self.SQL_ERRORS_STR if x[0] == match][0]
            res.append((match, dbms_type))

        for match, _, regex_comp, dbms_type in self._multi_re.query(
                response.body):
            om.out.information(self.SQLI_MESSAGE %
                               (match.group(0), response.id))
            res.append((match.group(0), dbms_type))

        return res

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """