Exemple #1
0
    def _test_DNS(self, original_response, dns_wildcard_url):
        '''
        Check if http://www.domain.tld/ == http://domain.tld/
        '''
        headers = Headers([('Host', dns_wildcard_url.get_domain())])
        try:
            modified_response = self._uri_opener.GET(
                original_response.get_url(), cache=True, headers=headers)
        except w3afException:
            return
        else:
            if relative_distance_lt(modified_response.get_body(),
                                    original_response.get_body(), 0.35):
                desc = 'The target site has NO DNS wildcard, and the contents' \
                       ' of "%s" differ from the contents of "%s".'
                desc = desc % (dns_wildcard_url, original_response.get_url())

                i = Info('No DNS wildcard', desc, modified_response.id,
                         self.get_name())
                i.set_url(dns_wildcard_url)

                kb.kb.append(self, 'dns_wildcard', i)
                om.out.information(i.get_desc())
            else:
                desc = 'The target site has a DNS wildcard configuration, the'\
                      ' contents of "%s" are equal to the ones of "%s".'
                desc = desc % (dns_wildcard_url, original_response.get_url())

                i = Info('DNS wildcard', desc, modified_response.id,
                         self.get_name())
                i.set_url(original_response.get_url())

                kb.kb.append(self, 'dns_wildcard', i)
                om.out.information(i.get_desc())
Exemple #2
0
    def _analyze_crossdomain_clientaccesspolicy(self, url, response,
                                                file_name):
        try:
            dom = xml.dom.minidom.parseString(response.get_body())
        except Exception:
            # Report this, it may be interesting for the final user
            # not a vulnerability per-se... but... it's information after all
            if 'allow-access-from' in response.get_body() or \
            'cross-domain-policy' in response.get_body() or \
            'cross-domain-access' in response.get_body():

                desc = 'The "%s" file at: "%s" is not a valid XML.'
                desc = desc % (file_name, response.get_url())

                i = Info('Invalid RIA settings file', desc, response.id,
                         self.get_name())
                i.set_url(response.get_url())

                kb.kb.append(self, 'info', i)
                om.out.information(i.get_desc())
        else:
            if (file_name == 'crossdomain.xml'):
                url_list = dom.getElementsByTagName("allow-access-from")
                attribute = 'domain'
            if (file_name == 'clientaccesspolicy.xml'):
                url_list = dom.getElementsByTagName("domain")
                attribute = 'uri'

            for url in url_list:
                url = url.getAttribute(attribute)

                desc = 'The "%s" file at "%s" allows flash/silverlight'\
                       ' access from any site.'
                desc = desc % (file_name, response.get_url())

                if url == '*':
                    v = Vuln('Insecure RIA settings', desc, severity.LOW,
                             response.id, self.get_name())
                    v.set_url(response.get_url())
                    v.set_method('GET')

                    kb.kb.append(self, 'vuln', v)
                    om.out.vulnerability(v.get_desc(),
                                         severity=v.get_severity())
                else:
                    i = Info('Cross-domain allow ACL', desc, response.id,
                             self.get_name())
                    i.set_url(response.get_url())
                    i.set_method('GET')

                    kb.kb.append(self, 'info', i)
                    om.out.information(i.get_desc())
Exemple #3
0
    def _analyze_crossdomain_clientaccesspolicy(self, url, response, file_name):
        try:
            dom = xml.dom.minidom.parseString(response.get_body())
        except Exception:
            # Report this, it may be interesting for the final user
            # not a vulnerability per-se... but... it's information after all
            if 'allow-access-from' in response.get_body() or \
            'cross-domain-policy' in response.get_body() or \
            'cross-domain-access' in response.get_body():

                desc = 'The "%s" file at: "%s" is not a valid XML.'
                desc = desc % (file_name, response.get_url())
            
                i = Info('Invalid RIA settings file', desc, response.id,
                         self.get_name())
                i.set_url(response.get_url())
                
                kb.kb.append(self, 'info', i)
                om.out.information(i.get_desc())
        else:
            if(file_name == 'crossdomain.xml'):
                url_list = dom.getElementsByTagName("allow-access-from")
                attribute = 'domain'
            if(file_name == 'clientaccesspolicy.xml'):
                url_list = dom.getElementsByTagName("domain")
                attribute = 'uri'

            for url in url_list:
                url = url.getAttribute(attribute)

                desc = 'The "%s" file at "%s" allows flash/silverlight'\
                       ' access from any site.'
                desc = desc % (file_name, response.get_url())

                if url == '*':
                    v = Vuln('Insecure RIA settings', desc, severity.LOW,
                             response.id, self.get_name())
                    v.set_url(response.get_url())
                    v.set_method('GET')

                    kb.kb.append(self, 'vuln', v)
                    om.out.vulnerability(v.get_desc(),
                                         severity=v.get_severity())
                else:
                    i = Info('Cross-domain allow ACL', desc,
                             response.id, self.get_name())
                    i.set_url(response.get_url())
                    i.set_method('GET')

                    kb.kb.append(self, 'info', i)
                    om.out.information(i.get_desc())
Exemple #4
0
    def _analyze_author(self, response, frontpage_author):
        '''
        Analyze the author URL.

        :param response: The http response object for the _vti_inf file.
        :param frontpage_author: A regex match object.
        :return: None. All the info is saved to the kb.
        '''
        author_location = response.get_url().get_domain_path().url_join(
            frontpage_author.group(1))

        # Check for anomalies in the location of author.exe
        if frontpage_author.group(1) != '_vti_bin/_vti_aut/author.exe':
            name = 'Customized frontpage configuration'

            desc = 'The FPAuthorScriptUrl is at: "%s" instead of the default'\
                   ' location: "/_vti_bin/_vti_adm/author.exe". This is very'\
                   ' uncommon.'
            desc = desc % author_location
        else:
            name = 'FrontPage FPAuthorScriptUrl'

            desc = 'The FPAuthorScriptUrl is at: "%s".'
            desc = desc % author_location

        i = Info(name, desc, response.id, self.get_name())
        i.set_url(author_location)
        i['FPAuthorScriptUrl'] = author_location
        
        kb.kb.append(self, 'frontpage_version', i)
        om.out.information(i.get_desc())
Exemple #5
0
    def _fingerprint_meta(self, domain_path, wp_unique_url, response):
        '''
        Check if the wp version is in index header
        '''
        # Main scan URL passed from w3af + wp index page
        wp_index_url = domain_path.url_join('index.php')
        response = self._uri_opener.GET(wp_index_url, cache=True)

        # Find the string in the response html
        find = '<meta name="generator" content="[Ww]ord[Pp]ress (\d\.\d\.?\d?)" />'
        m = re.search(find, response.get_body())

        # If string found, group version
        if m:
            version = m.group(1)

            # Save it to the kb!
            desc = 'WordPress version "%s" found in the index header.'
            desc = desc % version

            i = Info('Fingerprinted Wordpress version', desc, response.id,
                     self.get_name())
            i.set_url(wp_index_url)

            kb.kb.append(self, 'info', i)
            om.out.information(i.get_desc())
Exemple #6
0
    def _fingerprint_data(self, domain_path, wp_unique_url, response):
        '''
        Find wordpress version from data
        '''
        for wp_fingerprint in self._get_wp_fingerprints():

            # The URL in the XML is relative AND it has two different variables
            # that we need to replace:
            #        $wp-content$    -> wp-content/
            #        $wp-plugins$    -> wp-content/plugins/
            path = wp_fingerprint.filepath
            path = path.replace('$wp-content$', 'wp-content/')
            path = path.replace('$wp-plugins$', 'wp-content/plugins/')
            test_url = domain_path.url_join(path)

            response = self._uri_opener.GET(test_url, cache=True)

            response_hash = hashlib.md5(response.get_body()).hexdigest()

            if response_hash == wp_fingerprint.hash:
                version = wp_fingerprint.version

                # Save it to the kb!
                desc = 'WordPress version "%s" fingerprinted by matching known md5'\
                       ' hashes to HTTP responses of static resources available at'\
                       ' the remote WordPress install.'
                desc = desc % version
                i = Info('Fingerprinted Wordpress version', desc, response.id,
                         self.get_name())
                i.set_url(test_url)

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

                break
    def _force_disclosures(self, domain_path, potentially_vulnerable_paths):
        '''
        :param domain_path: The path to wordpress' root directory
        :param potentially_vulnerable_paths: A list with the paths I'll URL-join
                                             with @domain_path, GET and parse.
        '''
        for pvuln_path in potentially_vulnerable_paths:

            pvuln_url = domain_path.url_join(pvuln_path)
            response = self._uri_opener.GET(pvuln_url, cache=True)

            if is_404(response):
                continue

            response_body = response.get_body()
            if 'Fatal error: ' in response_body:
                desc = 'Analyze the HTTP response body to find the full path'\
                       ' where wordpress was installed.'
                i = Info('WordPress path disclosure', desc, response.id,
                         self.get_name())
                i.set_url(pvuln_url)

                kb.kb.append(self, 'info', i)
                om.out.information(i.get_desc())
                break
Exemple #8
0
    def _do_request(self, url, mutant):
        '''
        Perform a simple GET to see if the result is an error or not, and then
        run the actual fuzzing.
        '''
        response = self._uri_opener.GET(
            mutant, cache=True, headers=self._headers)

        if not (is_404(response) or
        response.get_code() in (403, 401) or
        self._return_without_eval(mutant)):
            
            for fr in self._create_fuzzable_requests(response):
                self.output_queue.put(fr)
            
            #
            #   Save it to the kb (if new)!
            #
            if response.get_url() not in self._seen and response.get_url().get_file_name():
                desc = 'A potentially interesting file was found at: "%s".'
                desc = desc % response.get_url()

                i = Info('Potentially interesting file', desc, response.id,
                         self.get_name())
                i.set_url(response.get_url())
                
                kb.kb.append(self, 'files', i)
                om.out.information(i.get_desc())

                # Report only once
                self._seen.add(response.get_url())
Exemple #9
0
    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()))
Exemple #10
0
    def discover(self, fuzzable_request):
        '''
        :param fuzzable_request: A fuzzable_request instance that contains
                                    (among other things) the URL to test.
        '''
        root_domain = fuzzable_request.get_url().get_root_domain()

        pks_se = pks(self._uri_opener)
        results = pks_se.search(root_domain)
        pks_url = 'http://pgp.mit.edu:11371/'

        for result in results:
            mail = result.username + '@' + root_domain

            desc = 'The mail account: "%s" was found at: "%s".'
            desc = desc % (mail, pks_url)

            i = Info('Email account', desc, result.id, self.get_name())
            i.set_url(URL(pks_url))
            i['mail'] = mail
            i['user'] = result.username
            i['name'] = result.name
            i['url_list'] = [
                URL(pks_url),
            ]

            kb.kb.append('emails', 'emails', i)
            #   Don't save duplicated information in the KB. It's useless.
            #kb.kb.append( self, 'emails', i )
            om.out.information(i.get_desc())
Exemple #11
0
    def _analyze_results(self, filtered, not_filtered):
        '''
        Analyze the test results and save the conclusion to the kb.
        '''
        if len(filtered) >= len(self._get_offending_strings()) / 5.0:
            desc = 'The remote network has an active filter. IMPORTANT: The'\
                   ' result of all the other plugins will be unaccurate, web'\
                   ' applications could be vulnerable but "protected" by the'\
                   ' active filter.'

            i = Info('Active filter detected', desc, 1, self.get_name())
            i['filtered'] = filtered

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

            om.out.information('The following URLs were filtered:')
            for i in filtered:
                om.out.information('- ' + i)

            if not_filtered:
                om.out.information(
                    'The following URLs passed undetected by the filter:')
                for i in not_filtered:
                    om.out.information('- ' + i)
    def _fingerprint_data(self, domain_path, wp_unique_url, response):
        '''
        Find wordpress version from data
        '''
        for wp_fingerprint in self._get_wp_fingerprints():
            
            # The URL in the XML is relative AND it has two different variables
            # that we need to replace:
            #        $wp-content$    -> wp-content/
            #        $wp-plugins$    -> wp-content/plugins/
            path = wp_fingerprint.filepath
            path = path.replace('$wp-content$', 'wp-content/')
            path = path.replace('$wp-plugins$', 'wp-content/plugins/')
            test_url = domain_path.url_join(path)
            
            response = self._uri_opener.GET(test_url, cache=True)

            response_hash = hashlib.md5(response.get_body()).hexdigest()

            if response_hash == wp_fingerprint.hash:
                version = wp_fingerprint.version

                # Save it to the kb!
                desc = 'WordPress version "%s" fingerprinted by matching known md5'\
                       ' hashes to HTTP responses of static resources available at'\
                       ' the remote WordPress install.'
                desc = desc % version
                i = Info('Fingerprinted Wordpress version', desc, response.id,
                         self.get_name())
                i.set_url(test_url)
        
                kb.kb.append(self, 'info', i)
                om.out.information(i.get_desc())
                
                break
    def _fingerprint_meta(self, domain_path, wp_unique_url, response):
        '''
        Check if the wp version is in index header
        '''
        # Main scan URL passed from w3af + wp index page
        wp_index_url = domain_path.url_join('index.php')
        response = self._uri_opener.GET(wp_index_url, cache=True)

        # Find the string in the response html
        find = '<meta name="generator" content="[Ww]ord[Pp]ress (\d\.\d\.?\d?)" />'
        m = re.search(find, response.get_body())

        # If string found, group version
        if m:
            version = m.group(1)

            # Save it to the kb!
            desc = 'WordPress version "%s" found in the index header.'
            desc = desc % version

            i = Info('Fingerprinted Wordpress version', desc, response.id,
                     self.get_name())
            i.set_url(wp_index_url)
            
            kb.kb.append(self, 'info', i)
            om.out.information(i.get_desc())
Exemple #14
0
    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()))
Exemple #15
0
    def discover(self, fuzzable_request):
        '''
        :param fuzzable_request: A fuzzable_request instance that contains
                                    (among other things) the URL to test.
        '''
        root_domain = fuzzable_request.get_url().get_root_domain()

        pks_se = pks(self._uri_opener)
        results = pks_se.search(root_domain)
        pks_url = 'http://pgp.mit.edu:11371/'

        for result in results:
            mail = result.username + '@' + root_domain
            
            desc = 'The mail account: "%s" was found at: "%s".'
            desc = desc % (mail, pks_url)

            i = Info('Email account', desc, result.id, self.get_name())
            i.set_url(URL(pks_url))
            i['mail'] = mail
            i['user'] = result.username
            i['name'] = result.name
            i['url_list'] = set([URL(pks_url), ])
            
            kb.kb.append('emails', 'emails', i)
            #   Don't save duplicated information in the KB. It's useless.
            #kb.kb.append( self, 'emails', i )
            om.out.information(i.get_desc())
Exemple #16
0
    def _analyze_results(self, filtered, not_filtered):
        '''
        Analyze the test results and save the conclusion to the kb.
        '''
        if len(filtered) >= len(self._get_offending_strings()) / 5.0:
            desc = 'The remote network has an active filter. IMPORTANT: The'\
                   ' result of all the other plugins will be unaccurate, web'\
                   ' applications could be vulnerable but "protected" by the'\
                   ' active filter.'
                   
            i = Info('Active filter detected', desc, 1, self.get_name())
            i['filtered'] = filtered
            
            kb.kb.append(self, 'afd', i)
            om.out.information(i.get_desc())

            om.out.information('The following URLs were filtered:')
            for i in filtered:
                om.out.information('- ' + i)

            if not_filtered:
                om.out.information(
                    'The following URLs passed undetected by the filter:')
                for i in not_filtered:
                    om.out.information('- ' + i)
    def _force_disclosures(self, domain_path, potentially_vulnerable_paths):
        '''
        :param domain_path: The path to wordpress' root directory
        :param potentially_vulnerable_paths: A list with the paths I'll URL-join
                                             with @domain_path, GET and parse.
        '''
        for pvuln_path in potentially_vulnerable_paths:

            pvuln_url = domain_path.url_join(pvuln_path)
            response = self._uri_opener.GET(pvuln_url, cache=True)

            if is_404(response):
                continue

            response_body = response.get_body()
            if 'Fatal error: ' in response_body:
                desc = 'Analyze the HTTP response body to find the full path'\
                       ' where wordpress was installed.'
                i = Info('WordPress path disclosure', desc, response.id,
                         self.get_name())
                i.set_url(pvuln_url)
                
                kb.kb.append(self, 'info', i)
                om.out.information(i.get_desc())
                break
Exemple #18
0
    def discover(self, fuzzable_request):
        '''
        Identify server software using favicon.

        :param fuzzable_request: A fuzzable_request instance that contains
                                (among other things) the URL to test.
        '''
        domain_path = fuzzable_request.get_url().get_domain_path()

        # TODO: Maybe I should also parse the html to extract the favicon location?
        favicon_url = domain_path.url_join('favicon.ico')
        response = self._uri_opener.GET(favicon_url, cache=True)
        remote_fav_md5 = hashlib.md5(response.get_body()).hexdigest()

        if not is_404(response):

            # check if MD5 is matched in database/list
            for md5part, favicon_desc in self._read_favicon_db():

                if md5part == remote_fav_md5:
                    desc = 'Favicon.ico file was identified as "%s".' % favicon_desc
                    i = Info('Favicon identification', desc, response.id,
                             self.get_name())
                    i.set_url(favicon_url)

                    kb.kb.append(self, 'info', i)
                    om.out.information(i.get_desc())
                    break
            else:
                #
                #   Report to the kb that we failed to ID this favicon.ico
                #   and that the md5 should be sent to the developers.
                #
                desc = 'Favicon identification failed. If the remote site is'  \
                       ' using framework that is being exposed by its favicon,'\
                       ' please send an email to [email protected]'\
                       ' including this md5 hash "%s" and the' \
                       ' name of the server or Web application it represents.' \
                       ' New fingerprints make this plugin more powerful and ' \
                       ' accurate.'
                desc = desc % remote_fav_md5
                i = Info('Favicon identification failed', desc, response.id,
                         self.get_name())
                i.set_url(favicon_url)

                kb.kb.append(self, 'info', i)
                om.out.information(i.get_desc())
    def discover(self, fuzzable_request):
        '''
        Identify server software using favicon.

        :param fuzzable_request: A fuzzable_request instance that contains
                                (among other things) the URL to test.
        '''
        domain_path = fuzzable_request.get_url().get_domain_path()

        # TODO: Maybe I should also parse the html to extract the favicon location?
        favicon_url = domain_path.url_join('favicon.ico')
        response = self._uri_opener.GET(favicon_url, cache=True)
        remote_fav_md5 = hashlib.md5(response.get_body()).hexdigest()

        if not is_404(response):

            # check if MD5 is matched in database/list
            for md5part, favicon_desc in self._read_favicon_db():

                if md5part == remote_fav_md5:
                    desc = 'Favicon.ico file was identified as "%s".' % favicon_desc
                    i = Info('Favicon identification', desc, response.id,
                             self.get_name())
                    i.set_url(favicon_url)
                    
                    kb.kb.append(self, 'info', i)
                    om.out.information(i.get_desc())
                    break
            else:
                #
                #   Report to the kb that we failed to ID this favicon.ico
                #   and that the md5 should be sent to the developers.
                #
                desc = 'Favicon identification failed. If the remote site is'  \
                       ' using framework that is being exposed by its favicon,'\
                       ' please send an email to [email protected]'\
                       ' including this md5 hash "%s" and the' \
                       ' name of the server or Web application it represents.' \
                       ' New fingerprints make this plugin more powerful and ' \
                       ' accurate.'
                desc = desc % remote_fav_md5
                i = Info('Favicon identification failed', desc, response.id,
                         self.get_name())
                i.set_url(favicon_url)

                kb.kb.append(self, 'info', i)
                om.out.information(i.get_desc())
Exemple #20
0
    def _extract_version_from_egg(self, query_results):
        """
        Analyzes the eggs and tries to deduce a PHP version number
        ( which is then saved to the kb ).
        """
        if not query_results:
            return None
        else:
            cmp_list = []
            for query_result in query_results:
                body = query_result.http_response.get_body()
                if isinstance(body, unicode):
                    body = body.encode("utf-8")
                hash_str = hashlib.md5(body).hexdigest()

                cmp_list.append((hash_str, query_result.egg_desc))

            cmp_set = set(cmp_list)

            found = False
            matching_versions = []
            for version in self.EGG_DB:
                version_hashes = set(self.EGG_DB[version])

                if len(cmp_set) == len(cmp_set.intersection(version_hashes)):
                    matching_versions.append(version)
                    found = True

            if matching_versions:
                desc = "The PHP framework version running on the remote" " server was identified as:\n- %s"
                versions = "\n- ".join(matching_versions)
                desc = desc % versions

                response_ids = [r.http_response.get_id() for r in query_results]

                i = Info("Fingerprinted PHP version", desc, response_ids, self.get_name())
                i["version"] = matching_versions

                kb.kb.append(self, "version", i)
                om.out.information(i.get_desc())

            if not found:
                version = "unknown"
                powered_by_headers = kb.kb.raw_read("server_header", "powered_by_string")
                try:
                    for v in powered_by_headers:
                        if "php" in v.lower():
                            version = v.split("/")[1]
                except:
                    pass

                msg = (
                    "The PHP version could not be identified using PHP eggs,"
                    ", please send this signature and the PHP version to the"
                    " w3af project develop mailing list. Signature:"
                    " EGG_DB['%s'] = %s\n"
                )
                msg = msg % (version, str(list(cmp_set)))
                om.out.information(msg)
Exemple #21
0
    def _extract_version_from_egg(self, query_results):
        '''
        Analyzes the eggs and tries to deduce a PHP version number
        ( which is then saved to the kb ).
        '''
        if not query_results:
            return None
        else:
            cmp_list = []
            for query_result in query_results:
                body = query_result.http_response.get_body()
                if isinstance(body, unicode): body = body.encode('utf-8')
                hash_str = hashlib.md5(body).hexdigest()
                
                cmp_list.append((hash_str, query_result.egg_desc))
                
            cmp_set = set(cmp_list)

            found = False
            matching_versions = []
            for version in self.EGG_DB:
                version_hashes = set(self.EGG_DB[version])

                if len(cmp_set) == len(cmp_set.intersection(version_hashes)):
                    matching_versions.append(version)
                    found = True

            if matching_versions:
                desc = 'The PHP framework version running on the remote'\
                       ' server was identified as:\n- %s'
                versions = '\n- '.join(matching_versions)
                desc = desc % versions
                
                response_ids = [r.http_response.get_id() for r in query_results]
                
                i = Info('Fingerprinted PHP version', desc, response_ids,
                         self.get_name())
                i['version'] = matching_versions
                
                kb.kb.append(self, 'version', i)
                om.out.information(i.get_desc())

            if not found:
                version = 'unknown'
                powered_by_headers = kb.kb.raw_read('server_header',
                                                    'powered_by_string')
                try:
                    for v in powered_by_headers:
                        if 'php' in v.lower():
                            version = v.split('/')[1]
                except:
                    pass
                
                msg = 'The PHP version could not be identified using PHP eggs,'\
                      ', please send this signature and the PHP version to the'\
                      ' w3af project develop mailing list. Signature:'\
                      ' EGG_DB[\'%s\'] = %s\n'
                msg = msg % (version, str(list(cmp_set)))
                om.out.information(msg)
Exemple #22
0
    def _check_server_header(self, fuzzable_request):
        '''
        HTTP GET and analyze response for server header
        '''
        response = self._uri_opener.GET(fuzzable_request.get_url(), cache=True)

        for hname, hvalue in response.get_lower_case_headers().iteritems():
            if hname == 'server':
                server = hvalue

                desc = 'The server header for the remote web server is: "%s".'
                desc = desc % server

                i = Info('Server header', desc, response.id, self.get_name())
                i['server'] = server
                i.add_to_highlight(hname + ':')

                om.out.information(i.get_desc())

                # Save the results in the KB so the user can look at it
                kb.kb.append(self, 'server', i)

                # Also save this for easy internal use
                # other plugins can use this information
                kb.kb.raw_write(self, 'server_string', server)
                break

        else:
            # strange !
            desc = 'The remote HTTP Server omitted the "server" header in'\
                  ' its response.'
            i = Info('Omitted server header', desc, response.id,
                     self.get_name())

            om.out.information(i.get_desc())

            # Save the results in the KB so that other plugins can use this
            # information
            kb.kb.append(self, 'ommited_server_header', i)

            # Also save this for easy internal use
            # other plugins can use this information
            kb.kb.raw_write(self, 'server_string', '')
Exemple #23
0
    def _check_server_header(self, fuzzable_request):
        '''
        HTTP GET and analyze response for server header
        '''
        response = self._uri_opener.GET(fuzzable_request.get_url(), cache=True)

        for hname, hvalue in response.get_lower_case_headers().iteritems():
            if hname == 'server':
                server = hvalue
                
                desc = 'The server header for the remote web server is: "%s".'
                desc = desc % server
                
                i = Info('Server header', desc, response.id, self.get_name())
                i['server'] = server
                i.add_to_highlight(hname + ':')
                
                om.out.information(i.get_desc())

                # Save the results in the KB so the user can look at it
                kb.kb.append(self, 'server', i)

                # Also save this for easy internal use
                # other plugins can use this information
                kb.kb.raw_write(self, 'server_string', server)
                break

        else:
            # strange !
            desc = 'The remote HTTP Server omitted the "server" header in'\
                  ' its response.'
            i = Info('Omitted server header', desc, response.id,
                     self.get_name())

            om.out.information(i.get_desc())

            # Save the results in the KB so that other plugins can use this
            # information
            kb.kb.append(self, 'ommited_server_header', i)

            # Also save this for easy internal use
            # other plugins can use this information
            kb.kb.raw_write(self, 'server_string', '')
Exemple #24
0
    def _report_no_realm(self, response):
        # Report this strange case
        desc = 'The resource: "%s" requires authentication (HTTP Code'\
               ' 401) but the www-authenticate header is not present.'\
               ' This requires human verification.'
        desc = desc % response.get_url() 
        i = Info('Authentication without www-authenticate header', desc,
                 response.id, self.get_name())
        i.set_url(response.get_url())

        kb.kb.append(self, 'non_rfc_auth', i)
        om.out.information(i.get_desc())
    def _report_finding(self, response):
        '''
        Save the finding to the kb.

        :param response: The response that triggered the detection
        '''
        desc = 'The remote web server seems to have a reverse proxy installed.'

        i = Info('Reverse proxy identified', desc, response.id, self.get_name())
        i.set_url(response.get_url())

        kb.kb.append(self, 'detect_reverse_proxy', i)
        om.out.information(i.get_desc())
    def _kb_info_user(self, url, response_id, username):
        '''
        Put user in Kb
        :return: None, everything is saved in kb
        '''
        desc = 'WordPress user "%s" found during username enumeration.'
        desc = desc % username

        i = Info('Identified WordPress user', desc, response_id,
                 self.get_name())
        i.set_url(url)

        kb.kb.append(self, 'users', i)
        om.out.information(i.get_desc())
 def _kb_info_user(self, url, response_id, username):
     '''
     Put user in Kb
     :return: None, everything is saved in kb
     '''
     desc = 'WordPress user "%s" found during username enumeration.'
     desc = desc % username
     
     i = Info('Identified WordPress user', desc, response_id,
              self.get_name())
     i.set_url(url)
     
     kb.kb.append(self, 'users', i)
     om.out.information(i.get_desc())
Exemple #28
0
 def _analyze_gears_manifest(self, url, response, file_name):
     if '"entries":' in response:
         # Save it to the kb!
         desc = 'A gears manifest file was found at: "%s".'\
                ' Each file should be manually reviewed for sensitive'\
                ' information that may get cached on the client.'
         desc = desc % url
         
         i = Info('Gears manifest resource', desc, response.id,
                  self.get_name())
         i.set_url(url)
         
         kb.kb.append(self, url, i)
         om.out.information(i.get_desc())
Exemple #29
0
    def _report_finding(self, response):
        '''
        Save the finding to the kb.

        :param response: The response that triggered the detection
        '''
        desc = 'The remote web server seems to have a reverse proxy installed.'

        i = Info('Reverse proxy identified', desc, response.id,
                 self.get_name())
        i.set_url(response.get_url())

        kb.kb.append(self, 'detect_reverse_proxy', i)
        om.out.information(i.get_desc())
Exemple #30
0
    def _analyze_gears_manifest(self, url, response, file_name):
        if '"entries":' in response:
            # Save it to the kb!
            desc = 'A gears manifest file was found at: "%s".'\
                   ' Each file should be manually reviewed for sensitive'\
                   ' information that may get cached on the client.'
            desc = desc % url

            i = Info('Gears manifest resource', desc, response.id,
                     self.get_name())
            i.set_url(url)

            kb.kb.append(self, url, i)
            om.out.information(i.get_desc())
Exemple #31
0
    def _parse_zone_h_result(self, response):
        '''
        Parse the result from the zone_h site and create the corresponding info
        objects.

        :return: None
        '''
        #
        #   I'm going to do only one big "if":
        #
        #       - The target site was hacked more than one time
        #       - The target site was hacked only one time
        #

        # This is the string I have to parse:
        # in the zone_h response, they are two like this, the first has to be ignored!
        regex = 'Total notifications: <b>(\d*)</b> of which <b>(\d*)</b> single ip and <b>(\d*)</b> mass'
        regex_result = re.findall(regex, response.get_body())

        try:
            total_attacks = int(regex_result[0][0])
        except IndexError:
            om.out.debug(
                'An error was generated during the parsing of the zone_h website.'
            )
        else:

            # Do the if...
            if total_attacks > 1:
                desc = 'The target site was defaced more than one time in the'\
                       ' past. For more information please visit the following'\
                       ' URL: "%s".' % response.get_url()

                v = Vuln('Previous defacements', desc, severity.MEDIUM,
                         response.id, self.get_name())
                v.set_url(response.get_url())

                kb.kb.append(self, 'defacements', v)
                om.out.information(v.get_desc())
            elif total_attacks == 1:
                desc = 'The target site was defaced in the past. For more'\
                       ' information please visit the following URL: "%s".'
                desc = desc % response.get_url()
                i = Info('Previous defacements', desc, response.id,
                         self.get_name())
                i.set_url(response.get_url())

                kb.kb.append(self, 'defacements', i)
                om.out.information(i.get_desc())
Exemple #32
0
    def crawl(self, fuzzable_request):
        '''
        Get the robots.txt file and parse it.

        :param fuzzable_request: A fuzzable_request instance that contains
                                (among other things) the URL to test.
        '''
        dirs = []

        base_url = fuzzable_request.get_url().base_url()
        robots_url = base_url.url_join('robots.txt')
        http_response = self._uri_opener.GET(robots_url, cache=True)

        if not is_404(http_response):
            # Save it to the kb!
            desc = 'A robots.txt file was found at: "%s", this file might'\
                   ' expose private URLs and requires a manual review. The'\
                   ' scanner will add all URLs listed in this files to the'\
                   ' analysis queue.'
            desc =  desc % robots_url
            
            i = Info('robots.txt file', desc,
                     http_response.id, self.get_name())
            i.set_url(robots_url)
            
            kb.kb.append(self, 'robots.txt', i)
            om.out.information(i.get_desc())

            # Work with it...
            dirs.append(robots_url)
            for line in http_response.get_body().split('\n'):

                line = line.strip()

                if len(line) > 0 and line[0] != '#' and \
                    (line.upper().find('ALLOW') == 0 or
                     line.upper().find('DISALLOW') == 0):

                    url = line[line.find(':') + 1:]
                    url = url.strip()
                    try:
                        url = base_url.url_join(url)
                    except:
                        # Simply ignore the invalid URL
                        pass
                    else:
                        dirs.append(url)

        self.worker_pool.map(self.http_get_and_parse, dirs)
Exemple #33
0
    def _test_DNS(self, original_response, dns_wildcard_url):
        '''
        Check if http://www.domain.tld/ == http://domain.tld/
        '''
        headers = Headers([('Host', dns_wildcard_url.get_domain())])
        try:
            modified_response = self._uri_opener.GET(
                original_response.get_url(),
                cache=True,
                headers=headers)
        except w3afException:
            return
        else:
            if relative_distance_lt(modified_response.get_body(),
                                    original_response.get_body(), 0.35):
                desc = 'The target site has NO DNS wildcard, and the contents' \
                       ' of "%s" differ from the contents of "%s".'
                desc = desc % (dns_wildcard_url, original_response.get_url())
                
                i = Info('No DNS wildcard', desc, modified_response.id,
                         self.get_name())
                i.set_url(dns_wildcard_url)

                kb.kb.append(self, 'dns_wildcard', i)
                om.out.information(i.get_desc())
            else:
                desc = 'The target site has a DNS wildcard configuration, the'\
                      ' contents of "%s" are equal to the ones of "%s".'
                desc = desc % (dns_wildcard_url, original_response.get_url())
                
                i = Info('DNS wildcard', desc, modified_response.id,
                         self.get_name())
                i.set_url(original_response.get_url())

                kb.kb.append(self, 'dns_wildcard', i)
                om.out.information(i.get_desc())
Exemple #34
0
    def _fingerprint_installer(self, domain_path, wp_unique_url, response):
        '''
        GET latest.zip and latest.tar.gz and compare with the hashes from the
        release.db that was previously generated from wordpress.org [0]
        and contains all release hashes.

        This gives the initial wordpress version, not the current one.

        [0] http://wordpress.org/download/release-archive/
        '''
        zip_url = domain_path.url_join('latest.zip')
        tar_gz_url = domain_path.url_join('latest.tar.gz')
        install_urls = [zip_url, tar_gz_url]

        for install_url in install_urls:
            response = self._uri_opener.GET(install_url,
                                            cache=True,
                                            respect_size_limit=False)

            # md5sum the response body
            m = hashlib.md5()
            m.update(response.get_body())
            remote_release_hash = m.hexdigest()

            release_db = self._release_db

            for line in file(release_db):
                try:
                    line = line.strip()
                    release_db_hash, release_db_name = line.split(',')
                except:
                    continue

                if release_db_hash == remote_release_hash:

                    desc = 'The sysadmin used WordPress version "%s" during the'\
                           ' installation, which was found by matching the contents'\
                           ' of "%s" with the hashes of known releases. If the'\
                           ' sysadmin did not update wordpress, the current version'\
                           ' will still be the same.'
                    desc = desc % (release_db_name, install_url)

                    i = Info('Fingerprinted Wordpress version', desc,
                             response.id, self.get_name())
                    i.set_url(install_url)

                    kb.kb.append(self, 'info', i)
                    om.out.information(i.get_desc())
 def discover(self, fuzzable_request):
     '''
     :param fuzzable_request: A fuzzable_request instance that contains
                                 (among other things) the URL to test.
     '''
     if self._is_proxyed_conn(fuzzable_request):
         desc = 'Your ISP seems to have a transparent proxy installed,'\
                ' this can influence scan results in unexpected ways.'
        
         i = Info('Transparent proxy detected', desc, 1, self.get_name())
         i.set_url(fuzzable_request.get_url())
         
         kb.kb.append(self, 'detect_transparent_proxy', i)
         om.out.information(i.get_desc())
     else:
         om.out.information('Your ISP has no transparent proxy.')
    def discover(self, fuzzable_request):
        '''
        :param fuzzable_request: A fuzzable_request instance that contains
                                    (among other things) the URL to test.
        '''
        if self._is_proxyed_conn(fuzzable_request):
            desc = 'Your ISP seems to have a transparent proxy installed,'\
                   ' this can influence scan results in unexpected ways.'

            i = Info('Transparent proxy detected', desc, 1, self.get_name())
            i.set_url(fuzzable_request.get_url())

            kb.kb.append(self, 'detect_transparent_proxy', i)
            om.out.information(i.get_desc())
        else:
            om.out.information('Your ISP has no transparent proxy.')
    def _fingerprint_installer(self, domain_path, wp_unique_url, response):
        '''
        GET latest.zip and latest.tar.gz and compare with the hashes from the
        release.db that was previously generated from wordpress.org [0]
        and contains all release hashes.

        This gives the initial wordpress version, not the current one.

        [0] http://wordpress.org/download/release-archive/
        '''
        zip_url = domain_path.url_join('latest.zip')
        tar_gz_url = domain_path.url_join('latest.tar.gz')
        install_urls = [zip_url, tar_gz_url]

        for install_url in install_urls:
            response = self._uri_opener.GET(install_url, cache=True,
                                            respect_size_limit=False)

            # md5sum the response body
            m = hashlib.md5()
            m.update(response.get_body())
            remote_release_hash = m.hexdigest()

            release_db = self._release_db

            for line in file(release_db):
                try:
                    line = line.strip()
                    release_db_hash, release_db_name = line.split(',')
                except:
                    continue

                if release_db_hash == remote_release_hash:

                    desc = 'The sysadmin used WordPress version "%s" during the'\
                           ' installation, which was found by matching the contents'\
                           ' of "%s" with the hashes of known releases. If the'\
                           ' sysadmin did not update wordpress, the current version'\
                           ' will still be the same.'
                    desc = desc % (release_db_name, install_url)

                    i = Info('Fingerprinted Wordpress version', desc, response.id,
                             self.get_name())
                    i.set_url(install_url)
                    
                    kb.kb.append(self, 'info', i)
                    om.out.information(i.get_desc())
Exemple #38
0
    def _parse_zone_h_result(self, response):
        '''
        Parse the result from the zone_h site and create the corresponding info
        objects.

        :return: None
        '''
        #
        #   I'm going to do only one big "if":
        #
        #       - The target site was hacked more than one time
        #       - The target site was hacked only one time
        #

        # This is the string I have to parse:
        # in the zone_h response, they are two like this, the first has to be ignored!
        regex = 'Total notifications: <b>(\d*)</b> of which <b>(\d*)</b> single ip and <b>(\d*)</b> mass'
        regex_result = re.findall(regex, response.get_body())

        try:
            total_attacks = int(regex_result[0][0])
        except IndexError:
            om.out.debug('An error was generated during the parsing of the zone_h website.')
        else:

            # Do the if...
            if total_attacks > 1:
                desc = 'The target site was defaced more than one time in the'\
                       ' past. For more information please visit the following'\
                       ' URL: "%s".' % response.get_url()
                       
                v = Vuln('Previous defacements', desc,
                         severity.MEDIUM, response.id, self.get_name())
                v.set_url(response.get_url())
                
                kb.kb.append(self, 'defacements', v)
                om.out.information(v.get_desc())
            elif total_attacks == 1:
                desc = 'The target site was defaced in the past. For more'\
                       ' information please visit the following URL: "%s".'
                desc = desc % response.get_url()
                i = Info('Previous defacements', desc, response.id,
                         self.get_name())
                i.set_url(response.get_url())

                kb.kb.append(self, 'defacements', i)
                om.out.information(i.get_desc())
Exemple #39
0
    def _check_x_power(self, fuzzable_request):
        '''
        Analyze X-Powered-By header.
        '''
        response = self._uri_opener.GET(fuzzable_request.get_url(), cache=True)

        for header_name in response.get_headers().keys():
            for i in ['ASPNET', 'POWERED']:
                if i in header_name.upper() or header_name.upper() in i:
                    powered_by = response.get_headers()[header_name]

                    # Only get the first one
                    self._x_powered = False

                    #
                    #    Check if I already have this info in the KB
                    #
                    pow_by_kb = kb.kb.get('server_header', 'powered_by')
                    powered_by_in_kb = [j['powered_by'] for j in pow_by_kb]
                    if powered_by not in powered_by_in_kb:

                        #
                        #    I don't have it in the KB, so I need to add it,
                        #
                        desc = 'The %s header for the target HTTP server is "%s".'
                        desc = desc % (header_name, powered_by)
                        
                        i = Info('Powered-by header', desc, response.id,
                                 self.get_name())
                        i['powered_by'] = powered_by
                        i.add_to_highlight(header_name + ':')
                        
                        om.out.information(i.get_desc())

                        # Save the results in the KB so that other plugins can
                        # use this information. Before knowing that some servers
                        # may return more than one poweredby header I had:
                        #     kb.kb.raw_write( self , 'powered_by' , powered_by )
                        # But I have seen an IIS server with PHP that returns
                        # both the ASP.NET and the PHP headers
                        kb.kb.append(self, 'powered_by', i)
                        
                        # Update the list and save it,
                        powered_by_in_kb.append(powered_by)
                        kb.kb.raw_write(self, 'powered_by_string',
                                        powered_by_in_kb)
Exemple #40
0
    def _check_x_power(self, fuzzable_request):
        '''
        Analyze X-Powered-By header.
        '''
        response = self._uri_opener.GET(fuzzable_request.get_url(), cache=True)

        for header_name in response.get_headers().keys():
            for i in ['ASPNET', 'POWERED']:
                if i in header_name.upper() or header_name.upper() in i:
                    powered_by = response.get_headers()[header_name]

                    # Only get the first one
                    self._x_powered = False

                    #
                    #    Check if I already have this info in the KB
                    #
                    pow_by_kb = kb.kb.get('server_header', 'powered_by')
                    powered_by_in_kb = [j['powered_by'] for j in pow_by_kb]
                    if powered_by not in powered_by_in_kb:

                        #
                        #    I don't have it in the KB, so I need to add it,
                        #
                        desc = 'The %s header for the target HTTP server is "%s".'
                        desc = desc % (header_name, powered_by)

                        i = Info('Powered-by header', desc, response.id,
                                 self.get_name())
                        i['powered_by'] = powered_by
                        i.add_to_highlight(header_name + ':')

                        om.out.information(i.get_desc())

                        # Save the results in the KB so that other plugins can
                        # use this information. Before knowing that some servers
                        # may return more than one poweredby header I had:
                        #     kb.kb.raw_write( self , 'powered_by' , powered_by )
                        # But I have seen an IIS server with PHP that returns
                        # both the ASP.NET and the PHP headers
                        kb.kb.append(self, 'powered_by', i)

                        # Update the list and save it,
                        powered_by_in_kb.append(powered_by)
                        kb.kb.raw_write(self, 'powered_by_string',
                                        powered_by_in_kb)
Exemple #41
0
    def _analyze_response(self, response):
        '''
        It seems that we have found a _vti_inf file, parse it and analyze the content!

        :param response: The http response object for the _vti_inf file.
        :return: None. All the info is saved to the kb.
        '''
        version_mo = self.VERSION_RE.search(response.get_body())
        admin_mo = self.ADMIN_URL_RE.search(response.get_body())
        author_mo = self.AUTHOR_URL_RE.search(response.get_body())

        if version_mo and admin_mo and author_mo:
            #Set the self._exec to false
            self._exec = False

            desc = 'The FrontPage Configuration Information file was found'\
                   ' at: "%s" and the version of FrontPage Server Extensions'\
                   ' is: "%s".'
            desc = desc % (response.get_url(), version_mo.group(1))

            i = Info('FrontPage configuration information', desc, response.id,
                     self.get_name())
            i.set_url(response.get_url())
            i['version'] = version_mo.group(1)
            
            kb.kb.append(self, 'frontpage_version', i)
            om.out.information(i.get_desc())

            #
            # Handle the admin.exe file
            #
            self._analyze_admin(response, admin_mo)

            #
            # Handle the author.exe file
            #
            self._analyze_author(response, author_mo)

        else:
            # This is strange... we found a _vti_inf file, but there is no frontpage
            # information in it... IPS? WAF? honeypot?
            msg = '[IMPROVEMENT] Invalid frontPage configuration information'\
                  ' found at %s (id: %s).'
            msg = msg % (response.get_url(), response.id)
            om.out.debug(msg)
Exemple #42
0
    def _extract_server_version(self, fuzzable_request, response):
        '''
        Get the server version from the HTML:
            <dl><dt>Server Version: Apache/2.2.9 (Unix)</dt>
        '''
        for version in re.findall('<dl><dt>Server Version: (.*?)</dt>',
                                  response.get_body()):
            # Save the results in the KB so the user can look at it
            desc = 'The web server has the apache server status module'\
                   ' enabled which discloses the following remote server'\
                   ' version: "%s".'
            desc = desc % version
            
            i = Info('Apache Server version', desc, response.id, self.get_name())
            i.set_url(response.get_url())

            om.out.information(i.get_desc())
            kb.kb.append(self, 'server', i)
Exemple #43
0
    def _extract_server_version(self, fuzzable_request, response):
        '''
        Get the server version from the HTML:
            <dl><dt>Server Version: Apache/2.2.9 (Unix)</dt>
        '''
        for version in re.findall('<dl><dt>Server Version: (.*?)</dt>',
                                  response.get_body()):
            # Save the results in the KB so the user can look at it
            desc = 'The web server has the apache server status module'\
                   ' enabled which discloses the following remote server'\
                   ' version: "%s".'
            desc = desc % version

            i = Info('Apache Server version', desc, response.id,
                     self.get_name())
            i.set_url(response.get_url())

            om.out.information(i.get_desc())
            kb.kb.append(self, 'server', i)
Exemple #44
0
    def _find_OS(self, fuzzable_request):
        '''
        Analyze responses and determine if remote web server runs on windows
        or *nix.

        @Return: None, the knowledge is saved in the knowledgeBase
        '''
        freq_url = fuzzable_request.get_url()
        filename = freq_url.get_file_name()
        dirs = freq_url.get_directories()[:-1]  # Skipping "domain level" dir.

        if dirs and filename:

            last_url = dirs[-1]
            last_url = last_url.url_string

            windows_url = URL(last_url[0:-1] + '\\' + filename)
            windows_response = self._uri_opener.GET(windows_url)

            original_response = self._uri_opener.GET(freq_url)

            if relative_distance_ge(original_response.get_body(),
                                    windows_response.get_body(), 0.98):
                desc = 'Fingerprinted this host as a Microsoft Windows system.'
                os_str = 'windows'
            else:
                desc = 'Fingerprinted this host as a *nix system. Detection for'\
                       ' this operating system is weak, "if not windows then'\
                       ' linux".'
                os_str = 'unix'

            response_ids = [windows_response.id, original_response.id]
            i = Info('Operating system', desc, response_ids,
                     self.get_name())
            i.set_url(windows_response.get_url())
            
            kb.kb.raw_write(self, 'operating_system_str', os_str)
            kb.kb.append(self, 'operating_system', i)
            om.out.information(i.get_desc())
            return True

        return False
Exemple #45
0
    def crawl(self, fuzzable_request):
        """
        Find CAPTCHA images.

        :param fuzzable_request: A fuzzable_request instance that contains
                                    (among other things) the URL to test.
        """
        result, captchas = self._identify_captchas(fuzzable_request)

        if result:
            for captcha in captchas:

                desc = 'Found a CAPTCHA image at: "%s".' % captcha.img_src
                response_ids = [response.id for response in captcha.http_responses]

                i = Info("Captcha image detected", desc, response_ids, self.get_name())
                i.set_uri(captcha.img_src)

                kb.kb.append(self, "CAPTCHA", i)
                om.out.information(i.get_desc())
Exemple #46
0
    def _find_OS(self, fuzzable_request):
        '''
        Analyze responses and determine if remote web server runs on windows
        or *nix.

        @Return: None, the knowledge is saved in the knowledgeBase
        '''
        freq_url = fuzzable_request.get_url()
        filename = freq_url.get_file_name()
        dirs = freq_url.get_directories()[:-1]  # Skipping "domain level" dir.

        if dirs and filename:

            last_url = dirs[-1]
            last_url = last_url.url_string

            windows_url = URL(last_url[0:-1] + '\\' + filename)
            windows_response = self._uri_opener.GET(windows_url)

            original_response = self._uri_opener.GET(freq_url)

            if relative_distance_ge(original_response.get_body(),
                                    windows_response.get_body(), 0.98):
                desc = 'Fingerprinted this host as a Microsoft Windows system.'
                os_str = 'windows'
            else:
                desc = 'Fingerprinted this host as a *nix system. Detection for'\
                       ' this operating system is weak, "if not windows then'\
                       ' linux".'
                os_str = 'unix'

            response_ids = [windows_response.id, original_response.id]
            i = Info('Operating system', desc, response_ids, self.get_name())
            i.set_url(windows_response.get_url())

            kb.kb.raw_write(self, 'operating_system_str', os_str)
            kb.kb.append(self, 'operating_system', i)
            om.out.information(i.get_desc())
            return True

        return False
Exemple #47
0
    def crawl(self, fuzzable_request):
        '''
        GET some files and parse them.

        :param fuzzable_request: A fuzzable_request instance that contains
                                    (among other things) the URL to test.
        '''
        base_url = fuzzable_request.get_url().base_url()

        for url, re_obj in self.ORACLE_DATA:

            oracle_discovery_URL = base_url.url_join(url)
            response = self._uri_opener.GET(oracle_discovery_URL, cache=True)

            if not is_404(response):

                # Extract the links and send to core
                for fr in self._create_fuzzable_requests(response):
                    self.output_queue.put(fr)
                
                # pylint: disable=E1101
                # E1101: Instance of 'str' has no 'search' member
                mo = re_obj.search(response.get_body(), re.DOTALL)

                if mo:
                    desc = '"%s" version "%s" was detected at "%s".'
                    desc = desc % (mo.group(1).title(), mo.group(2).title(),
                                   response.get_url())
                    i = Info('Oracle Application Server', desc, response.id,
                             self.get_name())
                    i.set_url(response.get_url())
                    
                    kb.kb.append(self, 'oracle_discovery', i)
                    om.out.information(i.get_desc())
                else:
                    msg = 'oracle_discovery found the URL: "%s" but failed to'\
                          ' parse it as an Oracle page. The first 50 bytes of'\
                          ' the response body is: "%s".'
                    body_start = response.get_body()[:50]
                    om.out.debug(msg % (response.get_url(), body_start))
Exemple #48
0
    def _are_php_eggs(self, query_results):
        """
        Now I analyze if this is really a PHP eggs thing, or simply a response that
        changes a lot on each request. Before, I had something like this:

            if relative_distance(original_response.get_body(), response.get_body()) < 0.1:

        But I got some reports about false positives with this approach, so now I'm
        changing it to something a little bit more specific.
        """
        images = 0
        not_images = 0
        for query_result in query_results:
            if "image" in query_result.http_response.content_type:
                images += 1
            else:
                not_images += 1

        if images == 3 and not_images == 1:
            #
            #   The remote web server has expose_php = On. Report all the findings.
            #
            for query_result in query_results:
                desc = (
                    "The PHP framework running on the remote server has a"
                    ' "%s" easter egg, access to the PHP egg is possible'
                    ' through the URL: "%s".'
                )
                desc = desc % (query_result.egg_desc, query_result.egg_URL)

                i = Info("PHP Egg", desc, query_result.http_response.id, self.get_name())
                i.set_url(query_result.egg_URL)

                kb.kb.append(self, "eggs", i)
                om.out.information(i.get_desc())

            return True

        return False
Exemple #49
0
    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()))
Exemple #50
0
    def _are_php_eggs(self, query_results):
        '''
        Now I analyze if this is really a PHP eggs thing, or simply a response that
        changes a lot on each request. Before, I had something like this:

            if relative_distance(original_response.get_body(), response.get_body()) < 0.1:

        But I got some reports about false positives with this approach, so now I'm
        changing it to something a little bit more specific.
        '''
        images = 0
        not_images = 0
        for query_result in query_results:
            if 'image' in query_result.http_response.content_type:
                images += 1
            else:
                not_images += 1

        if images == 3 and not_images == 1:
            #
            #   The remote web server has expose_php = On. Report all the findings.
            #
            for query_result in query_results:
                desc = 'The PHP framework running on the remote server has a'\
                       ' "%s" easter egg, access to the PHP egg is possible'\
                       ' through the URL: "%s".'
                desc = desc % (query_result.egg_desc, query_result.egg_URL)

                i = Info('PHP Egg', desc, query_result.http_response.id,
                         self.get_name())
                i.set_url(query_result.egg_URL)

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

            return True

        return False
Exemple #51
0
    def _report_finding(self, name, response, protected_by=None):
        '''
        Creates a information object based on the name and the response parameter
        and saves the data in the kb.

        :param name: The name of the WAF
        :param response: The HTTP response object that was used to identify the WAF
        :param protected_by: A more detailed description/version of the WAF
        '''
        desc = 'The remote network seems to have a "%s" WAF deployed to' \
              ' protect access to the web server.'
        desc = desc % name

        if protected_by:
            desc += ' The following is the WAF\'s version: "%s".'

        i = Info('Web Application Firewall fingerprint', desc, response.id,
                 self.get_name())
        i.set_url(response.get_url())
        i.set_id(response.id)

        kb.kb.append(self, name, i)
        om.out.information(i.get_desc())
Exemple #52
0
    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()))
Exemple #53
0
    def crawl(self, fuzzable_request):
        '''
        Find CAPTCHA images.

        :param fuzzable_request: A fuzzable_request instance that contains
                                    (among other things) the URL to test.
        '''
        result, captchas = self._identify_captchas(fuzzable_request)

        if result:
            for captcha in captchas:

                desc = 'Found a CAPTCHA image at: "%s".' % captcha.img_src
                response_ids = [
                    response.id for response in captcha.http_responses
                ]

                i = Info('Captcha image detected', desc, response_ids,
                         self.get_name())
                i.set_uri(captcha.img_src)

                kb.kb.append(self, 'CAPTCHA', i)
                om.out.information(i.get_desc())
Exemple #54
0
    def _fingerprint_readme(self, domain_path, wp_unique_url, response):
        '''
        GET the readme.html file and extract the version information from there.
        '''
        wp_readme_url = domain_path.url_join('readme.html')
        response = self._uri_opener.GET(wp_readme_url, cache=True)

        # Find the string in the response html
        find = '<br /> Version (\d\.\d\.?\d?)'
        m = re.search(find, response.get_body())

        # If string found, group version
        if m:
            version = m.group(1)

            desc = 'WordPress version "%s" found in the readme.html file.'
            desc = desc % version

            i = Info('Fingerprinted Wordpress version', desc, response.id,
                     self.get_name())
            i.set_url(wp_readme_url)

            kb.kb.append(self, 'info', i)
            om.out.information(i.get_desc())
Exemple #55
0
    def _verify_content_neg_enabled(self, fuzzable_request):
        '''
        Checks if the remote website is vulnerable or not. Saves the result in
        self._content_negotiation_enabled , because we want to perform this test
        only once.

        :return: True if vulnerable.
        '''
        if self._content_negotiation_enabled is not None:
            # The test was already performed, we return the old response
            return self._content_negotiation_enabled

        else:
            # We perform the test, for this we need a URL that has a filename, URL's
            # that don't have a filename can't be used for this.
            filename = fuzzable_request.get_url().get_file_name()
            if filename == '':
                return None

            filename = filename.split('.')[0]

            # Now I simply perform the request:
            alternate_resource = fuzzable_request.get_url().url_join(filename)
            headers = fuzzable_request.get_headers()
            headers['Accept'] = 'w3af/bar'
            response = self._uri_opener.GET(alternate_resource,
                                            headers=headers)

            if 'alternates' in response.get_lower_case_headers():
                # Even if there is only one file, with an unique mime type,
                # the content negotiation will return an alternates header.
                # So this is pretty safe.

                # Save the result internally
                self._content_negotiation_enabled = True

                # Save the result as an info in the KB, for the user to see it:
                desc = 'HTTP Content negotiation is enabled in the remote web'\
                       ' server. This could be used to bruteforce file names'\
                       ' and find new resources.'

                i = Info('HTTP Content Negotiation enabled', desc, response.id,
                         self.get_name())
                i.set_url(response.get_url())

                kb.kb.append(self, 'content_negotiation', i)
                om.out.information(i.get_desc())
            else:
                om.out.information(
                    'The remote Web server has Content Negotiation disabled.')

                # I want to perform this test a couple of times... so I only return False
                # if that "couple of times" is empty
                self._tries_left -= 1
                if self._tries_left == 0:
                    # Save the FALSE result internally
                    self._content_negotiation_enabled = False
                else:
                    # None tells the plugin to keep trying with the next URL
                    return None

            return self._content_negotiation_enabled
Exemple #56
0
    def _extract_version_from_egg(self, query_results):
        '''
        Analyzes the eggs and tries to deduce a PHP version number
        ( which is then saved to the kb ).
        '''
        if not query_results:
            return None
        else:
            cmp_list = []
            for query_result in query_results:
                body = query_result.http_response.get_body()
                if isinstance(body, unicode): body = body.encode('utf-8')
                hash_str = hashlib.md5(body).hexdigest()

                cmp_list.append((hash_str, query_result.egg_desc))

            cmp_set = set(cmp_list)

            found = False
            matching_versions = []
            for version in self.EGG_DB:
                version_hashes = set(self.EGG_DB[version])

                if len(cmp_set) == len(cmp_set.intersection(version_hashes)):
                    matching_versions.append(version)
                    found = True

            if matching_versions:
                desc = 'The PHP framework version running on the remote'\
                       ' server was identified as:\n- %s'
                versions = '\n- '.join(matching_versions)
                desc = desc % versions

                response_ids = [
                    r.http_response.get_id() for r in query_results
                ]

                i = Info('Fingerprinted PHP version', desc, response_ids,
                         self.get_name())
                i['version'] = matching_versions

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

            if not found:
                version = 'unknown'
                powered_by_headers = kb.kb.raw_read('server_header',
                                                    'powered_by_string')
                try:
                    for v in powered_by_headers:
                        if 'php' in v.lower():
                            version = v.split('/')[1]
                except:
                    pass

                msg = 'The PHP version could not be identified using PHP eggs,'\
                      ', please send this signature and the PHP version to the'\
                      ' w3af project develop mailing list. Signature:'\
                      ' EGG_DB[\'%s\'] = %s\n'
                msg = msg % (version, str(list(cmp_set)))
                om.out.information(msg)