Exemplo n.º 1
0
    def test_null_byte(self):
        in_list = ['\x00']
        imi = MultiIn(in_list)

        result = to_list(imi.query('abc\x00def'))
        self.assertEqual(1, len(result))
        self.assertEqual('\x00', result[0])
Exemplo n.º 2
0
    def test_null_byte(self):
        in_list = ['\x00']
        imi = MultiIn(in_list)

        result = to_list(imi.query('abc\x00def'))
        self.assertEqual(1, len(result))
        self.assertEqual('\x00', result[0])
Exemplo n.º 3
0
    def _is_in_phishtank(self, to_check):
        """
        Reads the phishtank db and tries to match the entries on that db with
        the to_check

        :return: A list with the sites to match against the phishtank db
        """
        try:
            phishtank_db_fd = file(self.PHISHTANK_DB, 'r')
        except Exception as e:
            msg = 'Failed to open phishtank database: "%s", exception: "%s".'
            raise BaseFrameworkException(msg % (self.PHISHTANK_DB, e))

        pt_matches = []
        self._multi_in = MultiIn(to_check)

        om.out.debug('Starting the phishtank CSV parsing.')

        pt_csv_reader = csv.reader(phishtank_db_fd,
                                   delimiter=' ',
                                   quotechar='|',
                                   quoting=csv.QUOTE_MINIMAL)

        for phishing_url, phishtank_detail_url in pt_csv_reader:
            pt_match = self._url_matches(phishing_url, phishtank_detail_url)
            if pt_match:
                pt_matches.append(pt_match)

        om.out.debug('Finished CSV parsing.')

        return pt_matches
Exemplo n.º 4
0
    def test_unicode(self):
        in_list = [u'ñ', u'ý']
        imi = MultiIn(in_list)

        result = to_list(imi.query('abcn'))
        self.assertEqual(0, len(result))

        result = to_list(imi.query('abcñ'))
        self.assertEqual(1, len(result))
        self.assertEqual('ñ', result[0])
Exemplo n.º 5
0
    def test_unicode(self):
        in_list = [u'ñ', u'ý']
        imi = MultiIn(in_list)

        result = to_list(imi.query('abcn'))
        self.assertEqual(0, len(result))

        result = to_list(imi.query('abcñ'))
        self.assertEqual(1, len(result))
        self.assertEqual('ñ', result[0])
Exemplo n.º 6
0
    def test_simplest(self):
        in_list = ['123', '456', '789']
        imi = MultiIn(in_list)

        result = to_list(imi.query('456'))
        self.assertEqual(1, len(result))
        self.assertEqual('456', result[0])

        result = to_list(imi.query('789'))
        self.assertEqual(1, len(result))
        self.assertEqual('789', result[0])
Exemplo n.º 7
0
    def test_simplest(self):
        in_list = ['123', '456', '789']
        imi = MultiIn(in_list)

        result = to_list(imi.query('456'))
        self.assertEqual(1, len(result))
        self.assertEqual('456', result[0])

        result = to_list(imi.query('789'))
        self.assertEqual(1, len(result))
        self.assertEqual('789', result[0])
Exemplo n.º 8
0
    def end(self):
        """
        This method is called when the plugin wont be used anymore and is used
        to find persistent SSI vulnerabilities.

        Example where a persistent SSI can be found:

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

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

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

        For a working example please see moth VM.
        """
        fuzzable_request_set = kb.kb.get_all_known_fuzzable_requests()

        debugging_id = rand_alnum(8)
        om.out.debug('Starting stored SSI search (did=%s)' % debugging_id)

        #
        # TODO
        #
        # After identifying the issue we have in these lines, I should change
        # this code to use:
        #
        #   len(self._expected_mutant_dict)
        #   self._expected_mutant_dict.iterkeys()
        #
        # Those two methods are faster and consume less memory than the things
        # I'm doing below now.
        #
        expected_strings = self._expected_mutant_dict.keys()
        args = (len(expected_strings), debugging_id)
        om.out.debug('About to create MultiIn with %s keys (did=%s)' % args)

        self._persistent_multi_in = MultiIn(expected_strings)
        om.out.debug('Created stored SSI MultiIn (did=%s)' % debugging_id)

        self._send_mutants_in_threads(self._uri_opener.send_mutant,
                                      fuzzable_request_set,
                                      self._analyze_persistent,
                                      cache=False,
                                      debugging_id=debugging_id)

        self._expected_mutant_dict.cleanup()
Exemplo n.º 9
0
    def test_special_char(self):
        in_list = ['javax.naming.NameNotFoundException', '7', '8']
        imi = MultiIn(in_list)

        s = 'abc \\n javax.naming.NameNotFoundException \\n 123'
        result = to_list(imi.query(s))
        self.assertEqual(1, len(result))
        self.assertEqual('javax.naming.NameNotFoundException', result[0])

        in_list = [u'abc(def)', u'foo(bar)']
        imi = MultiIn(in_list)

        result = to_list(imi.query('foo abc(def) bar'))
        self.assertEqual(1, len(result))
        self.assertEqual('abc(def)', result[0])
Exemplo n.º 10
0
    def test_assoc_obj(self):
        in_list = [('123456', None, None), ('abcdef', 1, 2)]
        imi = MultiIn(in_list)

        result = to_list(imi.query('spam1234567890eggs'))
        self.assertEqual(1, len(result))
        self.assertEqual('123456', result[0][0])
        self.assertEqual(None, result[0][1])
        self.assertEqual(None, result[0][2])

        result = to_list(imi.query('foo abcdef bar'))
        self.assertEqual(1, len(result))
        self.assertEqual('abcdef', result[0][0])
        self.assertEqual(1, result[0][1])
        self.assertEqual(2, result[0][2])
Exemplo n.º 11
0
    def test_assoc_obj(self):
        in_list = [('123456', None, None), ('abcdef', 1, 2)]
        imi = MultiIn(in_list)

        result = to_list(imi.query('spam1234567890eggs'))
        self.assertEqual(1, len(result))
        self.assertEqual('123456', result[0][0])
        self.assertEqual(None, result[0][1])
        self.assertEqual(None, result[0][2])

        result = to_list(imi.query('foo abcdef bar'))
        self.assertEqual(1, len(result))
        self.assertEqual('abcdef', result[0][0])
        self.assertEqual(1, result[0][1])
        self.assertEqual(2, result[0][2])
Exemplo n.º 12
0
    def __init__(self):
        GrepPlugin.__init__(self)

        self.PUBLIC = 'public'
        self.PRIVATE = 'private'

        PUBLIC = 'public'
        PRIVATE = 'private'

        KEY_FORMATS = (
            # RSA (PKCS1)
            ('-----BEGIN RSA PRIVATE KEY-----', ('RSA-PRIVATE', PRIVATE)), 
            ('-----BEGIN RSA PUBLIC KEY-----', ('RSA-PUBLIC', PUBLIC)),
            ('ssh-rsa', ('RSA-PUBLIC', PUBLIC)),
            
            # DSA
            ('-----BEGIN DSA PRIVATE KEY-----', ('DSA-PRIVATE', PRIVATE)),
            ('-----BEGIN DSA PUBLIC KEY-----', ('DSA-PUBLIC', PUBLIC)),
            ('ssh-dss', ('DSA-PUBLIC', PUBLIC)),
            
            # Elliptic Curve
            ('-----BEGIN EC PRIVATE KEY-----', ('EC-PRIVATE', PRIVATE)),
            ('-----BEGIN EC PUBLIC KEY-----', ('EC-PUBLIC', PUBLIC)),
            ('ecdsa-sha2-nistp256', ('EC-PUBLIC', PUBLIC)),
            
            # SSH2
            ('---- BEGIN SSH2 PUBLIC KEY ----', ('SSH2-PRIVATE', PRIVATE)),
            ('---- BEGIN SSH2 PRIVATE KEY ----', ('SSH2-PUBLIC', PUBLIC)),

            # ed25519 (OpenSSH)
            ('-----BEGIN OPENSSH PRIVATE KEY-----', ('ED25519-SSH-PRIVATE', PRIVATE)),
            ('-----BEGIN OPENSSH PUBLIC KEY-----', ('ED25519-SSH-PUBLIC', PUBLIC)),
            ('ssh-ed25519', ('ED25519-SSH-PUBLIC', PUBLIC)),
            
            # PKCS8
            ('-----BEGIN PRIVATE KEY-----', ('PKCS8-PRIVATE', PRIVATE)),
            ('-----BEGIN PUBLIC KEY-----', ('PKCS8-PUBLIC', PUBLIC)),
            ('-----BEGIN ENCRYPTED PRIVATE KEY-----', ('PKCS8-ENCRYPTED-PRIVATE', PRIVATE)),
            ('-----BEGIN ENCRYPTED PUBLIC KEY-----', ('PKCS8-ENCRYPTED-PUBLIC', PUBLIC)),
            
            # XML
            ('<RSAKeyPair>', ('XML-RSA', PRIVATE)),
            ('<RSAKeyValue>', ('.NET-XML-RSA', PUBLIC))
        )        

        self._multi_in = MultiIn(KEY_FORMATS)
Exemplo n.º 13
0
    def test_many_start_similar(self):

        prefix = '0000000'

        def generator(count):
            for _ in xrange(count):
                a = rand_number(5)
                yield prefix + a

        fixed_samples = [prefix + '78912']
        in_list = itertools.chain(generator(5000), fixed_samples)

        imi = MultiIn(in_list)

        result = to_list(imi.query(prefix + '78912'))
        self.assertEqual(1, len(result))
        self.assertEqual(prefix + '78912', result[0])
Exemplo n.º 14
0
    def test_dup_keys(self):
        def generator(count):
            for _ in xrange(count):
                a = rand_number(5)
                yield a

                a = int(a)
                b = int(rand_number(5))
                yield str(a * b)

        fixed_samples_1 = ['123', '456']
        fixed_samples_2 = ['123', '456', '789']
        in_list = itertools.chain(fixed_samples_1, generator(5000),
                                  fixed_samples_2)

        imi = MultiIn(in_list)

        result = to_list(imi.query('789'))
        self.assertEqual(1, len(result))
        self.assertEqual('789', result[0])
Exemplo n.º 15
0
    def test_very_large_multiin(self):

        # Change this to test larger sizes
        COUNT = 5000

        def generator(count):
            for _ in xrange(count):
                a = rand_number(5)
                yield a

                a = int(a)
                b = int(rand_number(5))
                yield str(a * b)

        fixed_samples = ['123', '456', '789']
        in_list = itertools.chain(fixed_samples, generator(COUNT))

        imi = MultiIn(in_list)

        result = to_list(imi.query('456'))
        self.assertEqual(1, len(result))
        self.assertEqual('456', result[0])
Exemplo n.º 16
0
    def _load_secure_js_file(self, secure_js_file):
        """
        Loads the configuration file containing the domains
        """
        if not secure_js_file:
            return

        if secure_js_file == 'None':
            return

        secure_js_domains = set()
        secure_js_domains_fh = file(secure_js_file)

        for domain in secure_js_domains_fh:
            secure_js_domains.add(domain.strip())

        self._secure_domain_multi_in = MultiIn(secure_js_domains)
Exemplo n.º 17
0
    def test_special_char(self):
        in_list = ['javax.naming.NameNotFoundException', '7', '8']
        imi = MultiIn(in_list)

        s = 'abc \\n javax.naming.NameNotFoundException \\n 123'
        result = to_list(imi.query(s))
        self.assertEqual(1, len(result))
        self.assertEqual('javax.naming.NameNotFoundException', result[0])

        in_list = [u'abc(def)', u'foo(bar)']
        imi = MultiIn(in_list)

        result = to_list(imi.query('foo abc(def) bar'))
        self.assertEqual(1, len(result))
        self.assertEqual('abc(def)', result[0])
Exemplo n.º 18
0
    def end(self):
        """
        This method is called when the plugin wont be used anymore and is used
        to find persistent SSI vulnerabilities.

        Example where a persistent SSI can be found:

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

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

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

        For a working example please see moth VM.
        """
        fuzzable_request_set = kb.kb.get_all_known_fuzzable_requests()

        debugging_id = rand_alnum(8)
        om.out.debug('Starting stored SSI search (did=%s)' % debugging_id)

        self._persistent_multi_in = MultiIn(self._expected_mutant_dict.keys())
        om.out.debug('Created stored SSI MultiIn (did=%s)' % debugging_id)

        self._send_mutants_in_threads(self._uri_opener.send_mutant,
                                      fuzzable_request_set,
                                      self._analyze_persistent,
                                      cache=False,
                                      debugging_id=debugging_id)

        self._expected_mutant_dict.cleanup()
Exemplo n.º 19
0
class directory_indexing(GrepPlugin):
    """
    Grep every response for directory indexing problems.

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

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

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

        self._already_visited = ScalableBloomFilter()

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

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

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

        html_string = response.get_body()

        for _ in self._multi_in.query(html_string):

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

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

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

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Exemplo n.º 20
0
class ssi(AuditPlugin):
    """
    Find server side inclusion vulnerabilities.
    :author: Andres Riancho ([email protected])
    """
    def __init__(self):
        AuditPlugin.__init__(self)

        # Internal variables
        self._persistent_multi_in = None
        self._expected_mutant_dict = DiskDict(table_prefix='ssi')
        self._extract_expected_re = re.compile('[1-9]{5}')

    def audit(self, freq, orig_response, debugging_id):
        """
        Tests an URL for server side inclusion vulnerabilities.

        :param freq: A FuzzableRequest
        :param orig_response: The HTTP response associated with the fuzzable request
        :param debugging_id: A unique identifier for this call to audit()
        """
        ssi_strings = self._get_ssi_strings()
        mutants = create_mutants(freq, ssi_strings, orig_resp=orig_response)

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

    def _get_ssi_strings(self):
        """
        This method returns a list of server sides to try to include.

        :return: A string, see above.
        """
        # Generic
        yield '<!--#exec cmd="echo -n %s;echo -n %s" -->' % get_seeds()

        # Perl SSI
        yield ('<!--#set var="SEED_A" value="%s" -->'
               '<!--#echo var="SEED_A" -->'
               '<!--#set var="SEED_B" value="%s" -->'
               '<!--#echo var="SEED_B" -->' % get_seeds())

        # Smarty
        # http://www.smarty.net/docsv2/en/language.function.math.tpl
        yield '{math equation="x * y" x=%s y=%s}' % get_seeds()

        # Mako
        # http://docs.makotemplates.org/en/latest/syntax.html
        yield '${%s * %s}' % get_seeds()

        # Jinja2 and Twig
        # http://jinja.pocoo.org/docs/dev/templates/#math
        # http://twig.sensiolabs.org/doc/templates.html
        yield '{{%s * %s}}' % get_seeds()

        # Generic
        yield '{%s * %s}' % get_seeds()

    def _get_expected_results(self, mutant):
        """
        Extracts the potential results from the mutant payload and returns them
        in a list.
        """
        sent_payload = mutant.get_token_payload()
        seed_numbers = self._extract_expected_re.findall(sent_payload)

        seed_a = int(seed_numbers[0])
        seed_b = int(seed_numbers[1])

        return [str(seed_a * seed_b), '%s%s' % (seed_a, seed_b)]

    def _analyze_result(self, mutant, response):
        """
        Analyze the result of the previously sent request.
        :return: None, save the vuln to the kb.
        """
        # Store the mutants in order to be able to analyze the persistent case
        # later
        expected_results = self._get_expected_results(mutant)

        for expected_result in expected_results:
            self._expected_mutant_dict[expected_result] = mutant

        # Now we analyze the "reflected" case
        if self._has_bug(mutant):
            return

        for expected_result in expected_results:
            if expected_result not in response:
                continue

            if expected_result in mutant.get_original_response_body():
                continue

            desc = 'Server side include (SSI) was found at: %s'
            desc %= mutant.found_at()

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

            v.add_to_highlight(expected_result)
            self.kb_append_uniq(self, 'ssi', v)

    def end(self):
        """
        This method is called when the plugin wont be used anymore and is used
        to find persistent SSI vulnerabilities.

        Example where a persistent SSI can be found:

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

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

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

        For a working example please see moth VM.
        """
        fuzzable_request_set = kb.kb.get_all_known_fuzzable_requests()

        debugging_id = rand_alnum(8)
        om.out.debug('Starting stored SSI search (did=%s)' % debugging_id)

        #
        # TODO
        #
        # After identifying the issue we have in these lines, I should change
        # this code to use:
        #
        #   len(self._expected_mutant_dict)
        #   self._expected_mutant_dict.iterkeys()
        #
        # Those two methods are faster and consume less memory than the things
        # I'm doing below now.
        #
        expected_strings = self._expected_mutant_dict.keys()
        args = (len(expected_strings), debugging_id)
        om.out.debug('About to create MultiIn with %s keys (did=%s)' % args)

        self._persistent_multi_in = MultiIn(expected_strings)
        om.out.debug('Created stored SSI MultiIn (did=%s)' % debugging_id)

        self._send_mutants_in_threads(self._uri_opener.send_mutant,
                                      fuzzable_request_set,
                                      self._analyze_persistent,
                                      cache=False,
                                      debugging_id=debugging_id)

        self._expected_mutant_dict.cleanup()

    def _analyze_persistent(self, freq, response):
        """
        Analyze the response of sending each fuzzable request found by the
        framework, trying to identify any locations where we might have injected
        a payload.

        :param freq: The fuzzable request
        :param response: The HTTP response
        :return: None, vulns are stored in KB
        """
        msg = 'Analyzing HTTP response %s to verify if SSI string is found'
        om.out.debug(msg % response.get_uri())

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

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

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

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

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Exemplo n.º 21
0
class html_comments(GrepPlugin):
    """
    Extract and analyze HTML comments.

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

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

    HTML_FALSE_POSITIVES = {
        '[if IE]',
        '[if !IE]',
        '[if IE 7 ]',
        '[if IE 8 ]',
        '[if IE 9]',
        '[if lte IE 8]',
        '[if lte IE 9]',
    }

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

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

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

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

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

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

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

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

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

        for comment in dp.get_comments():
            if self._is_new(comment, response):
                self._interesting_word(comment, request, response)
                self._html_in_comment(comment, request, response)

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

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

            # These next two lines fix a false positive which appears when
            # audit.ssi sends a payload to a site which has XSS, and
            # grep.html_comments sees that comment and reports it.
            if request.sent(comment):
                continue

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

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

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

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

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

        if html_in_comment is None:
            return

        for false_positive_string in self.HTML_FALSE_POSITIVES:
            if false_positive_string in comment:
                return

        comment_data = (comment, response.get_url())

        if comment_data in self._already_reported:
            return

        self._already_reported.add(comment_data)

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

        desc = ('A comment containing HTML code "%s" was found in: "%s".'
                ' This could be interesting.')
        desc %= (comment, response.get_url())

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

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

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

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

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

        raise NoSuchTableException(msg % args)

    def _is_new(self, comment, response):
        """
        Avoid duplicates by checking self._comments
        """
        # pylint: disable=E1103
        try:
            comment_data = self._comments.get(comment, None)
        except NoSuchTableException, nste:
            self._handle_no_such_table(comment, response, nste)
            return

        response_url = response.get_url()

        # The comment was never seen before
        if comment_data is None:
            self._comments[comment] = [(response_url, response.id)]
            return True

        # The comment was seen before, maybe on a different URL
        for saved_url, response_id in comment_data:
            if response_url == saved_url:
                return False

        # The comment was never seen before on this URL, store this knowledge
        comment_data.append((response_url, response.id))
        self._comments[comment] = comment_data

        return True
Exemplo n.º 22
0
 def test_is_generator(self):
     in_list = ['123', '456', '789']
     imi = MultiIn(in_list)
     results = imi.query('456')
     self.assertIsInstance(results, types.GeneratorType)
Exemplo n.º 23
0
class preg_replace(AuditPlugin):
    """
    Find unsafe usage of PHPs preg_replace.
    :author: Andres Riancho ([email protected])
    """
    PREG_PAYLOAD = [
        'a' + ')/' * 100,
    ]
    PREG_ERRORS = ('Compilation failed: unmatched parentheses at offset',
                   '<b>Warning</b>:  preg_replace() [<a',
                   'Warning: preg_replace(): ')

    _multi_in = MultiIn(PREG_ERRORS)

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

        :param freq: A FuzzableRequest
        :param orig_response: The HTTP response associated with the fuzzable request
        :param debugging_id: A unique identifier for this call to audit()
        """
        # First I check If I get the error message from php
        mutants = create_mutants(freq,
                                 self.PREG_PAYLOAD,
                                 orig_resp=orig_response)

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

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

        for preg_error_string in self._find_preg_error(response):
            if preg_error_string in mutant.get_original_response_body():
                continue

            desc = 'Unsafe usage of preg_replace was found at: %s'
            desc %= mutant.found_at()

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

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

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

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

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

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

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Exemplo n.º 24
0
class ssi(AuditPlugin):
    """
    Find server side inclusion vulnerabilities.
    :author: Andres Riancho ([email protected])
    """
    def __init__(self):
        AuditPlugin.__init__(self)

        # Internal variables
        self._persistent_multi_in = None
        self._expected_mutant_dict = DiskDict(table_prefix='ssi')
        self._extract_expected_re = re.compile('[1-9]{5}')

    def audit(self, freq, orig_response, debugging_id):
        """
        Tests an URL for server side inclusion vulnerabilities.

        :param freq: A FuzzableRequest
        :param orig_response: The HTTP response associated with the fuzzable request
        :param debugging_id: A unique identifier for this call to audit()
        """
        ssi_strings = self._get_ssi_strings()
        mutants = create_mutants(freq, ssi_strings, orig_resp=orig_response)

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

    def _get_ssi_strings(self):
        """
        This method returns a list of server sides to try to include.

        :return: A string, see above.
        """
        # Generic
        yield '<!--#exec cmd="echo -n %s;echo -n %s" -->' % get_seeds()

        # Perl SSI
        yield ('<!--#set var="SEED_A" value="%s" -->'
               '<!--#echo var="SEED_A" -->'
               '<!--#set var="SEED_B" value="%s" -->'
               '<!--#echo var="SEED_B" -->' % get_seeds())

        # Smarty
        # http://www.smarty.net/docsv2/en/language.function.math.tpl
        yield '{math equation="x * y" x=%s y=%s}' % get_seeds()

        # Mako
        # http://docs.makotemplates.org/en/latest/syntax.html
        yield '${%s * %s}' % get_seeds()

        # Jinja2 and Twig
        # http://jinja.pocoo.org/docs/dev/templates/#math
        # http://twig.sensiolabs.org/doc/templates.html
        yield '{{%s * %s}}' % get_seeds()

        # Generic
        yield '{%s * %s}' % get_seeds()

    def _get_expected_results(self, mutant):
        """
        Extracts the potential results from the mutant payload and returns them
        in a list.
        """
        sent_payload = mutant.get_token_payload()
        seed_numbers = self._extract_expected_re.findall(sent_payload)

        seed_a = int(seed_numbers[0])
        seed_b = int(seed_numbers[1])

        return [str(seed_a * seed_b), '%s%s' % (seed_a, seed_b)]

    def _analyze_result(self, mutant, response):
        """
        Analyze the result of the previously sent request.
        :return: None, save the vuln to the kb.
        """
        # Store the mutants in order to be able to analyze the persistent case
        # later
        expected_results = self._get_expected_results(mutant)

        for expected_result in expected_results:
            self._expected_mutant_dict[expected_result] = mutant

        # Now we analyze the "reflected" case
        if self._has_bug(mutant):
            return

        for expected_result in expected_results:
            if expected_result not in response:
                continue

            if expected_result in mutant.get_original_response_body():
                continue

            desc = 'Server side include (SSI) was found at: %s'
            desc %= mutant.found_at()

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

            v.add_to_highlight(expected_result)
            self.kb_append_uniq(self, 'ssi', v)

    def end(self):
        """
        This method is called when the plugin wont be used anymore and is used
        to find persistent SSI vulnerabilities.

        Example where a persistent SSI can be found:

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

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

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

        For a working example please see moth VM.
        """
        fuzzable_request_set = kb.kb.get_all_known_fuzzable_requests()

        debugging_id = rand_alnum(8)
        om.out.debug('Starting stored SSI search (did=%s)' % debugging_id)

        self._persistent_multi_in = MultiIn(self._expected_mutant_dict.keys())
        om.out.debug('Created stored SSI MultiIn (did=%s)' % debugging_id)

        self._send_mutants_in_threads(self._uri_opener.send_mutant,
                                      fuzzable_request_set,
                                      self._analyze_persistent,
                                      cache=False,
                                      debugging_id=debugging_id)

        self._expected_mutant_dict.cleanup()

    def _analyze_persistent(self, freq, response):
        """
        Analyze the response of sending each fuzzable request found by the
        framework, trying to identify any locations where we might have injected
        a payload.

        :param freq: The fuzzable request
        :param response: The HTTP response
        :return: None, vulns are stored in KB
        """
        for matched_expected_result in self._persistent_multi_in.query(response.get_body()):
            # We found one of the expected results, now we search the
            # self._expected_mutant_dict to find which of the mutants sent it
            # and create the vulnerability
            mutant = self._expected_mutant_dict[matched_expected_result]

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

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

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

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Exemplo n.º 25
0
class sqli(AuditPlugin):
    """
    Find SQL injection bugs.
    :author: Andres Riancho ([email protected])
    """
    SQL_ERRORS_STR = (
        # ASP / MSSQL
        (r'System.Data.OleDb.OleDbException', dbms.MSSQL),
        (r'[SQL Server]', dbms.MSSQL),
        (r'[Microsoft][ODBC SQL Server Driver]', dbms.MSSQL),
        (r'[SQLServer JDBC Driver]', dbms.MSSQL),
        (r'[SqlException', dbms.MSSQL),
        (r'System.Data.SqlClient.SqlException', dbms.MSSQL),
        (r'Unclosed quotation mark after the character string', dbms.MSSQL),
        (r"'80040e14'", dbms.MSSQL),
        (r'mssql_query()', dbms.MSSQL),
        (r'odbc_exec()', dbms.MSSQL),
        (r'Microsoft OLE DB Provider for ODBC Drivers', dbms.MSSQL),
        (r'Microsoft OLE DB Provider for SQL Server', dbms.MSSQL),
        (r'Incorrect syntax near', dbms.MSSQL),
        (r'Sintaxis incorrecta cerca de', dbms.MSSQL),
        (r'Syntax error in string in query expression', dbms.MSSQL),
        (r'ADODB.Field (0x800A0BCD)<br>', dbms.MSSQL),
        (r"ADODB.Recordset'", dbms.MSSQL),
        (r"Unclosed quotation mark before the character string", dbms.MSSQL),
        (r"'80040e07'", dbms.MSSQL),
        (r'Microsoft SQL Native Client error', dbms.MSSQL),
        (r'SQL Server Native Client', dbms.MSSQL),
        (r'Invalid SQL statement', dbms.MSSQL),

        # DB2
        (r'SQLCODE', dbms.DB2),
        (r'DB2 SQL error:', dbms.DB2),
        (r'SQLSTATE', dbms.DB2),
        (r'[CLI Driver]', dbms.DB2),
        (r'[DB2/6000]', dbms.DB2),

        # Sybase
        (r"Sybase message:", dbms.SYBASE),
        (r"Sybase Driver", dbms.SYBASE),
        (r"[SYBASE]", dbms.SYBASE),

        # Access
        (r'Syntax error in query expression', dbms.ACCESS),
        (r'Data type mismatch in criteria expression.', dbms.ACCESS),
        (r'Microsoft JET Database Engine', dbms.ACCESS),
        (r'[Microsoft][ODBC Microsoft Access Driver]', dbms.ACCESS),

        # ORACLE
        (r'Microsoft OLE DB Provider for Oracle', dbms.ORACLE),
        (r'wrong number or types', dbms.ORACLE),

        # POSTGRE
        (r'PostgreSQL query failed:', dbms.POSTGRE),
        (r'supplied argument is not a valid PostgreSQL result', dbms.POSTGRE),
        (r'unterminated quoted string at or near', dbms.POSTGRE),
        (r'pg_query() [:', dbms.POSTGRE),
        (r'pg_exec() [:', dbms.POSTGRE),

        # MYSQL
        (r'supplied argument is not a valid MySQL', dbms.MYSQL),
        (r'Column count doesn\'t match value count at row', dbms.MYSQL),
        (r'mysql_fetch_array()', dbms.MYSQL),
        (r'mysql_', dbms.MYSQL),
        (r'on MySQL result index', dbms.MYSQL),
        (r'You have an error in your SQL syntax;', dbms.MYSQL),
        (r'You have an error in your SQL syntax near', dbms.MYSQL),
        (r'MySQL server version for the right syntax to use', dbms.MYSQL),
        (r'Division by zero in', dbms.MYSQL),
        (r'not a valid MySQL result', dbms.MYSQL),
        (r'[MySQL][ODBC', dbms.MYSQL),
        (r"Column count doesn't match", dbms.MYSQL),
        (r"the used select statements have different number of columns",
         dbms.MYSQL),
        (r"DBD::mysql::st execute failed", dbms.MYSQL),
        (r"DBD::mysql::db do failed:", dbms.MYSQL),

        # Informix
        (r'com.informix.jdbc', dbms.INFORMIX),
        (r'Dynamic Page Generation Error:', dbms.INFORMIX),
        (r'An illegal character has been found in the statement',
         dbms.INFORMIX),
        (r'[Informix]', dbms.INFORMIX),
        (r'<b>Warning</b>:  ibase_', dbms.INTERBASE),
        (r'Dynamic SQL Error', dbms.INTERBASE),

        # DML
        (r'[DM_QUERY_E_SYNTAX]', dbms.DMLDATABASE),
        (r'has occurred in the vicinity of:', dbms.DMLDATABASE),
        (r'A Parser Error (syntax error)', dbms.DMLDATABASE),

        # Java
        (r'java.sql.SQLException', dbms.JAVA),
        (r'Unexpected end of command in statement', dbms.JAVA),

        # Coldfusion
        (r'[Macromedia][SQLServer JDBC Driver]', dbms.MSSQL),

        # SQLite
        (r'could not prepare statement', dbms.SQLITE),

        # Generic errors..
        (r'Unknown column', dbms.UNKNOWN),
        (r'where clause', dbms.UNKNOWN),
        (r'SqlServer', dbms.UNKNOWN),
        (r'syntax error', dbms.UNKNOWN),
        (r'Microsoft OLE DB Provider', dbms.UNKNOWN),
    )
    _multi_in = MultiIn(x[0] for x in SQL_ERRORS_STR)

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

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

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

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

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

        :param freq: A FuzzableRequest
        :param orig_response: The HTTP response associated with the fuzzable request
        :param debugging_id: A unique identifier for this call to audit()
        """
        mutants = create_mutants(freq,
                                 self.SQLI_STRINGS,
                                 orig_resp=orig_response)

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

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

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

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

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

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

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

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

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

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

        return res

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Exemplo n.º 26
0
class ldapi(AuditPlugin):
    """
    Find LDAP injection bugs.
    :author: Andres Riancho ([email protected])
    """

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

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

        # PHP
        'Bad search filter',

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

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

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

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

    _multi_in = MultiIn(LDAP_ERRORS)

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

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

        :param freq: A FuzzableRequest
        :param orig_response: The HTTP response associated with the fuzzable request
        :param debugging_id: A unique identifier for this call to audit()
        """
        mutants = create_mutants(freq,
                                 self.LDAPI_STRINGS,
                                 orig_resp=orig_response)

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

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

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

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

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

                    v.add_to_highlight(ldap_error_string)

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

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

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

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

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Exemplo n.º 27
0
    def test_dup(self):
        in_list = ['123', '456', '789']
        imi = MultiIn(in_list)

        result = to_list(imi.query('456 456'))
        self.assertEqual(1, len(result))
Exemplo n.º 28
0
class error_pages(GrepPlugin):
    """
    Grep every page for error pages.

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

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

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

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

        # nwwcgi errors
        '<TITLE>nwwcgi Error',

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

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

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

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

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

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

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

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

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

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

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

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

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

    MAX_REPORTED_PER_MSG = 10

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

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

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

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

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

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

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

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

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

            title = 'Descriptive error page'

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

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

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

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

        if count < self.MAX_REPORTED_PER_MSG:
            return False

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

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

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

        return True

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

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

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

        self._potential_vulns.cleanup()

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

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

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

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

                    self._already_reported_versions.append(match_string)

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Exemplo n.º 29
0
 def test_is_generator(self):
     in_list = ['123', '456', '789']
     imi = MultiIn(in_list)
     results = imi.query('456')
     self.assertIsInstance(results, types.GeneratorType)
Exemplo n.º 30
0
class xxe(AuditPlugin):
    """
    Find XXE vulnerabilities.

    :author: Andres Riancho ([email protected])
    """
    WINDOWS_FILES = [
        '%SYSTEMDRIVE%\\boot.ini',
        '%WINDIR%\\win.ini',
    ]

    LINUX_FILES = [
        '/etc/passwd',
    ]

    REMOTE_FILES = ['http://w3af.org/xxe.txt']

    # This is the only content stored in the https://w3af.org/xxe.txt file
    REMOTE_SUCCESS = '667067323'

    ENTITY_DEF = '<!DOCTYPE xxe_test [ <!ENTITY xxe_test SYSTEM "%s"> ]>'
    ENTITY = '&xxe_test;'

    GENERIC_PAYLOADS = [
        # This is the most effective payload I've found until now, tested using
        # libxml (python wrapper, but should apply to all libxml versions).
        '<!DOCTYPE xxe_test [ <!ENTITY xxe_test SYSTEM "%s"> ]><x>&xxe_test;</x>',
        '<?xml version="1.0" encoding="ISO-8859-1"?>'
        '<!DOCTYPE xxe_test [ <!ENTITY xxe_test SYSTEM "%s"> ]><x>&xxe_test;</x>',
        '<?xml version="1.0" encoding="ISO-8859-1"?>'
        '<!DOCTYPE xxe_test [<!ELEMENT foo ANY><!ENTITY xxe_test SYSTEM "%s">]>'
        '<foo>&xxe_test;</foo>',
    ]

    LINUX_PAYLOADS = [
        '<!DOCTYPE xxe_test [ <!ENTITY xxe_test SYSTEM "file://%s"> ]><x>&xxe_test;</x>',
    ]

    WINDOWS_PAYLOADS = [
        # Note that this one uses file:/// instead of file://
        '<!DOCTYPE xxe_test [ <!ENTITY xxe_test SYSTEM "file:///%s"> ]><x>&xxe_test;</x>',
    ]

    XML_PARSER_ERRORS = [
        # PHP
        'xmlParseEntityDecl',
        'simplexml_load_string',
        'xmlParseInternalSubset',
        'DOCTYPE improperly terminated',
        'Start tag expected',
        'No declaration for attribute',
        'No declaration for element',

        # libxml and python
        'failed to load external entity',
        'Start tag expected',
        'Invalid URI: file:///',
        'Malformed declaration expecting version',
        'Unicode strings with encoding',

        # java
        'must be well-formed',
        'Content is not allowed in prolog',
        'org.xml.sax',
        'SAXParseException',
        'com.sun.org.apache.xerces',

        # ruby
        'ParseError',
        'nokogiri',
        'REXML',

        # golang
        'XML syntax error on line',
        'Error unmarshaling XML',
        'conflicts with field',
        'illegal character code'

        # .NET
        'XML Parsing Error',
        'SyntaxError',
        'no root element',
        'not well-formed',
    ]

    MAX_XML_PARAM_MUTANTS = 5
    TOKEN_XXE = '__TOKEN_XXE1__'

    file_pattern_multi_in = MultiIn(FILE_PATTERNS)
    parser_errors_multi_in = MultiIn(XML_PARSER_ERRORS)

    def _should_inject_parameter(self, param_name, param_value):
        """
        The objective is to reduce the number of HTTP requests sent by this plugin.

        The idea is simple, only try to inject into a parameter if the parameter
        is empty or contains something that looks like XML.

        :param param_name: The parameter name
        :param param_value: The original value of the parameter
        :return: True if we should inject into this parameter
        """
        if not param_value:
            return True

        if 'xml' in param_name.lower():
            return True

        if '<' in param_value and '>' in param_value:
            return True

        return False

    def _create_payloads(self, param_name, original_value):
        """
        Use the class attributes to create all the payloads, yield them using
        an iterator.

        :yield: Payloads as strings
        """
        #
        # First we send the generic tests, which don't take the original value
        # into account and are likely to work on some cases
        #
        for file_name in itertools.chain(self.WINDOWS_FILES, self.LINUX_FILES,
                                         self.REMOTE_FILES):
            for payload in self.GENERIC_PAYLOADS:
                yield payload % file_name

        for file_name in self.LINUX_FILES:
            for payload in self.LINUX_PAYLOADS:
                yield payload % file_name

        for file_name in self.WINDOWS_FILES:
            for payload in self.WINDOWS_PAYLOADS:
                yield payload % file_name

        #
        # Now we parse the original value using our XML parser, and modify that
        # XML in order to inject the payloads there. In order to do that, we
        # need the original value to be an xml document
        #
        if not original_value:
            return

        xml_root = self._parse_xml(param_name, original_value)
        if xml_root is not None:
            for payload in self._create_xml_payloads(xml_root):
                yield payload

    def _create_xml_payloads(self, xml_root):
        """
        This method receives the XML as captured by w3af during crawling and
        modifies it to add entities which will inject file contents

        Note that we can't use a generic "create xml mutants" since this method
        needs to do two different things:

            * Add the entity at the beginning of the XML
            * Add the entity reference as a tag text

        :param xml_root: The xml object as parsed by etree.fromstring
        :return: A string representing the xml object, with added entities
        """
        xml_mutant_count = 0

        for tag in xml_root.iter():
            if tag.text is None:
                continue

            tag_orig = tag.text
            tag.text = self.TOKEN_XXE

            for file_name in itertools.chain(self.WINDOWS_FILES,
                                             self.LINUX_FILES):
                dtd = self.ENTITY_DEF % file_name
                xml_body = etree.tostring(xml_root).replace(
                    self.TOKEN_XXE, self.ENTITY)
                yield dtd + xml_body

            # Restore the original value to inject in the next parameter
            tag.text = tag_orig

            # Test XXE in the first MAX_XML_PARAM_MUTANTS parameters found in the XML
            xml_mutant_count += 1
            if xml_mutant_count > self.MAX_XML_PARAM_MUTANTS:
                break

    def _parse_xml(self, param_name, original_value):
        """
        Parse the XML into an object

        :param param_name: The name of the parameter as seen by the HTML parser
        :param original_value: The XML as sent by the application
        :return: The XML object or None if parsing failed
        """
        # This is a safety measure to prevent us from loading large XML files
        # into memory (high memory usage) or loading a very complex xml which
        # might require a lot of CPU time
        if len(original_value) > 1024 * 1024:
            return None

        try:
            original_value_str = smart_str_ignore(original_value)
        except Exception, e:
            msg = ('Failed to encode unicode original value to string'
                   ' in _parse_xml(). Exception: "%s"')
            om.out.debug(msg % e)
            return None

        # Secure, don't introduce XXE in our XXE detection plugin ;-)
        parser = etree.XMLParser(load_dtd=False,
                                 no_network=True,
                                 resolve_entities=False)

        try:
            xml_root = etree.fromstring(original_value_str, parser=parser)
        except Exception, e:
            msg = ('Failed to parse "%s..." as XML to inject XXE tests.'
                   ' The parameter name where injection failed was "%s".'
                   ' Exception: "%s"')
            args = (original_value[:25], param_name, e)
            om.out.debug(msg % args)
            return None
Exemplo n.º 31
0
class mx_injection(AuditPlugin):
    """
    Find MX injection vulnerabilities.
    :author: Andres Riancho ([email protected])
    """
    MX_PAYLOADS = ['"', 'iDontExist', '']

    MX_ERRORS = (
        'Unexpected extra arguments to Select',
        'Bad or malformed request',
        'Could not access the following folders',
        # Removing! Too many false positives...
        # 'A000',
        # 'A001',
        'Invalid mailbox name',
        'To check for outside changes to the folder list go to the folders page',
        'go to the folders page',
        'Query: SELECT',
        'Query: FETCH',
        'IMAP command')
    _multi_in = MultiIn(MX_ERRORS)

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

        Plugin added just for completeness... I don't really expect to find one
        of this bugs in my life... but well.... if someone , somewhere in the
        planet ever finds a bug of using this plugin... THEN my job has been
        done :P

        :param freq: A FuzzableRequest
        :param orig_response: The HTTP response associated with the fuzzable request
        :param debugging_id: A unique identifier for this call to audit()
        """
        mutants = create_mutants(freq,
                                 self.MX_PAYLOADS,
                                 orig_resp=orig_response)

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

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

        for mx_error in self._multi_in.query(response.body):
            if mx_error in mutant.get_original_response_body():
                continue

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

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

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

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Exemplo n.º 32
0
class phishtank(CrawlPlugin):
    """
    Search the phishtank.com database to determine if your server is (or was)
    being used in phishing scams.

    :author: Andres Riancho ([email protected])
    :author: Special thanks to http://www.phishtank.com/ !
    """
    PHISHTANK_DB = os.path.join(ROOT_PATH, 'plugins', 'crawl', 'phishtank',
                                'index.csv')

    def __init__(self):
        CrawlPlugin.__init__(self)
        self._multi_in = None

    @runonce(exc_class=RunOnce)
    def crawl(self, fuzzable_request, debugging_id):
        """
        Plugin entry point, performs all the work.

        :param debugging_id: A unique identifier for this call to discover()
        :param fuzzable_request: A fuzzable_request instance that contains
                                    (among other things) the URL to test.
        """
        to_check = self._get_to_check(fuzzable_request.get_url())

        # I found some URLs, create fuzzable requests
        pt_matches = self._is_in_phishtank(to_check)

        if not pt_matches:
            return

        for ptm in pt_matches:
            fr = FuzzableRequest(ptm.url)
            self.output_queue.put(fr)

        desc = ('The URL: "%s" seems to be involved in a Phishing scam.'
                ' Please see %s for more info.')
        desc %= (ptm.url, ptm.more_info_url)

        v = Vuln('Phishing scam', desc, severity.MEDIUM, [], self.get_name())
        v.set_url(ptm.url)

        kb.kb.append(self, 'phishtank', v)
        om.out.vulnerability(v.get_desc(), severity=v.get_severity())

    def _get_to_check(self, target_url):
        """
        :param target_url: The url object we can use to extract some information
        :return: From the domain, get a list of FQDN, rootDomain and IP address.
        """
        def addrinfo(url):
            return [x[4][0] for x in socket.getaddrinfo(url.get_domain(), 0)]

        def getfqdn(url):
            return [socket.getfqdn(url.get_domain()), ]

        def root_domain(url):
            if not is_ip_address(url.get_domain()):
                return [url.get_root_domain(), ]
            
            return []

        res = set()
        for func in (addrinfo, getfqdn, root_domain):
            try:
                data_lst = func(target_url)
            except Exception:
                pass
            else:
                for data in data_lst:
                    res.add(data)

        return res

    def _is_in_phishtank(self, to_check):
        """
        Reads the phishtank db and tries to match the entries on that db with
        the to_check

        :return: A list with the sites to match against the phishtank db
        """
        try:
            phishtank_db_fd = file(self.PHISHTANK_DB, 'r')
        except Exception, e:
            msg = 'Failed to open phishtank database: "%s", exception: "%s".'
            raise BaseFrameworkException(msg % (self.PHISHTANK_DB, e))

        pt_matches = []
        self._multi_in = MultiIn(to_check)

        om.out.debug('Starting the phishtank CSV parsing.')

        pt_csv_reader = csv.reader(phishtank_db_fd, delimiter=' ',
                                   quotechar='|', quoting=csv.QUOTE_MINIMAL)

        for phishing_url, phishtank_detail_url in pt_csv_reader:
            pt_match = self._url_matches(phishing_url, phishtank_detail_url)
            if pt_match:
                pt_matches.append(pt_match)

        om.out.debug('Finished CSV parsing.')

        return pt_matches
Exemplo n.º 33
0
class lfi(AuditPlugin):
    """
    Find local file inclusion vulnerabilities.
    :author: Andres Riancho ([email protected])
    """

    file_pattern_multi_in = MultiIn(FILE_PATTERNS)
    file_read_error_multi_re = MultiRE(FILE_OPEN_ERRORS)

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

        :param freq: A FuzzableRequest
        :param orig_response: The HTTP response associated with the fuzzable request
        :param debugging_id: A unique identifier for this call to audit()
        """
        mutants = create_mutants(freq,
                                 self.get_lfi_tests(freq),
                                 orig_resp=orig_response)

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

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

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

        return lfi_tests

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

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

        extension = orig_url.get_extension()

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

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

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

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

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

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

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

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

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

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

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

        return local_files

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

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

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

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

                v['file_pattern'] = file_pattern_match

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

        #
        # If the vulnerability could not be identified by matching strings that
        # commonly appear in "/etc/passwd", then I'll check one more thing...
        # (note that this is run if no vulns were identified)
        #
        # http://host.tld/show_user.php?id=show_user.php
        #
        # The calls to smart_str_ignore fix a UnicodeDecoreError which appears when
        # the token value is a binary string which can't be converted to unicode.
        # This happens, for example, when trying to upload JPG files to a multipart form
        #
        # >>> u'' in '\x80'
        # ...
        # UnicodeDecodeError: 'ascii' codec can't decode byte 0x80 in position 0: ordinal not in range(128)
        #
        filename = smart_str_ignore(mutant.get_url().get_file_name())
        token_value = smart_str_ignore(mutant.get_token_value())

        if filename in token_value:
            match, lang = contains_source_code(response)
            if match:
                # We were able to read the source code of the file that is
                # vulnerable to local file read
                desc = ('An arbitrary local file read vulnerability was'
                        ' found at: %s')
                desc %= mutant.found_at()

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

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

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

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

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

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

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

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

        for file_pattern_match in self.file_pattern_multi_in.query(body):
            res.add(file_pattern_match)

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

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

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

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

            om.out.debug(msg)

        return res

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Exemplo n.º 34
0
class buffer_overflow(AuditPlugin):
    """
    Find buffer overflow vulnerabilities.
    :author: Andres Riancho ([email protected])
    """

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

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

    _multi_in = MultiIn(OVERFLOW_ERRORS)

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

    def __init__(self):
        """
        Some notes:
            On Apache, when an overflow happends on a cgic script, this is
            written to the log:
                *** stack smashing detected ***:

                    /var/www/.../buffer_overflow.cgi terminated,
                    referer: http://localhost/w3af/buffer_overflow.cgi

                    Premature end of script headers: buffer_overflow.cgi,
                    referer: ...

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

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

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

        :param freq: A FuzzableRequest
        :param orig_response: The HTTP response associated with the fuzzable request
        :param debugging_id: A unique identifier for this call to audit()
        """
        mutants = create_mutants(freq,
                                 self.BUFFER_TESTS,
                                 orig_resp=orig_response)
        args = zip(repeat(self._send_request), mutants, repeat(debugging_id))

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

    def _send_request(self, mutant, debugging_id):
        """
        Sends a mutant to the remote web server. I wrap urllib's _send_mutant
        just to handle errors in a different way.
        """
        # Only grep the request which sends the larger payload
        grep = mutant.get_token_value() == self.BUFFER_TESTS[-1]

        try:
            response = self._uri_opener.send_mutant(mutant,
                                                    debugging_id=debugging_id,
                                                    grep=grep)
        except (BaseFrameworkException, ScanMustStopException):
            desc = ('A potential (most probably a false positive than a bug)'
                    ' buffer-overflow was found when requesting: "%s", using'
                    ' HTTP method %s. The data sent was: "%s".')
            desc %= (mutant.get_url(), mutant.get_method(), mutant.get_dc())

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

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

    def _analyze_result(self, mutant, response):
        """
        Analyze results of the _send_mutant method.
        """
        for error_str in self._multi_in.query(response.body):

            if error_str in mutant.get_original_response_body():
                continue

            if self._has_bug(mutant):
                continue

            desc = ('A potential buffer overflow (accurate detection is'
                    ' hard) was found at: %s')
            desc %= mutant.found_at()

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

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

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

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Exemplo n.º 35
0
class keys(GrepPlugin):
    """
    Grep every page for public and private keys.

    :author: Yvonne Kim
    """
    def __init__(self):
        GrepPlugin.__init__(self)

        self.PUBLIC = 'public'
        self.PRIVATE = 'private'

        PUBLIC = 'public'
        PRIVATE = 'private'

        KEY_FORMATS = (
            # RSA (PKCS1)
            ('-----BEGIN RSA PRIVATE KEY-----', ('RSA-PRIVATE', PRIVATE)), 
            ('-----BEGIN RSA PUBLIC KEY-----', ('RSA-PUBLIC', PUBLIC)),
            ('ssh-rsa', ('RSA-PUBLIC', PUBLIC)),
            
            # DSA
            ('-----BEGIN DSA PRIVATE KEY-----', ('DSA-PRIVATE', PRIVATE)),
            ('-----BEGIN DSA PUBLIC KEY-----', ('DSA-PUBLIC', PUBLIC)),
            ('ssh-dss', ('DSA-PUBLIC', PUBLIC)),
            
            # Elliptic Curve
            ('-----BEGIN EC PRIVATE KEY-----', ('EC-PRIVATE', PRIVATE)),
            ('-----BEGIN EC PUBLIC KEY-----', ('EC-PUBLIC', PUBLIC)),
            ('ecdsa-sha2-nistp256', ('EC-PUBLIC', PUBLIC)),
            
            # SSH2
            ('---- BEGIN SSH2 PUBLIC KEY ----', ('SSH2-PRIVATE', PRIVATE)),
            ('---- BEGIN SSH2 PRIVATE KEY ----', ('SSH2-PUBLIC', PUBLIC)),

            # ed25519 (OpenSSH)
            ('-----BEGIN OPENSSH PRIVATE KEY-----', ('ED25519-SSH-PRIVATE', PRIVATE)),
            ('-----BEGIN OPENSSH PUBLIC KEY-----', ('ED25519-SSH-PUBLIC', PUBLIC)),
            ('ssh-ed25519', ('ED25519-SSH-PUBLIC', PUBLIC)),
            
            # PKCS8
            ('-----BEGIN PRIVATE KEY-----', ('PKCS8-PRIVATE', PRIVATE)),
            ('-----BEGIN PUBLIC KEY-----', ('PKCS8-PUBLIC', PUBLIC)),
            ('-----BEGIN ENCRYPTED PRIVATE KEY-----', ('PKCS8-ENCRYPTED-PRIVATE', PRIVATE)),
            ('-----BEGIN ENCRYPTED PUBLIC KEY-----', ('PKCS8-ENCRYPTED-PUBLIC', PUBLIC)),
            
            # XML
            ('<RSAKeyPair>', ('XML-RSA', PRIVATE)),
            ('<RSAKeyValue>', ('.NET-XML-RSA', PUBLIC))
        )        

        self._multi_in = MultiIn(KEY_FORMATS)


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

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

        if not response.get_code() == 200:
            return

        for _, (key, keypair_type) in self._multi_in.query(response.body):
            desc = u'The URL: "%s" discloses a key of type: "%s"'
            desc %= (response.get_url(), key)

            if keypair_type == self.PUBLIC:
                item = Info(
                    'Public key disclosure', desc, response.id, self.get_name())

            elif keypair_type == self.PRIVATE:
                item = Vuln(
                    'Private key disclosure', desc, severity.HIGH, response.id, self.get_name())                

            item.set_url(response.get_url())
            item.add_to_highlight(key)

            self.kb_append(self, 'keys', item)
        

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

        return """
Exemplo n.º 36
0
class html_comments(GrepPlugin):
    """
    Extract and analyze HTML comments.

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

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

    HTML_FALSE_POSITIVES = {
        '[if IE]',
        '[if !IE]',
        '[if IE 7 ]',
        '[if IE 8 ]',
        '[if IE 9]',
        '[if lte IE 8]',
        '[if lte IE 9]',
    }

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

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

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

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

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

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

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

        for comment in dp.get_comments():
            self._interesting_word(comment, request, response)
            self._html_in_comment(comment, request, response)

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

        for word in self._multi_in.query(lower_comment):
            # These next two lines fix a false positive which appears when
            # audit.ssi sends a payload to a site which has XSS, and
            # grep.html_comments sees that comment and reports it.
            if request.sent(comment):
                continue

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

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

            self.kb_append_uniq_group(self,
                                      'interesting_comments',
                                      i,
                                      group_klass=HTMLCommentHidesHTMLInfoSet)

    def _html_in_comment(self, comment, request, response):
        """
        Find HTML code in HTML comments
        """
        #
        # Check if HTML code is present in this comment
        #
        html_in_comment = self.HTML_RE.search(comment)

        if html_in_comment is None:
            return

        #
        # Remove false positives
        #
        for false_positive_string in self.HTML_FALSE_POSITIVES:
            if false_positive_string in comment:
                return

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

        desc = ('A comment containing HTML code "%s" was found in: "%s".'
                ' This could be interesting.')
        desc %= (comment, response.get_url())

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

        self.kb_append_uniq_group(self,
                                  'html_comment_hides_html',
                                  i,
                                  group_klass=HTMLCommentHidesHTMLInfoSet)

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Exemplo n.º 37
0
    def test_dup(self):
        in_list = ['123', '456', '789']
        imi = MultiIn(in_list)

        result = to_list(imi.query('456 456'))
        self.assertEqual(1, len(result))
Exemplo n.º 38
0
class wsdl_greper(GrepPlugin):
    """
    Grep every page for web service definition files.

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

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

        self._disco_strings = ['disco:discovery ']

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

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

    def analyze_wsdl(self, request, response):
        for match in self._multi_in.query(response.body):
            desc = ('The URL: "%s" is a Web Services Description Language'
                    ' page. This requires manual analysis to determine the'
                    ' security of the web service.')
            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)

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

    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 %= response.get_url()
                i = Info('DISCO resource', desc, response.id, self.get_name())
                i.set_url(response.get_url())
                i.add_to_highlight(disco_string)

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

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Exemplo n.º 39
0
class os_commanding(AuditPlugin):
    """
    Find OS Commanding vulnerabilities.
    :author: Andres Riancho ([email protected])
    """

    FILE_PATTERNS = FILE_PATTERNS
    _multi_in = MultiIn(FILE_PATTERNS)

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

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

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

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

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

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

        # Create the mutants, notice that we use append=False (default) and
        # True to have better coverage.
        mutants = create_mutants(freq,
                                 only_command_strings,
                                 orig_resp=orig_response)
        mutants.extend(
            create_mutants(freq,
                           only_command_strings,
                           orig_resp=orig_response,
                           append=True))

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

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

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

            if file_pattern_match in mutant.get_original_response_body():
                continue

            # Search for the correct command and separator
            sent_os, sent_separator = self._get_os_separator(mutant)

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

            v['os'] = sent_os
            v['separator'] = sent_separator
            v.add_to_highlight(file_pattern_match)

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

    def _get_os_separator(self, mutant):
        """
        :param mutant: The mutant that is being analyzed.
        :return: A tuple with the OS and the command separator
        that was used to generate the mutant.
        """
        os = separator = None

        # Retrieve the data I need to create the vuln and the info objects
        command_list = self._get_echo_commands()

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

        return os, separator

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

        :param freq: A FuzzableRequest
        """
        self._send_mutants_in_threads(func=self._find_delay_in_mutant,
                                      iterable=self._generate_delay_tests(
                                          freq, debugging_id),
                                      callback=lambda x, y: None)

    def _generate_delay_tests(self, freq, debugging_id):
        fake_mutants = create_mutants(freq, [
            '',
        ])
        fake_mutants.extend(create_mutants(freq, [
            '',
        ], append=True))

        for mutant in fake_mutants:
            #
            # Don't try to find an OS commanding using a time delay method
            # if we already found it via echo
            #
            if self._has_bug(mutant):
                return

            for delay_obj in self._get_wait_commands():
                yield mutant, delay_obj, debugging_id

    def _find_delay_in_mutant(self, (mutant, delay_obj, debugging_id)):
        """
        Try to delay the response and save a vulnerability if successful

        :param mutant: The mutant to modify and test
        :param delay_obj: The delay to use
        :param debugging_id: The debugging ID for logging
        """
        if self._has_bug(mutant):
            return

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

        if not success:
            return

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

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

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

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