Esempio n. 1
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()
Esempio n. 2
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()
Esempio n. 3
0
File: xpath.py Progetto: weisst/w3af
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',
        '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)

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

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

        :param freq: A FuzzableRequest
        '''
        xpath_strings = self._get_xpath_strings()
        mutants = create_mutants(freq, xpath_strings, orig_resp=orig_response)

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

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

        :return: A list with all xpath strings to test.
        '''
        xpath_strings = []
        xpath_strings.append("d'z\"0")

        # http://www.owasp.org/index.php/Testing_for_XML_Injection
        xpath_strings.append("<!--")

        return xpath_strings

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

            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 '''
Esempio n. 4
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 '''
Esempio n. 5
0
File: lfi.py Progetto: weisst/w3af
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 = []
        local_files.append(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_mod_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 '''
Esempio n. 6
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 response.get_code() > 400 and\
        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 = 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 '''
Esempio n. 7
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 = (
        'user',
        'pass',
        'xxx',
        'fix',
        'bug',
        'broken',
        'oops',
        'hack',
        'caution',
        'todo',
        'note',
        'warning',
        '!!!',
        '???',
        'shit',
        'stupid',
        'tonto',
        'porqueria',
        'ciudado',
        'usuario',
        'contrase',
        'puta',
        'secret',
        '@',
        '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(INTERESTING_WORDS)

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

        # Internal variables
        self._comments = DiskDict()
        self._already_reported_interesting = 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 w3afException:
            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

            # show nice comments ;)
            comment = comment.strip()

            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(response.body):
            if (word, response.get_url()
                ) not in self._already_reported_interesting:
                desc = 'A comment with the string "%s" was found in: "%s".'\
                       ' This could be interesting.'
                desc = desc % (word, response.get_url())

                i = Info('Interesting HTML comment', desc, response.id,
                         self.get_name())
                i.set_dc(request.get_dc())
                i.set_uri(response.get_uri())
                i.add_to_highlight(word)

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

                self._already_reported_interesting.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 and \
        (comment, response.get_url()) not in self._already_reported_interesting:
            # There is HTML code in the comment.
            comment = comment.replace('\n', '')
            comment = comment.replace('\r', '')
            desc = 'A comment with the string "%s" was found in: "%s".'\
                   ' This could be interesting.'
            desc = desc % (comment, response.get_url())

            i = Info('HTML comment contains HTML code', desc, response.id,
                     self.get_name())
            i.set_dc(request.get_dc())
            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_interesting.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)

            if comment_data is None:
                self._comments[comment] = [
                    (response.get_url(), response.id),
                ]
                return True
            else:
                if response.get_url() not in [x[0] for x in comment_data]:
                    comment_data.append((response.get_url(), response.id))
                    self._comments[comment] = comment_data
                    return True
            #pylint: enable=E1103

        return False

    def end(self):
        '''
        This method is called when the plugin wont be used anymore.
        :return: None
        '''
        inform = []
        for comment in self._comments.iterkeys():
            urls_with_this_comment = self._comments[comment]
            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):'
                om.out.information(
                    msg % (stick_comment[:40], str(len(stick_comment) - 40)))
            else:
                msg = 'A comment containing "%s" was found on these URL(s):'
                om.out.information(msg % (stick_comment))

            for url, request_id in urls_with_this_comment:
                inform.append('- ' + url + ' (request with id: ' +
                              str(request_id) + ')')

            inform.sort()
            for i in inform:
                om.out.information(i)

        self._comments.cleanup()

    def get_long_desc(self):
        '''
        :return: A DETAILED description of the plugin functions and features.
        '''
        return '''
Esempio n. 8
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()
        for dir_indexing_match in 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')
            break

    def get_long_desc(self):
        '''
        :return: A DETAILED description of the plugin functions and features.
        '''
        return '''
Esempio n. 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)

        self.worker_pool.map(self._send_request, mutants)

    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 (w3afException, w3afMustStopException):
            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, response.ids, 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 '''
Esempio n. 10
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 '''
Esempio n. 11
0
File: ldapi.py Progetto: weisst/w3af
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
        'Search: 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 '''
Esempio n. 12
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_no_bug(mutant):

            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 = ping_delay(cmd_fmt, 'windows', special_char)
            commands.append(delay_cmd)

            # Unix
            cmd_fmt = special_char + 'ping -c %s localhost'
            delay_cmd = ping_delay(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 = ping_delay(cmd_fmt, 'unix', special_char)
            commands.append(delay_cmd)

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

        # FoxPro uses the "run" macro to exec os commands. I found one of this vulns !!
        commands.append(
            ping_delay('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 '''