def _generic_vhosts(self, fuzzable_request): """ Test some generic virtual hosts, only do this once. """ # Get some responses to compare later base_url = fuzzable_request.get_url().base_url() original_response = self._uri_opener.GET(base_url, cache=True) orig_resp_body = original_response.get_body() non_existent_response = self._get_non_exist(fuzzable_request) nonexist_resp_body = non_existent_response.get_body() res = [] vhosts = self._get_common_virtualhosts(base_url) for vhost, vhost_response in self._send_in_threads(base_url, vhosts): vhost_resp_body = vhost_response.get_body() # If they are *really* different (not just different by some chars) if fuzzy_not_equal(vhost_resp_body, orig_resp_body, 0.35) and fuzzy_not_equal( vhost_resp_body, nonexist_resp_body, 0.35 ): res.append((vhost, vhost_response.id)) return res
def _generic_vhosts(self, fuzzable_request): """ Test some generic virtual hosts, only do this once. """ # Get some responses to compare later base_url = fuzzable_request.get_url().base_url() original_response = self._uri_opener.GET(base_url, cache=True) orig_resp_body = original_response.get_body() non_existent_response = self._get_non_exist(fuzzable_request) non_existent_resp_body = non_existent_response.get_body() res = [] vhosts = self._get_common_virtual_hosts(base_url) for vhost, vhost_response in self._send_in_threads(base_url, vhosts): vhost_resp_body = vhost_response.get_body() # If they are *really* different (not just different by some chars) if not fuzzy_not_equal(vhost_resp_body, orig_resp_body, 0.35): continue if not fuzzy_not_equal(vhost_resp_body, non_existent_resp_body, 0.35): continue res.append((vhost, vhost_response.id)) return res
def _check_user_dir(self, mutated_url, user, user_desc, user_tag, non_existent): """ Perform the request and compare with non_existent :see _create_tests: For parameter description :return: The HTTP response id if the mutated_url is a web user directory, None otherwise. """ resp = self.http_get_and_parse(mutated_url) path = mutated_url.get_path() response_body = resp.get_body().replace(path, '') if fuzzy_not_equal(response_body, non_existent, 0.7): # Avoid duplicates known_users = [u['user'] for u in kb.kb.get('user_dir', 'users')] if user in known_users: return # Save the finding to the KB desc = 'An operating system user directory was found at: "%s"' desc = desc % resp.get_url() i = Info('Web user home directory', desc, resp.id, self.get_name()) i.set_url(resp.get_url()) i['user'] = user i['user_desc'] = user_desc i['user_tag'] = user_tag self.kb_append_uniq(self, 'users', i) # Analyze if we can get more information from this finding self._analyze_finding(i)
def _test_DNS(self, original_response, dns_wildcard_url): """ Check if http://www.domain.tld/ == http://domain.tld/ """ headers = Headers([('Host', dns_wildcard_url.get_domain())]) try: modified_response = self._uri_opener.GET( original_response.get_url(), cache=True, headers=headers) except BaseFrameworkException: return else: if fuzzy_not_equal(modified_response.get_body(), original_response.get_body(), 0.35): desc = 'The target site has NO DNS wildcard, and the contents' \ ' of "%s" differ from the contents of "%s".' desc = desc % (dns_wildcard_url, original_response.get_url()) i = Info('No DNS wildcard', desc, modified_response.id, self.get_name()) i.set_url(dns_wildcard_url) kb.kb.append(self, 'dns_wildcard', i) om.out.information(i.get_desc()) else: desc = 'The target site has a DNS wildcard configuration, the' \ ' contents of "%s" are equal to the ones of "%s".' desc = desc % (dns_wildcard_url, original_response.get_url()) i = Info('DNS wildcard', desc, modified_response.id, self.get_name()) i.set_url(original_response.get_url()) kb.kb.append(self, 'dns_wildcard', i) om.out.information(i.get_desc())
def _do_request(self, mutated_url, user): """ Perform the request and compare. :return: The HTTP response id if the mutated_url is a web user directory, None otherwise. """ response = self._uri_opener.GET(mutated_url, cache=True, headers=self._headers) path = mutated_url.get_path() response_body = response.get_body().replace(path, '') if fuzzy_not_equal(response_body, self._non_existent, 0.7): # Avoid duplicates if user not in [u['user'] for u in kb.kb.get('user_dir', 'users')]: desc = 'A user directory was found at: %s' desc = desc % response.get_url() i = Info('Web user home directory', desc, response.id, self.get_name()) i.set_url(response.get_url()) i['user'] = user kb.kb.append(self, 'users', i) for fr in self._create_fuzzable_requests(response): self.output_queue.put(fr) return response.id return None
def _analyze_generic_vhosts(self, original_response): """ Test some generic virtual hosts. """ orig_resp_body = original_response.get_body() non_existent_response = self._get_non_exist() nonexist_resp_body = non_existent_response.get_body() res = [] vhosts = self._get_common_virtualhosts() for vhost, vhost_response in self._send_in_threads(vhosts): vhost_resp_body = vhost_response.get_body() # If they are *really* different (not just different by some chars) if fuzzy_not_equal(vhost_resp_body, orig_resp_body, 0.35) and \ fuzzy_not_equal(vhost_resp_body, nonexist_resp_body, 0.35): res.append((vhost, vhost_response.id)) return res
def _check_existance(self, original_response, mutant): """ Actually check if the mutated URL exists. :return: None, all important data is put() to self.output_queue """ response = self._uri_opener.send_mutant(mutant) if not is_404(response) and \ fuzzy_not_equal(original_response.body, response.body, 0.85): # Verify against something random rand = rand_alpha() rand_mutant = mutant.copy() rand_mutant.set_mod_value(rand) rand_response = self._uri_opener.send_mutant(rand_mutant) if fuzzy_not_equal(response.body, rand_response.body, 0.85): for fr in self._create_fuzzable_requests(response): self.output_queue.put(fr)
def _do_request(self, fuzzable_request, original_resp, headers): """ Send the request. :param fuzzable_request: The modified fuzzable request :param original_resp: The response for the original request that was sent. """ response = self._uri_opener.GET(fuzzable_request.get_uri(), cache=True, headers=headers) add = False if not is_404(response): # We have different cases: # - If the URLs are different, then there is nothing to think # about, we simply found something new! if response.get_url() != original_resp.get_url(): add = True # - If the content type changed, then there is no doubt that # we've found something new! elif response.doc_type != original_resp.doc_type: add = True # - If we changed the query string parameters, we have to check # the content elif fuzzy_not_equal(response.get_clear_text_body(), original_resp.get_clear_text_body(), 0.8): # In this case what might happen is that the number we changed # is "out of range" and when requesting that it will trigger an # error in the web application, or show us a non-interesting # response that holds no content. # # We choose to return these to the core because they might help # with the code coverage efforts. Think about something like: # foo.aspx?id=OUT_OF_RANGE&foo=inject_here # vs. # foo.aspx?id=IN_RANGE&foo=inject_here # # This relates to the EXPECTED_URLS in test_digit_sum.py add = True if add: for fr in self._create_fuzzable_requests(response): self.output_queue.put(fr)
def _test_dns(self, original_response, dns_wildcard_url): """ Check if http://www.domain.tld/ == http://domain.tld/ """ headers = Headers([('Host', dns_wildcard_url.get_domain())]) try: modified_response = self._uri_opener.GET(original_response.get_url(), cache=True, headers=headers) except BaseFrameworkException as bfe: msg = ('An error occurred while fetching IP address URL in ' ' dns_wildcard plugin: "%s"') om.out.debug(msg % bfe) return if fuzzy_not_equal(modified_response.get_body(), original_response.get_body(), 0.35): desc = ('The target site has NO DNS wildcard, and the contents' ' of "%s" differ from the contents of "%s".') desc %= (dns_wildcard_url, original_response.get_url()) i = Info('No DNS wildcard', desc, [original_response.id, modified_response.id], self.get_name()) i.set_url(dns_wildcard_url) kb.kb.append(self, 'dns_wildcard', i) om.out.information(i.get_desc()) else: desc = ('The target site has a DNS wildcard configuration, the' ' contents of "%s" are equal to the ones of "%s".') desc %= (dns_wildcard_url, original_response.get_url()) i = Info('DNS wildcard', desc, [original_response.id, modified_response.id], self.get_name()) i.set_url(original_response.get_url()) kb.kb.append(self, 'dns_wildcard', i) om.out.information(i.get_desc())
def _analyze_response(self, original_resp, resp): """ :param original_resp: The HTTPResponse object that holds the ORIGINAL response. :param resp: The HTTPResponse object that holds the content of the response to analyze. """ if fuzzy_not_equal(original_resp.get_body(), resp.get_body(), 0.7): response_ids = [original_resp.id, resp.id] desc = '[Manual verification required] The response body for a ' \ 'request with a trailing dot in the domain, and the response ' \ 'body without a trailing dot in the domain differ. This could ' \ 'indicate a misconfiguration in the virtual host settings. In ' \ 'some cases, this misconfiguration permits the attacker to ' \ 'read the source code of the web application.' i = Info('Potential virtual host misconfiguration', desc, response_ids, self.get_name()) om.out.information(desc) kb.kb.append(self, 'domain_dot', i)
def _send_and_analyze(self, offending_string, offending_url, original_resp_body, rnd_param): """ Actually send the HTTP request. :return: None, everything is saved to the self._filtered and self._not_filtered lists. """ try: resp_body = self._uri_opener.GET(offending_url, cache=False).get_body() except BaseFrameworkException: # I get here when the remote end closes the connection self._filtered.append(offending_url) else: # I get here when the remote end returns a 403 or something like # that... So I must analyze the response body resp_body = resp_body.replace(offending_string, '') resp_body = resp_body.replace(rnd_param, '') if fuzzy_not_equal(resp_body, original_resp_body, 0.15): self._filtered.append(offending_url) else: self._not_filtered.append(offending_url)
def _test_DNS(self, original_response, dns_wildcard_url): """ Check if http://www.domain.tld/ == http://domain.tld/ """ headers = Headers([('Host', dns_wildcard_url.get_domain())]) try: modified_response = self._uri_opener.GET( original_response.get_url(), cache=True, headers=headers) except BaseFrameworkException: return else: if fuzzy_not_equal(modified_response.get_body(), original_response.get_body(), 0.35): desc = 'The target site has NO DNS wildcard, and the contents' \ ' of "%s" differ from the contents of "%s".' desc = desc % (dns_wildcard_url, original_response.get_url()) i = Info('No DNS wildcard', desc, modified_response.id, self.get_name()) i.set_url(dns_wildcard_url) kb.kb.append(self, 'dns_wildcard', i) om.out.information(i.get_desc()) else: desc = 'The target site has a DNS wildcard configuration, the'\ ' contents of "%s" are equal to the ones of "%s".' desc = desc % (dns_wildcard_url, original_response.get_url()) i = Info('DNS wildcard', desc, modified_response.id, self.get_name()) i.set_url(original_response.get_url()) kb.kb.append(self, 'dns_wildcard', i) om.out.information(i.get_desc())
:return: None """ if base_url == url: return http_response = self._uri_opener.GET(url, cache=False) http_response_code = http_response.get_code() if is_404(http_response) or http_response_code in (204, 301, 302, 404, 503): return # Looking good, but lets see if this is a false positive or not... url = base_url.url_join(path + rand_alnum(5) + '/') invalid_http_response = self._uri_opener.GET(url, cache=False) if is_404(invalid_http_response) or fuzzy_not_equal(http_response.get_body(), invalid_http_response.get_body(), 0.35): # Good, the path + rand_alnum(5) return a 404, the original path is not a false positive. desc = 'Path: "{}" found with HTTP "response code: {}" and "Content-Length: {}". ' \ 'It might exposes private information and requires a manual review'.format(http_response.get_url(), http_response_code, len(http_response.get_body())) i = Info('.listing file found', desc, http_response.id, self.get_name()) i.set_url(self._target_url) self.kb_append(self, 'wg_dir_file_bruter', i) def _bruteforce_directories(self, base_url): """ :param base_url: The base path to use in the bruteforcing process, can be something like http://host.tld/ or http://host.tld/images/ . :return: None
def _get_dead_links(self, fuzzable_request): """ Find every link on a HTML document verify if the domain is reachable or not; after that, verify if the web found a different name for the target site or if we found a new site that is linked. If the link points to a dead site then report it (it could be pointing to some private address or something...) """ # Get some responses to compare later base_url = fuzzable_request.get_url().base_url() original_response = self._uri_opener.GET(fuzzable_request.get_uri(), cache=True) base_response = self._uri_opener.GET(base_url, cache=True) base_resp_body = base_response.get_body() try: dp = parser_cache.dpc.get_document_parser_for(original_response) except BaseFrameworkException: # Failed to find a suitable parser for the document return [] # Set the non existent response non_existent_response = self._get_non_exist(fuzzable_request) nonexist_resp_body = non_existent_response.get_body() # Note: # - With parsed_references I'm 100% that it's really something in the # HTML that the developer intended to add. # # - The re_references are the result of regular expressions, which in # some cases are just false positives. # # In this case, and because I'm only going to use the domain name of the # URL I'm going to trust the re_references also. parsed_references, re_references = dp.get_references() parsed_references.extend(re_references) res = [] vhosts = self._verify_link_domain(parsed_references) for domain, vhost_response in self._send_in_threads(base_url, vhosts): vhost_resp_body = vhost_response.get_body() if fuzzy_not_equal(vhost_resp_body, base_resp_body, 0.35) and \ fuzzy_not_equal(vhost_resp_body, nonexist_resp_body, 0.35): res.append((domain, vhost_response.id)) else: desc = 'The content of "%s" references a non existent domain:'\ ' "%s". This can be a broken link, or an internal'\ ' domain name.' desc = desc % (fuzzable_request.get_url(), domain) i = Info('Internal hostname in HTML link', desc, original_response.id, self.get_name()) i.set_url(fuzzable_request.get_url()) kb.kb.append(self, 'find_vhosts', i) om.out.information(i.get_desc()) return res
ip_address = socket.gethostbyname(domain) except: return url = original_response.get_url() ip_url = url.copy() ip_url.set_domain(ip_address) try: modified_response = self._uri_opener.GET(ip_url, cache=True) except BaseFrameworkException, w3: msg = 'An error occurred while fetching IP address URL in ' \ ' dns_wildcard plugin: "%s"' % w3 om.out.debug(msg) else: if fuzzy_not_equal(modified_response.get_body(), original_response.get_body(), 0.35): desc = 'The contents of %s and %s differ.' desc = desc % (modified_response.get_uri(), original_response.get_uri()) i = Info('Default virtual host', desc, modified_response.id, self.get_name()) i.set_url(modified_response.get_url()) kb.kb.append(self, 'dns_wildcard', i) om.out.information(i.get_desc()) def _test_DNS(self, original_response, dns_wildcard_url): """ Check if http://www.domain.tld/ == http://domain.tld/
def audit(self, freq, orig_response): """ Tests an URL for memcache injection vulnerabilities. """ # first checking error response fake_mutants = create_mutants(freq, ['', ]) for mutant in fake_mutants: orig_body = orig_response.get_body() #trying to break normal execution flow with error1 payload mutant.set_token_value(self.mci.error_1) error_1_response, body_error_1_response = self._uri_opener.send_clean(mutant) if fuzzy_equal(orig_body, body_error_1_response, self._eq_limit): # # if we manage to break execution flow, there is a potential # injection otherwise - no injection! # continue # trying the correct injection request, to confirm that we've found # it! mutant.set_token_value(self.mci.ok) ok_response, body_ok_response = self._uri_opener.send_clean(mutant) if fuzzy_not_equal(orig_body, body_ok_response, self._eq_limit): # # now requests should be equal, otherwise injection failed! # continue #error2 request to just make sure that wasn't random bytes mutant.set_token_value(self.mci.error_2) error_2_response, body_error_2_response = self._uri_opener.send_clean(mutant) if fuzzy_equal(orig_body, body_error_2_response, self._eq_limit): # # now requests should be different again, otherwise injection # is not confirmed # continue response_ids = [error_1_response.id, ok_response.id, error_2_response.id,] desc = 'Memcache injection was found at: "%s", using'\ ' HTTP method %s. The injectable parameter is: "%s"' desc = desc % (mutant.get_url(), mutant.get_method(), mutant.get_token_name()) v = Vuln.from_mutant('Memcache injection vulnerability', desc, severity.HIGH, response_ids, 'memcachei', mutant) self.debug(v.get_desc()) v['ok_html'] = ok_response.get_body() v['error_1_html'] = error_1_response.get_body() v['error_2_html'] = error_2_response.get_body() self.kb_append_uniq(self, 'memcachei', v) return
ip_address = socket.gethostbyname(domain) except: return url = original_response.get_url() ip_url = url.copy() ip_url.set_domain(ip_address) try: modified_response = self._uri_opener.GET(ip_url, cache=True) except BaseFrameworkException, w3: msg = 'An error occurred while fetching IP address URL in ' \ ' dns_wildcard plugin: "%s"' % w3 om.out.debug(msg) else: if fuzzy_not_equal(modified_response.get_body(), original_response.get_body(), 0.35): desc = 'The contents of %s and %s differ.' desc = desc % (modified_response.get_uri(), original_response.get_uri()) i = Info('Default virtual host', desc, modified_response.id, self.get_name()) i.set_url(modified_response.get_url()) kb.kb.append(self, 'dns_wildcard', i) om.out.information(i.get_desc()) def _test_DNS(self, original_response, dns_wildcard_url): """ Check if http://www.domain.tld/ == http://domain.tld/ """
def batch_injection_test(self, freq, orig_response): """ Uses the batch injection technique to find memcache injections """ # shortcut send_clean = self._uri_opener.send_clean # first checking error response fake_mutants = create_mutants(freq, [ '', ]) for mutant in fake_mutants: orig_body = orig_response.get_body() # trying to break normal execution flow with error1 payload mutant.set_token_value(self.mci.error_1) error_1_response, body_error_1_response = send_clean(mutant) if fuzzy_equal(orig_body, body_error_1_response, self._eq_limit): # # if we manage to break execution flow, there is a potential # injection otherwise - no injection! # continue # trying the correct injection request, to confirm that we've found # it! mutant.set_token_value(self.mci.ok) ok_response, body_ok_response = send_clean(mutant) if fuzzy_not_equal(orig_body, body_ok_response, self._eq_limit): # # now requests should be equal, otherwise injection failed! # continue # error2 request to just make sure that wasn't random bytes mutant.set_token_value(self.mci.error_2) error_2_response, body_error_2_response = send_clean(mutant) if fuzzy_equal(orig_body, body_error_2_response, self._eq_limit): # # now requests should be different again, otherwise injection # is not confirmed # continue response_ids = [ error_1_response.id, ok_response.id, error_2_response.id ] desc = 'Memcache injection was found at: "%s", using' \ ' HTTP method %s. The injectable parameter is: "%s"' desc = desc % (mutant.get_url(), mutant.get_method(), mutant.get_token_name()) v = Vuln.from_mutant('Memcache injection vulnerability', desc, severity.HIGH, response_ids, 'memcachei', mutant) v['ok_html'] = ok_response.get_body() v['error_1_html'] = error_1_response.get_body() v['error_2_html'] = error_2_response.get_body() self.kb_append_uniq(self, 'memcachei', v) return
def _get_dead_links(self, fuzzable_request): """ Find every link on a HTML document verify if the domain is reachable or not; after that, verify if the web found a different name for the target site or if we found a new site that is linked. If the link points to a dead site then report it (it could be pointing to some private address or something...) """ # Get some responses to compare later base_url = fuzzable_request.get_url().base_url() original_response = self._uri_opener.GET(fuzzable_request.get_uri(), cache=True) base_response = self._uri_opener.GET(base_url, cache=True) base_resp_body = base_response.get_body() try: dp = parser_cache.dpc.get_document_parser_for(original_response) except BaseFrameworkException: # Failed to find a suitable parser for the document return [] # Set the non existent response non_existent_response = self._get_non_exist(fuzzable_request) nonexist_resp_body = non_existent_response.get_body() # Note: # - With parsed_references I'm 100% that it's really something in the # HTML that the developer intended to add. # # - The re_references are the result of regular expressions, which in # some cases are just false positives. # # In this case, and because I'm only going to use the domain name of the # URL I'm going to trust the re_references also. parsed_references, re_references = dp.get_references() parsed_references.extend(re_references) res = [] vhosts = self._verify_link_domain(parsed_references) for domain, vhost_response in self._send_in_threads(base_url, vhosts): vhost_resp_body = vhost_response.get_body() if fuzzy_not_equal(vhost_resp_body, base_resp_body, 0.35) and \ fuzzy_not_equal(vhost_resp_body, nonexist_resp_body, 0.35): res.append((domain, vhost_response.id)) else: desc = u'The content of "%s" references a non existent domain:'\ u' "%s". This can be a broken link, or an internal'\ u' domain name.' desc %= (fuzzable_request.get_url(), domain) i = Info(u'Internal hostname in HTML link', desc, original_response.id, self.get_name()) i.set_url(fuzzable_request.get_url()) kb.kb.append(self, 'find_vhosts', i) om.out.information(i.get_desc()) return res