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])
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])
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])
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])
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])
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])
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])
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])
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])
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])
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])
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])
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])
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))
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 """
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 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 def _url_matches(self, phishing_url, phishtank_detail_url): """ :param url: The url (as string) from the phishtank database :return: A PhishTankMatch if url matches what we're looking for, None if there is no match """ for query_result in self._multi_in.query(phishing_url): phish_url = URL(phishing_url) target_host_url = URL(query_result[0]) if target_host_url.get_domain() == phish_url.get_domain() or \ phish_url.get_domain().endswith('.' + target_host_url.get_domain()): phish_detail_url = URL(phishtank_detail_url) ptm = PhishTankMatch(phish_url, phish_detail_url) return ptm return None def get_long_desc(self): """ :return: A DETAILED description of the plugin functions and features. """ return """
def test_is_generator(self): in_list = ['123', '456', '789'] imi = MultiIn(in_list) results = imi.query('456') self.assertIsInstance(results, types.GeneratorType)
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))
def test_is_generator(self): in_list = ['123', '456', '789'] imi = MultiIn(in_list) results = imi.query('456') self.assertIsInstance(results, types.GeneratorType)
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 """
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 """