Exemple #1
0
    def grep(self, request, response):
        '''
        Plugin entry point, find feeds.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None
        '''
        dom = response.get_dom()
        uri = response.get_uri()

        # In some strange cases, we fail to normalize the document
        if uri not in self._already_inspected and dom is not None:

            self._already_inspected.add(uri)

            # Find all feed tags
            element_list = self._tag_xpath(dom)

            for element in element_list:

                feed_tag = element.tag
                feed_type = self._feed_types[feed_tag.lower()]
                version = element.attrib.get('version', 'unknown')

                fmt = 'The URL "%s" is a %s version %s feed.'
                desc = fmt % (uri, feed_type, version)
                i = Info('Content feed resource', desc, response.id,
                         self.get_name())
                i.set_uri(uri)
                i.add_to_highlight(feed_type)
                
                self.kb_append_uniq(self, 'feeds', i, 'URL')
Exemple #2
0
    def grep(self, request, response):
        '''
        Plugin entry point, verify if the HTML has a form with file uploads.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None
        '''
        url = response.get_url()

        if response.is_text_or_html() and not url in self._already_inspected:

            self._already_inspected.add(url)
            dom = response.get_dom()

            # In some strange cases, we fail to normalize the document
            if dom is not None:

                # Loop through file inputs tags
                for input_file in self._file_input_xpath(dom):
                    msg = 'The URL: "%s" has form with file upload capabilities.'
                    msg = msg % url

                    i = Info('File upload form', msg, response.id,
                             self.get_name())
                    i.set_url(url)
                    to_highlight = etree.tostring(input_file)
                    i.add_to_highlight(to_highlight)

                    self.kb_append_uniq(self, 'file_upload', i, 'URL')
Exemple #3
0
    def grep(self, request, response):
        '''
        Plugin entry point. Parse the object tags.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None
        '''
        url = response.get_url()
        dom = response.get_dom()

        if response.is_text_or_html() and dom is not None \
        and url not in self._already_analyzed:

            self._already_analyzed.add(url)

            elem_list = self._tag_xpath(dom)
            for element in elem_list:

                tag_name = element.tag

                desc = 'The URL: "%s" has an "%s" tag. We recommend you download'\
                      ' the client side code and analyze it manually.'
                desc = desc % (response.get_uri(), tag_name)

                i = Info('Browser plugin content', desc, response.id,
                         self.get_name())
                i.set_url(url)
                i.add_to_highlight(tag_name)

                self.kb_append_uniq(self, tag_name, i, 'URL')
Exemple #4
0
    def grep(self, request, response):
        '''
        Plugin entry point.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None, all results are saved in the kb.
        '''
        url = response.get_url()
        if response.is_text_or_html() and url not in self._already_inspected:

            # Don't repeat URLs
            self._already_inspected.add(url)

            dom = response.get_dom()
            # In some strange cases, we fail to normalize the document
            if dom is None:
                return

            script_elements = self._script_xpath(dom)
            for element in script_elements:
                # returns the text between <script> and </script>
                script_content = element.text

                if script_content is not None:

                    res = self._ajax_regex_re.search(script_content)
                    if res:
                        desc = 'The URL: "%s" has AJAX code.' % url
                        i = Info('AJAX code', desc, response.id,
                                 self.get_name())
                        i.set_url(url)
                        i.add_to_highlight(res.group(0))

                        self.kb_append_uniq(self, 'ajax', i, 'URL')
Exemple #5
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 #6
0
    def grep(self, request, response):
        '''
        Plugin entry point, verify if the HTML has a form with file uploads.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None
        '''
        url = response.get_url()

        if response.is_text_or_html() and not url in self._already_inspected:

            self._already_inspected.add(url)
            dom = response.get_dom()

            # In some strange cases, we fail to normalize the document
            if dom is not None:

                # Loop through file inputs tags
                for input_file in self._file_input_xpath(dom):
                    msg = 'The URL: "%s" has form with file upload capabilities.'
                    msg = msg % url
                    
                    i = Info('File upload form', msg, response.id,
                             self.get_name())
                    i.set_url(url)
                    to_highlight = etree.tostring(input_file)
                    i.add_to_highlight(to_highlight)
                    
                    self.kb_append_uniq(self, 'file_upload', i, 'URL')
Exemple #7
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 #8
0
    def _content_location_not_300(self, request, response):
        """
        Check if the response has a content-location header and the response code
        is not in the 300 range.

        :return: None, all results are saved in the kb.
        """
        if (
            "content-location" in response.get_lower_case_headers()
            and response.get_code() > 300
            and response.get_code() < 310
        ):
            desc = (
                'The URL: "%s" sent the HTTP header: "content-location"'
                ' with value: "%s" in an HTTP response with code %s which'
                " is a violation to the RFC."
            )
            desc = desc % (
                response.get_url(),
                response.get_lower_case_headers()["content-location"],
                response.get_code(),
            )
            i = Info("Content-Location HTTP header anomaly", desc, response.id, self.get_name())
            i.set_url(response.get_url())
            i.add_to_highlight("content-location")

            kb.kb.append(self, "anomaly", i)
Exemple #9
0
    def grep(self, request, response):
        """
        Plugin entry point.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None, all results are saved in the kb.
        """
        url = response.get_url()
        if response.is_text_or_html() and url not in self._already_inspected:

            # Don't repeat URLs
            self._already_inspected.add(url)

            dom = response.get_dom()
            # In some strange cases, we fail to normalize the document
            if dom is None:
                return

            script_elements = self._script_xpath(dom)
            for element in script_elements:
                # returns the text between <script> and </script>
                script_content = element.text

                if script_content is not None:

                    res = self._ajax_regex_re.search(script_content)
                    if res:
                        desc = 'The URL: "%s" has AJAX code.' % url
                        i = Info("AJAX code", desc, response.id, self.get_name())
                        i.set_url(url)
                        i.add_to_highlight(res.group(0))

                        self.kb_append_uniq(self, "ajax", i, "URL")
Exemple #10
0
    def grep(self, request, response):
        '''
        Plugin entry point. Parse the object tags.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None
        '''
        url = response.get_url()
        dom = response.get_dom()

        if response.is_text_or_html() and dom is not None \
        and url not in self._already_analyzed:

            self._already_analyzed.add(url)

            elem_list = self._tag_xpath(dom)
            for element in elem_list:

                tag_name = element.tag
                
                desc = 'The URL: "%s" has an "%s" tag. We recommend you download'\
                      ' the client side code and analyze it manually.'
                desc = desc % (response.get_uri(), tag_name)

                i = Info('Browser plugin content', desc, response.id,
                         self.get_name())
                i.set_url(url)
                i.add_to_highlight(tag_name)

                self.kb_append_uniq(self, tag_name, i, 'URL')
Exemple #11
0
    def grep(self, request, response):
        '''
        Plugin entry point, search for meta tags.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None
        '''
        uri = response.get_uri()

        if not response.is_text_or_html() or uri in self._already_inspected\
        or is_404(response):
            return

        self._already_inspected.add(uri)

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

        meta_tag_list = dp.get_meta_tags()

        for tag in meta_tag_list:
            tag_name = self._find_name(tag)
            for key, val in tag.items():

                for word in self.INTERESTING_WORDS:

                    # Check if we have something interesting
                    # and WHERE that thing actually is
                    where = content = None
                    if (word in key):
                        where = 'name'
                        content = key
                    elif (word in val):
                        where = 'value'
                        content = val

                    # Now... if we found something, report it =)
                    if where is not None:
                        # The atribute is interesting!
                        fmt = 'The URI: "%s" sent a <meta> tag with attribute'\
                              ' %s set to "%s" which looks interesting.'
                        desc = fmt % (response.get_uri(), where, content)
                        
                        if self.INTERESTING_WORDS.get(tag_name, None):
                            usage = self.INTERESTING_WORDS[tag_name]
                            desc += ' The tag is used for %s.' % usage
                        
                        i = Info('Interesting META tag', desc, response.id,
                                 self.get_name())
                        i.set_uri(response.get_uri())
                        i.add_to_highlight(where, content)

                        self.kb_append_uniq(self, 'meta_tags', i, 'URL')
Exemple #12
0
 def analyze_disco(self, request, response):
     for disco_string in self._disco_strings:
         if disco_string in response:
             desc = 'The URL: "%s" is a DISCO file that contains references'\
                    ' to WSDL URLs.'
             desc = desc % response.get_url()
             i = Info('DISCO resource', desc, response.id,
                      self.get_name())
             i.set_url(response.get_url())
             i.add_to_highlight(disco_string)
             
             self.kb_append_uniq(self, 'disco', i, 'URL')
             break
Exemple #13
0
 def analyze_wsdl(self, request, response):
     match_list = self._multi_in.query(response.body)
     if len(match_list):
         desc = 'The URL: "%s" is a Web Services Description Language'\
                ' page. This requires manual analysis to determine the'\
                ' security of the web service.'
         desc = desc % response.get_url()
         
         i = Info('WSDL resource', desc, response.id,
                  self.get_name())
         i.set_url(response.get_url())
         i.add_to_highlight(*match_list)
         
         self.kb_append_uniq(self, 'wsdl', i, 'URL')
Exemple #14
0
    def grep(self, request, response):
        '''
        Plugin entry point, test existance of HTML auto-completable forms
        containing password-type inputs. Either form's <autocomplete> attribute
        is not present or is 'off'.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None, all results are saved in the kb.
        '''
        url = response.get_url()
        dom = response.get_dom()

        if response.is_text_or_html() and dom is not None \
        and not url in self._already_inspected:

            self._already_inspected.add(url)

            autocompletable = lambda inp: inp.get('autocomplete', 'on').lower(
            ) != 'off'

            # Loop through "auto-completable" forms
            for form in self._autocomplete_forms_xpath(dom):

                passwd_inputs = self._pwd_input_xpath(form)

                # Test existence of password-type inputs and verify that
                # all inputs are autocompletable
                if passwd_inputs and all(
                        map(autocompletable,
                            chain(passwd_inputs,
                                  self._text_input_xpath(form)))):

                    desc = 'The URL: "%s" has a "<form>" element with ' \
                           'auto-complete enabled.'
                    desc = desc % url

                    i = Info('Auto-completable form', desc, response.id,
                             self.get_name())
                    i.set_url(url)

                    form_str = etree.tostring(form)
                    to_highlight = form_str[:(form_str).find('>') + 1]
                    i.add_to_highlight(to_highlight)

                    # Store and print
                    kb.kb.append(self, 'form_autocomplete', i)
                    om.out.information(desc)

                    break
Exemple #15
0
    def grep(self, request, response):
        '''
        Plugin entry point, test existance of HTML auto-completable forms
        containing password-type inputs. Either form's <autocomplete> attribute
        is not present or is 'off'.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None, all results are saved in the kb.
        '''
        url = response.get_url()
        dom = response.get_dom()

        if response.is_text_or_html() and dom is not None \
        and not url in self._already_inspected:

            self._already_inspected.add(url)

            autocompletable = lambda inp: inp.get(
                'autocomplete', 'on').lower() != 'off'

            # Loop through "auto-completable" forms
            for form in self._autocomplete_forms_xpath(dom):

                passwd_inputs = self._pwd_input_xpath(form)

                # Test existence of password-type inputs and verify that
                # all inputs are autocompletable
                if passwd_inputs and all(map(autocompletable,
                                             chain(passwd_inputs,
                                                   self._text_input_xpath(form)))):

                    desc = 'The URL: "%s" has a "<form>" element with ' \
                           'auto-complete enabled.'
                    desc = desc % url

                    i = Info('Auto-completable form', desc, response.id,
                             self.get_name())
                    i.set_url(url)

                    form_str = etree.tostring(form)
                    to_highlight = form_str[:(form_str).find('>') + 1]
                    i.add_to_highlight(to_highlight)

                    # Store and print
                    kb.kb.append(self, 'form_autocomplete', i)
                    om.out.information(desc)

                    break
Exemple #16
0
    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
Exemple #17
0
    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
Exemple #18
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 #19
0
    def grep(self, request, response):
        '''
        Plugin entry point, identify hashes in the HTTP response.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None
        '''
        # I know that by doing this I loose the chance of finding hashes in
        # PDF files, but... this is much faster
        if not response.is_text_or_html():
            return

        body = response.get_body()
        splitted_body = self._split_re.split(body)
        for possible_hash in splitted_body:

            #    This is a performance enhancement that cuts the execution
            #    time of this plugin in half.
            if len(possible_hash) < 31 or\
            len(possible_hash) > 129 :
                return

            hash_type = self._get_hash_type(possible_hash)
            if not hash_type:
                return

            possible_hash = possible_hash.lower()
            if self._has_hash_distribution(possible_hash):
                if (possible_hash,
                        response.get_url()) not in self._already_reported:
                    desc = 'The URL: "%s" returned a response that may contain'\
                          ' a "%s" hash. The hash string is: "%s". This is'\
                          ' uncommon and requires human verification.'
                    desc = desc % (response.get_url(), hash_type,
                                   possible_hash)

                    i = Info('Hash string in HTML content', desc, response.id,
                             self.get_name())
                    i.set_url(response.get_url())
                    i.add_to_highlight(possible_hash)

                    self.kb_append(self, 'hash_analysis', i)

                    self._already_reported.add(
                        (possible_hash, response.get_url()))
Exemple #20
0
    def grep(self, request, response):
        '''
        Plugin entry point. Analyze if the HTTP response reason messages are strange.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None, all results are saved in the kb.
        '''
        response_code = response.get_code()
        msg_list = W3C_REASONS.get(response_code, None)

        if msg_list is not None:

            response_reason = response.get_msg().lower()

            if response_reason not in msg_list:
                #
                #   I check if the kb already has a info object with this code:
                #
                strange_reason_infos = kb.kb.get('strange_reason',
                                                 'strange_reason')

                corresponding_info = None
                for info_obj in strange_reason_infos:
                    if info_obj['reason'] == response.get_msg():
                        corresponding_info = info_obj
                        break

                if corresponding_info:
                    # Work with the "old" info object:
                    id_list = corresponding_info.get_id()
                    id_list.append(response.id)
                    corresponding_info.set_id(id_list)

                else:
                    # Create a new info object from scratch and save it to the kb:
                    desc = 'The remote Web server sent a strange HTTP reason'\
                           'message: "%s" manual inspection is advised.'
                    desc = desc % response.get_msg()
                    i = Info('Strange HTTP Reason message', desc, response.id,
                             self.get_name())
                    i.set_url(response.get_url())
                    i['reason'] = response.get_msg()
                    i.add_to_highlight(response.get_msg())

                    self.kb_append_uniq(self, 'strange_reason', i, 'URL')
Exemple #21
0
    def grep(self, request, response):
        '''
        Plugin entry point. Analyze if the HTTP response reason messages are strange.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None, all results are saved in the kb.
        '''
        response_code = response.get_code()
        msg_list = W3C_REASONS.get(response_code, None)

        if msg_list is not None:

            response_reason = response.get_msg().lower()

            if response_reason not in msg_list:
                #
                #   I check if the kb already has a info object with this code:
                #
                strange_reason_infos = kb.kb.get('strange_reason',
                                                 'strange_reason')

                corresponding_info = None
                for info_obj in strange_reason_infos:
                    if info_obj['reason'] == response.get_msg():
                        corresponding_info = info_obj
                        break

                if corresponding_info:
                    # Work with the "old" info object:
                    id_list = corresponding_info.get_id()
                    id_list.append(response.id)
                    corresponding_info.set_id(id_list)

                else:
                    # Create a new info object from scratch and save it to the kb:
                    desc = 'The remote Web server sent a strange HTTP reason'\
                           'message: "%s" manual inspection is advised.'
                    desc = desc % response.get_msg()
                    i = Info('Strange HTTP Reason message', desc,
                             response.id, self.get_name())
                    i.set_url(response.get_url())
                    i['reason'] = response.get_msg()
                    i.add_to_highlight(response.get_msg())
                    
                    self.kb_append_uniq(self, 'strange_reason', i, 'URL')
Exemple #22
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 #23
0
    def grep(self, request, response):
        '''
        Plugin entry point, verify if the HTML has a form with file uploads.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None
        '''
        url = response.get_url()

        if response.is_text_or_html() and not url in self._already_inspected:

            self._already_inspected.add(url)
            dom = response.get_dom()

            # In some strange cases, we fail to normalize the document
            if dom is None:
                return

            # Loop through script inputs tags
            for script_src_tag in self._script_src_xpath(dom):

                # This should be always False due to the XPATH we're using
                # but you never know...
                if not 'src' in script_src_tag.attrib:
                    continue

                script_src = script_src_tag.attrib['src']
                script_full_url = response.get_url().url_join(script_src)
                script_domain = script_full_url.get_domain()

                if script_domain != response.get_url().get_domain():
                    desc = 'The URL: "%s" has script tag with a source that points' \
                           ' to a third party site ("%s"). This practice is not' \
                           ' recommended as security of the current site is being' \
                           ' delegated to that external entity.'
                    desc = desc % (url, script_domain)

                    i = Info('Cross-domain javascript source', desc,
                             response.id, self.get_name())
                    i.set_url(url)
                    to_highlight = etree.tostring(script_src_tag)
                    i.add_to_highlight(to_highlight)

                    self.kb_append_uniq(self, 'cross_domain_js', i, 'URL')
Exemple #24
0
    def grep(self, request, response):
        '''
        Plugin entry point, verify if the HTML has a form with file uploads.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None
        '''
        url = response.get_url()

        if response.is_text_or_html() and not url in self._already_inspected:

            self._already_inspected.add(url)
            dom = response.get_dom()

            # In some strange cases, we fail to normalize the document
            if dom is None:
                return
            
            # Loop through script inputs tags
            for script_src_tag in self._script_src_xpath(dom):

                # This should be always False due to the XPATH we're using
                # but you never know...
                if not 'src' in script_src_tag.attrib:
                    continue

                script_src = script_src_tag.attrib['src']
                script_full_url = response.get_url().url_join(script_src)
                script_domain = script_full_url.get_domain()

                if script_domain != response.get_url().get_domain():
                    desc = 'The URL: "%s" has script tag with a source that points' \
                           ' to a third party site ("%s"). This practice is not' \
                           ' recommended as security of the current site is being' \
                           ' delegated to that external entity.'
                    desc = desc % (url, script_domain) 
                    
                    i = Info('Cross-domain javascript source', desc,
                             response.id, self.get_name())
                    i.set_url(url)
                    to_highlight = etree.tostring(script_src_tag)
                    i.add_to_highlight(to_highlight)
                    
                    self.kb_append_uniq(self, 'cross_domain_js', i, 'URL')
Exemple #25
0
    def grep(self, request, response):
        '''
        Plugin entry point.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None, all results are saved in the kb.
        '''
        uri = response.get_uri()
        
        # 501 Code is "Not Implemented" which in some cases responds with
        # this in the body:
        # <body><h2>HTTP/1.1 501 Not Implemented</h2></body>
        # Which creates a false positive.
        
        if response.get_code() != 501\
        and response.is_text_or_html()\
        and uri not in self._already_inspected:
            
            # Don't repeat URLs
            self._already_inspected.add(uri)

            body_without_tags = response.get_clear_text_body()
            if body_without_tags is None:
                return

            for match, _, _, reqres in self._multi_re.query(body_without_tags):

                if reqres == 'REQUEST':
                    desc = 'An HTTP request was found in the HTTP body of'\
                           ' a response.'
                    i = Info('HTTP Request in HTTP body', desc, response.id,
                             self.get_name())
                    i.set_uri(uri)
                    i.add_to_highlight(match.group(0))
                    kb.kb.append(self, 'request', i)

                if reqres == 'RESPONSE':
                    desc = 'An HTTP response was found in the HTTP body of'\
                           ' a response.'
                    i = Info('HTTP Response in HTTP body', desc, response.id,
                             self.get_name())
                    i.set_uri(uri)
                    i.add_to_highlight(match.group(0))
                    kb.kb.append(self, 'response', i)
Exemple #26
0
    def grep(self, request, response):
        '''
        Plugin entry point.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None, all results are saved in the kb.
        '''
        # Check if the header names are common or not
        for header_name in response.get_headers().keys():
            if header_name.upper() not in self.COMMON_HEADERS:

                # Check if the kb already has a info object with this code:
                strange_header_infos = kb.kb.get('strange_headers',
                                                 'strange_headers')

                for info_obj in strange_header_infos:
                    if info_obj['header_name'] == header_name:
                        # Work with the "old" info object:
                        id_list = info_obj.get_id()
                        id_list.append(response.id)
                        info_obj.set_id(id_list)
                        break
                else:
                    # Create a new info object from scratch and save it to
                    # the kb:
                    hvalue = response.get_headers()[header_name]

                    desc = 'The remote web server sent the HTTP header: "%s"'\
                           ' with value: "%s", which is quite uncommon and'\
                           ' requires manual analysis.'
                    desc = desc % (header_name, hvalue)

                    i = Info('Strange header', desc, response.id,
                             self.get_name())
                    i.set_url(response.get_url())
                    i['header_name'] = header_name
                    i['header_value'] = hvalue
                    i.add_to_highlight(hvalue, header_name)

                    kb.kb.append(self, 'strange_headers', i)

        # Now check for protocol anomalies
        self._content_location_not_300(request, response)
Exemple #27
0
    def grep(self, request, response):
        """
        Plugin entry point.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None, all results are saved in the kb.
        """
        # Check if the header names are common or not
        for header_name in response.get_headers().keys():
            if header_name.upper() not in self.COMMON_HEADERS:

                # Check if the kb already has a info object with this code:
                strange_header_infos = kb.kb.get("strange_headers", "strange_headers")

                for info_obj in strange_header_infos:
                    if info_obj["header_name"] == header_name:
                        # Work with the "old" info object:
                        id_list = info_obj.get_id()
                        id_list.append(response.id)
                        info_obj.set_id(id_list)
                        break
                else:
                    # Create a new info object from scratch and save it to
                    # the kb:
                    hvalue = response.get_headers()[header_name]

                    desc = (
                        'The remote web server sent the HTTP header: "%s"'
                        ' with value: "%s", which is quite uncommon and'
                        " requires manual analysis."
                    )
                    desc = desc % (header_name, hvalue)

                    i = Info("Strange header", desc, response.id, self.get_name())
                    i.set_url(response.get_url())
                    i["header_name"] = header_name
                    i["header_value"] = hvalue
                    i.add_to_highlight(hvalue, header_name)

                    kb.kb.append(self, "strange_headers", i)

        # Now check for protocol anomalies
        self._content_location_not_300(request, response)
    def grep(self, request, response):
        '''
        If I find __VIEWSTATE and empty __EVENTVALIDATION => vuln.

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

        # First verify if we havent analyzed this URI yet
        if request.get_url() in self._already_analyzed:
            return

        self._already_analyzed.add(request.get_url())

        res = self._viewstate.search(response.get_body())
        if res:

            # I have __viewstate!, verify if event validation is enabled
            if not self._eventvalidation.search(response.get_body()):
                desc = 'The URL: "%s" has .NET Event Validation disabled.'\
                       ' This programming/configuration error should be'\
                       ' manually verified.'
                desc = desc % response.get_url()
                i = Info('.NET Event Validation is disabled', desc,
                         response.id, self.get_name())
                i.set_url(response.get_url())
                i.add_to_highlight(res.group())

                self.kb_append(self, 'dot_net_event_validation', i)

            if not self._encryptedVs.search(response.get_body()):
                # Nice! We can decode the viewstate! =)
                desc = 'The URL: "%s" has .NET ViewState encryption disabled.'\
                       ' This programming/configuration error could be exploited'\
                       ' to decode the viewstate contents.'
                desc = desc % response.get_url()

                i = Info('.NET ViewState encryption is disabled', desc,
                         response.id, self.get_name())
                i.set_url(response.get_url())

                self.kb_append(self, 'dot_net_event_validation', i)
    def grep(self, request, response):
        '''
        If I find __VIEWSTATE and empty __EVENTVALIDATION => vuln.

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

        # First verify if we havent analyzed this URI yet
        if request.get_url() in self._already_analyzed:
            return

        self._already_analyzed.add(request.get_url())

        res = self._viewstate.search(response.get_body())
        if res:

            # I have __viewstate!, verify if event validation is enabled
            if not self._eventvalidation.search(response.get_body()):
                desc = 'The URL: "%s" has .NET Event Validation disabled.'\
                       ' This programming/configuration error should be'\
                       ' manually verified.'
                desc = desc % response.get_url()
                i = Info('.NET Event Validation is disabled', desc,
                         response.id, self.get_name())
                i.set_url(response.get_url())
                i.add_to_highlight(res.group())
                
                self.kb_append(self, 'dot_net_event_validation', i)

            if not self._encryptedVs.search(response.get_body()):
                # Nice! We can decode the viewstate! =)
                desc = 'The URL: "%s" has .NET ViewState encryption disabled.'\
                       ' This programming/configuration error could be exploited'\
                       ' to decode the viewstate contents.'
                desc = desc % response.get_url()
                
                i = Info('.NET ViewState encryption is disabled', desc,
                         response.id, self.get_name())
                i.set_url(response.get_url())
                
                self.kb_append(self, 'dot_net_event_validation', i)
Exemple #30
0
    def grep(self, request, response):
        '''
        Plugin entry point, identify hashes in the HTTP response.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None
        '''
        # I know that by doing this I loose the chance of finding hashes in
        # PDF files, but... this is much faster
        if not response.is_text_or_html():
            return

        body = response.get_body()
        splitted_body = self._split_re.split(body)
        for possible_hash in splitted_body:

            #    This is a performance enhancement that cuts the execution
            #    time of this plugin in half.
            if len(possible_hash) < 31 or\
            len(possible_hash) > 129 :
                return
            
            hash_type = self._get_hash_type(possible_hash)
            if not hash_type:
                return

            possible_hash = possible_hash.lower()
            if self._has_hash_distribution(possible_hash):
                if (possible_hash, response.get_url()) not in self._already_reported:
                    desc = 'The URL: "%s" returned a response that may contain'\
                          ' a "%s" hash. The hash string is: "%s". This is'\
                          ' uncommon and requires human verification.'
                    desc = desc % (response.get_url(), hash_type, possible_hash)
                    
                    i = Info('Hash string in HTML content', desc,
                             response.id, self.get_name())
                    i.set_url(response.get_url())
                    i.add_to_highlight(possible_hash)
                    
                    self.kb_append(self, 'hash_analysis', i)

                    self._already_reported.add( (possible_hash,
                                                 response.get_url()) )
Exemple #31
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 #32
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', '')
    def grep(self, request, response):
        '''
        Plugin entry point.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None, all results are saved in the kb.
        '''
        headers = response.get_headers()
        heaver_value, header_name = headers.iget('x-xss-protection', '')
        heaver_value = heaver_value.strip()
        if heaver_value == '0':
            desc = 'The remote web server sent the HTTP X-XSS-Protection header'\
                   ' with a 0 value, which disables Internet Explorer\'s XSS ' \
                   ' filter. In most cases, this is a bad practice and should' \
                   ' be subject to review.'
            i = Info('Insecure X-XSS-Protection header usage', desc,
                     response.id, self.get_name())
            i.add_to_highlight('X-XSS-Protection')

            self.kb_append_uniq(self, 'xss_protection_header', i, 'URL')
    def grep(self, request, response):
        '''
        Plugin entry point.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None, all results are saved in the kb.
        '''
        headers = response.get_headers()
        heaver_value, header_name = headers.iget('x-xss-protection', '')
        heaver_value = heaver_value.strip()
        if heaver_value == '0':
            desc = 'The remote web server sent the HTTP X-XSS-Protection header'\
                   ' with a 0 value, which disables Internet Explorer\'s XSS ' \
                   ' filter. In most cases, this is a bad practice and should' \
                   ' be subject to review.'
            i = Info('Insecure X-XSS-Protection header usage', desc,
                     response.id, self.get_name())
            i.add_to_highlight('X-XSS-Protection')
            
            self.kb_append_uniq(self, 'xss_protection_header', i, 'URL')
Exemple #35
0
    def _content_location_not_300(self, request, response):
        '''
        Check if the response has a content-location header and the response code
        is not in the 300 range.

        :return: None, all results are saved in the kb.
        '''
        if 'content-location' in response.get_lower_case_headers() \
        and response.get_code() > 300\
        and response.get_code() < 310:
            desc = 'The URL: "%s" sent the HTTP header: "content-location"'\
                   ' with value: "%s" in an HTTP response with code %s which'\
                   ' is a violation to the RFC.'
            desc = desc % (response.get_url(), response.get_lower_case_headers(
            )['content-location'], response.get_code())
            i = Info('Content-Location HTTP header anomaly', desc, response.id,
                     self.get_name())
            i.set_url(response.get_url())
            i.add_to_highlight('content-location')

            kb.kb.append(self, 'anomaly', i)
Exemple #36
0
    def grep(self, request, response):
        '''
        Plugin entry point. Analyze if the HTTP response codes are strange.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None, all results are saved in the kb.
        '''
        if response.get_code() in self.COMMON_HTTP_CODES:
            return

        # I check if the kb already has a info object with this code:
        strange_code_infos = kb.kb.get('strange_http_codes',
                                       'strange_http_codes')

        corresponding_info = None
        for info_obj in strange_code_infos:
            if info_obj['code'] == response.get_code():
                corresponding_info = info_obj
                break

        if corresponding_info:
            # Work with the "old" info object:
            id_list = corresponding_info.get_id()
            id_list.append(response.id)
            corresponding_info.set_id(id_list)

        else:
            # Create a new info object from scratch and save it to the kb:
            desc = 'The remote Web server sent a strange HTTP response code:'\
                   ' "%s" with the message: "%s", manual inspection is advised.'
            desc = desc % (response.get_code(), response.get_msg())
            
            i = Info('Strange HTTP response code', desc,
                     response.id, self.get_name())
            i.set_url(response.get_url())
            i['code'] = response.get_code()
            i.add_to_highlight(str(response.get_code()), response.get_msg())
            
            self.kb_append_uniq(self, 'strange_http_codes', i, 'URL')
Exemple #37
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 #38
0
    def _collect_cookies(self, request, response, cookie_object, cookie_header_value):
        '''
        Store (unique) cookies in the KB for later analysis.
        '''
        for cookie_info in kb.kb.get(self, 'cookies'):
            stored_cookie_obj = cookie_info['cookie-object']
            # Cookie class has an __eq__ which compares Cookies' keys for
            # equality, not the values, so these two cookies are equal:
            #        a=1;
            #        a=2;
            # And these two are not:
            #        a=1;
            #        b=1;
            if cookie_object == stored_cookie_obj:
                break
        else:
            cstr = cookie_object.output(header='')
            desc = 'The URL: "%s" sent the cookie: "%s".'
            desc = desc % (response.get_url(), cstr)

            i = Info('Cookie', desc, response.id, self.get_name())
            i.set_url(response.get_url())

            self._set_cookie_to_rep(i, cstr=cookie_header_value)

            i['cookie-object'] = cookie_object

            '''
            The expiration date tells the browser when to delete the
            cookie. If no expiration date is provided, the cookie is
            deleted at the end of the user session, that is, when the
            user quits the browser. As a result, specifying an expiration
            date is a means for making cookies to survive across
            browser sessions. For this reason, cookies that have an
            expiration date are called persistent.
            '''
            i['persistent'] = 'expires' in cookie_object
            i.add_to_highlight(i['cookie-string'])
            kb.kb.append(self, 'cookies', i)
Exemple #39
0
    def grep(self, request, response):
        '''
        Plugin entry point. Grep for oracle applications.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None
        '''
        url = response.get_url()
        if response.is_text_or_html() and url not in self._already_analyzed:
            self._already_analyzed.add(url)

            for msg in self.OAS_TAGS:
                if msg in response:
                    desc = 'The URL: "%s" was created using Oracle Application'\
                           ' Server.'
                    desc = desc % response.get_url()
                    i = Info('Oracle application server', desc, response.id,
                             self.get_name())
                    i.set_url(url)
                    i.add_to_highlight(msg)
                    
                    self.kb_append(self, 'oracle', i)
Exemple #40
0
    def grep(self, request, response):
        '''
        Plugin entry point. Grep for oracle applications.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None
        '''
        url = response.get_url()
        if response.is_text_or_html() and url not in self._already_analyzed:
            self._already_analyzed.add(url)

            for msg in self.OAS_TAGS:
                if msg in response:
                    desc = 'The URL: "%s" was created using Oracle Application'\
                           ' Server.'
                    desc = desc % response.get_url()
                    i = Info('Oracle application server', desc, response.id,
                             self.get_name())
                    i.set_url(url)
                    i.add_to_highlight(msg)

                    self.kb_append(self, 'oracle', i)
Exemple #41
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 #42
0
    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)
Exemple #43
0
    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)
Exemple #44
0
 def create_info(self, desc, response, motw_match):
     i = Info('Mark of the web', desc, response.id, self.get_name())
     i.set_url(response.get_url())
     i.add_to_highlight(motw_match.group(0))
     return i
Exemple #45
0
 def create_info(self, desc, response, motw_match):
     i = Info('Mark of the web', desc, response.id, self.get_name())
     i.set_url(response.get_url())
     i.add_to_highlight(motw_match.group(0))
     return i
    def grep(self, request, response):
        '''
        Plugin entry point.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None, all results are saved in the kb.
        '''
        try:
            dp = parser_cache.dpc.get_document_parser_for(response)
        except w3afException:
            return

        # Note:
        # - With parsed_references I'm 100% that it's really something in the
        #   HTML that the developer intended to add.
        #
        # - The re_references are the result of regular expressions, which in
        #   some cases are just false positives.
        #
        parsed_references, _ = dp.get_references()

        for ref in parsed_references:

            qs = ref.querystring

            for param_name in qs:
                # This for loop is to address the repeated parameter name issue
                for element_index in xrange(len(qs[param_name])):
                    if self._is_strange(request, param_name, qs[param_name][element_index])\
                    and (ref.uri2url(), param_name) not in self._already_reported:
                        # Don't repeat findings
                        self._already_reported.add((ref.uri2url(), param_name))

                        desc = 'The URI: "%s" has a parameter named: "%s"'\
                               ' with value: "%s", which is very uncommon.'\
                               ' and requires manual verification.'
                        desc = desc % (response.get_uri(), param_name,
                                       qs[param_name][element_index])

                        i = Info('Uncommon query string parameter', desc,
                                 response.id, self.get_name())
                        i.set_uri(ref)
                        i.set_var(param_name)
                        i['parameter_value'] = qs[param_name][element_index]
                        i.add_to_highlight(qs[param_name][element_index])

                        self.kb_append(self, 'strange_parameters', i)

                    # To find this kind of vulns
                    # http://thedailywtf.com/Articles/Oklahoma-
                    # Leaks-Tens-of-Thousands-of-Social-Security-Numbers,-Other-
                    # Sensitive-Data.aspx
                    if self._is_SQL(request, param_name, qs[param_name][element_index])\
                    and ref not in self._already_reported:

                        # Don't repeat findings
                        self._already_reported.add(ref)
                        desc = 'The URI: "%s" has a parameter named: "%s"'\
                               ' with value: "%s", which is a SQL query.'
                        desc = desc % (response.get_uri(), param_name,
                                       qs[param_name][element_index])
                        v = Vuln('Parameter has SQL sentence', desc,
                                 severity.LOW, response.id, self.get_name())
                        v.set_uri(ref)
                        v.set_var(param_name)
                        v['parameter_value'] = qs[param_name][element_index]
                        
                        v.add_to_highlight(qs[param_name][element_index])
                        self.kb_append(self, 'strange_parameters', v)
Exemple #47
0
    def _grep_worker(self, request, response, kb_key, domain=None):
        '''
        Helper method for using in self.grep()

        :param request: The HTTP request
        :param response: The HTTP response
        :param kb_key: Knowledge base dict key
        :param domain: Target domain for get_emails filter
        :return: None
        '''
        try:
            dp = parser_cache.dpc.get_document_parser_for(response)
        except w3afException:
            msg = 'If I can\'t parse the document, I won\'t be able to find'\
                  '  any emails. Ignoring the response for "%s".'
            om.out.debug(msg % response.get_url())
            return

        emails = dp.get_emails(domain)

        for mail_address in emails:
            # Reduce false positives
            if request.sent(mail_address):
                continue

            # Email address are case insensitive
            mail_address = mail_address.lower()
            url = response.get_url()

            email_map = {}
            for info_obj in kb.kb.get('emails', 'emails'):
                mail_string = info_obj['mail']
                email_map[mail_string] = info_obj

            if mail_address not in email_map:
                # Create a new info object, and report it
                desc = 'The mail account: "%s" was found in: \n- %s'\
                       ' - In request with id: %s.'
                desc = desc % (mail_address, url, response.id)

                i = Info('Exposed email address', desc, response.id,
                         self.get_name())
                i.set_url(url)
                i['mail'] = mail_address
                i['url_list'] = set([url,])
                i['user'] = mail_address.split('@')[0]
                i.add_to_highlight(mail_address)
                
                self.kb_append('emails', kb_key, i)

            else:

                # Get the corresponding info object.
                i = email_map[mail_address]
                # And work
                if url not in i['url_list']:
                    # This email was already found in some other URL
                    # I'm just going to modify the url_list and the description
                    # message of the information object.
                    id_list_of_info = i.get_id()
                    id_list_of_info.append(response.id)
                    i.set_id(id_list_of_info)
                    i.set_url(url)
                    desc = i.get_desc()
                    desc += '\n- %s - In request with id: %s.'
                    desc = desc % (url, response.id)
                    i.set_desc(desc)
                    i['url_list'].add(url)
Exemple #48
0
    def grep(self, request, response):
        '''
        Plugin entry point.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None, all results are saved in the kb.
        '''
        try:
            dp = parser_cache.dpc.get_document_parser_for(response)
        except w3afException:
            return

        # Note:
        # - With parsed_references I'm 100% that it's really something in the
        #   HTML that the developer intended to add.
        #
        # - The re_references are the result of regular expressions, which in
        #   some cases are just false positives.
        #
        parsed_references, _ = dp.get_references()

        for ref in parsed_references:

            qs = ref.querystring

            for param_name in qs:
                # This for loop is to address the repeated parameter name issue
                for element_index in xrange(len(qs[param_name])):
                    if self._is_strange(request, param_name, qs[param_name][element_index])\
                    and (ref.uri2url(), param_name) not in self._already_reported:
                        # Don't repeat findings
                        self._already_reported.add((ref.uri2url(), param_name))

                        desc = 'The URI: "%s" has a parameter named: "%s"'\
                               ' with value: "%s", which is very uncommon.'\
                               ' and requires manual verification.'
                        desc = desc % (response.get_uri(), param_name,
                                       qs[param_name][element_index])

                        i = Info('Uncommon query string parameter', desc,
                                 response.id, self.get_name())
                        i.set_uri(ref)
                        i.set_var(param_name)
                        i['parameter_value'] = qs[param_name][element_index]
                        i.add_to_highlight(qs[param_name][element_index])

                        self.kb_append(self, 'strange_parameters', i)

                    # To find this kind of vulns
                    # http://thedailywtf.com/Articles/Oklahoma-
                    # Leaks-Tens-of-Thousands-of-Social-Security-Numbers,-Other-
                    # Sensitive-Data.aspx
                    if self._is_SQL(request, param_name, qs[param_name][element_index])\
                    and ref not in self._already_reported:

                        # Don't repeat findings
                        self._already_reported.add(ref)
                        desc = 'The URI: "%s" has a parameter named: "%s"'\
                               ' with value: "%s", which is a SQL query.'
                        desc = desc % (response.get_uri(), param_name,
                                       qs[param_name][element_index])
                        v = Vuln('Parameter has SQL sentence', desc,
                                 severity.LOW, response.id, self.get_name())
                        v.set_uri(ref)
                        v.set_var(param_name)
                        v['parameter_value'] = qs[param_name][element_index]

                        v.add_to_highlight(qs[param_name][element_index])
                        self.kb_append(self, 'strange_parameters', v)