def _generic_vhosts( self, fuzzableRequest ): ''' Test some generic virtual hosts, only do this once. ''' res = [] base_url = fuzzableRequest.getURL().baseUrl() common_vhost_list = self._get_common_virtualhosts(base_url) # Get some responses to compare later original_response = self._urlOpener.GET(base_url, useCache=True) orig_resp_body = original_response.getBody() non_existant = 'iDoNotExistPleaseGoAwayNowOrDie' + createRandAlNum(4) self._non_existant_response = self._urlOpener.GET(base_url, useCache=False, \ headers={'Host': non_existant }) nonexist_resp_body = self._non_existant_response.getBody() for common_vhost in common_vhost_list: try: vhost_response = self._urlOpener.GET( base_url, useCache=False, \ headers={'Host': common_vhost } ) except w3afException: pass else: vhost_resp_body = vhost_response.getBody() # If they are *really* different (not just different by some chars) if relative_distance_lt(vhost_resp_body, orig_resp_body, 0.35) and \ relative_distance_lt(vhost_resp_body, nonexist_resp_body, 0.35): res.append((common_vhost, vhost_response.id)) return res
def _test_DNS(self, original_response, dns_wildcard_url): ''' Check if http://www.domain.tld/ == http://domain.tld/ ''' headers = Headers([('Host', dns_wildcard_url.get_domain())]) try: modified_response = self._uri_opener.GET( original_response.get_url(), cache=True, headers=headers) except w3afException: return else: if relative_distance_lt(modified_response.get_body(), original_response.get_body(), 0.35): desc = 'The target site has NO DNS wildcard, and the contents' \ ' of "%s" differ from the contents of "%s".' desc = desc % (dns_wildcard_url, original_response.get_url()) i = Info('No DNS wildcard', desc, modified_response.id, self.get_name()) i.set_url(dns_wildcard_url) kb.kb.append(self, 'dns_wildcard', i) om.out.information(i.get_desc()) else: desc = 'The target site has a DNS wildcard configuration, the'\ ' contents of "%s" are equal to the ones of "%s".' desc = desc % (dns_wildcard_url, original_response.get_url()) i = Info('DNS wildcard', desc, modified_response.id, self.get_name()) i.set_url(original_response.get_url()) kb.kb.append(self, 'dns_wildcard', i) om.out.information(i.get_desc())
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 relative_distance_lt(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 _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 \ relative_distance_lt(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 relative_distance_lt(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 relative_distance_lt(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 _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_existant_response = self._get_non_exist(fuzzable_request) nonexist_resp_body = non_existant_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 relative_distance_lt(vhost_resp_body, orig_resp_body, 0.35) and \ relative_distance_lt(vhost_resp_body, nonexist_resp_body, 0.35): res.append((vhost, vhost_response.id)) return res
def _analyze_response(self, original_resp, resp): ''' @parameter original_resp: The httpResponse object that holds the ORIGINAL response. @parameter resp: The httpResponse object that holds the content of the response to analyze. ''' if relative_distance_lt(original_resp.getBody(), resp.getBody(), 0.7): i = info.info(resp) i.setPluginName(self.getName()) i.setId([original_resp.id, resp.id]) i.setName('Responses differ') msg = '[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.setDesc(msg) om.out.information(msg) kb.kb.append(self, 'domain_dot', i)
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 relative_distance_lt(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 w3afException: # 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 relative_distance_lt(resp_body, original_resp_body, 0.15): self._filtered.append(offending_URL) else: self._not_filtered.append(offending_URL)
try: response = self._urlOpener.GET(fuzzableRequest.getURI(), useCache=True, headers=self._headers) except KeyboardInterrupt, e: raise e else: if not is_404( response ): # We have two different cases: # - If the URL's are different, then there is nothing to think about, we simply found # something new! # # - If we changed the query string parameters, we have to check the content is_new = False if response.getURL() != original_resp.getURL(): is_new = True elif relative_distance_lt(response.getBody(), original_resp.getBody(), 0.7): is_new = True # Add it to the result. if is_new: self._fuzzableRequests.extend( self._createFuzzableRequests( response ) ) om.out.debug('digitSum plugin found new URI: "' + fuzzableRequest.getURI() + '".') def _mangle_digits(self, fuzzableRequest): ''' Mangle those digits. @param fuzzableRequest: The original fuzzableRequest @return: A list of fuzzableRequests. ''' res = [] # First i'll mangle the digits in the URL file
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 w3afException, w3: msg = 'An error occurred while fetching IP address URL in ' \ ' dns_wildcard plugin: "%s"' % w3 om.out.debug(msg) else: if relative_distance_lt(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 _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 w3afException: # Failed to find a suitable parser for the document return [] # Set the non existant response non_existant_response = self._get_non_exist(fuzzable_request) nonexist_resp_body = non_existant_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 relative_distance_lt(vhost_resp_body, base_resp_body, 0.35) and \ relative_distance_lt(vhost_resp_body, nonexist_resp_body, 0.35): res.append((domain, vhost_response.id)) else: desc = 'The content of "%s" references a non existant 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
def _get_dead_links(self, fuzzableRequest): ''' 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...) ''' res = [] # Get some responses to compare later base_url = fuzzableRequest.getURL().baseUrl() original_response = self._urlOpener.GET(fuzzableRequest.getURI(), useCache=True) base_response = self._urlOpener.GET(base_url, useCache=True) base_resp_body = base_response.getBody() try: dp = dpCache.dpc.getDocumentParserFor(original_response) except w3afException: # Failed to find a suitable parser for the document return [] # Set the non existant response non_existant = 'iDoNotExistPleaseGoAwayNowOrDie' + createRandAlNum(4) self._non_existant_response = self._urlOpener.GET(base_url, useCache=False, headers={'Host': non_existant}) nonexist_resp_body = self._non_existant_response.getBody() # 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.getReferences() parsed_references.extend(re_references) for link in parsed_references: domain = link.getDomain() # # First section, find internal hosts using the HTTP Host header: # if domain not in self._already_queried: # If the parsed page has an external link to www.google.com # then I'll send a request to the target site, with Host: www.google.com # This sucks, but it's cool if the document has a link to # http://some.internal.site.target.com/ try: vhost_response = self._urlOpener.GET(base_url, useCache=False, headers={'Host': domain }) except w3afException: pass else: self._already_queried.add(domain) vhost_resp_body = vhost_response.getBody() # If they are *really* different (not just different by some chars) if relative_distance_lt(vhost_resp_body, base_resp_body, 0.35) and \ relative_distance_lt(vhost_resp_body, nonexist_resp_body, 0.35): # and the domain can't just be resolved using a DNS query to # our regular DNS server report = True if self._can_resolve_domain_names: try: socket.gethostbyname(domain) except: # aha! The HTML is linking to a domain that's # hosted in the same server, and the domain name # can NOT be resolved! report = True else: report = False # have found something interesting! if report: res.append( (domain, vhost_response.id) ) # # Second section, find hosts using failed DNS resolutions # if self._can_resolve_domain_names: try: # raises exception when it's not found # socket.gaierror: (-5, 'No address associated with hostname') socket.gethostbyname( domain ) except: i = info.info() i.setPluginName(self.getName()) i.setName('Internal hostname in HTML link') i.setURL( fuzzableRequest.getURL() ) i.setMethod( 'GET' ) i.setId( original_response.id ) msg = 'The content of "'+ fuzzableRequest.getURL() +'" references a non ' msg += 'existant domain: "' + link + '". This may be a broken link, or an' msg += ' internal domain name.' i.setDesc( msg ) kb.kb.append( self, 'findvhost', i ) om.out.information( i.getDesc() ) res = [ r for r in res if r != ''] return res
def _do_request( self, mutant, user ): ''' Perform the request and compare. @return: True when the user was found. ''' try: response = self._uri_opener.GET( mutant, cache=True, headers=self._headers ) except KeyboardInterrupt,e: raise e else: path = mutant.getPath() response_body = response.getBody().replace( path, '') if relative_distance_lt(response_body, self._non_existant, 0.7): # Avoid duplicates if user not in [ u['user'] for u in kb.kb.getData( 'userDir', 'users') ]: i = info.info() i.setPluginName(self.getName()) i.setName('User directory: ' + response.getURL() ) i.setId( response.id ) i.setDesc( 'A user directory was found at: ' + response.getURL() ) i['user'] = user kb.kb.append( self, 'users', i ) fuzzable_request_list = self._createFuzzableRequests( response ) self._fuzzable_requests.extend( fuzzable_request_list )
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 w3afException: # Failed to find a suitable parser for the document return [] # Set the non existant response non_existant_response = self._get_non_exist(fuzzable_request) nonexist_resp_body = non_existant_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 relative_distance_lt(vhost_resp_body, base_resp_body, 0.35) and \ relative_distance_lt(vhost_resp_body, nonexist_resp_body, 0.35): res.append((domain, vhost_response.id)) else: desc = 'The content of "%s" references a non existant 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
Actually send the HTTP request. @return: None, everything is saved to the self._filtered and self._not_filtered lists. ''' try: resp_body = self._urlOpener.GET(offending_URL, useCache=False).getBody() except KeyboardInterrupt, e: raise e except Exception: # 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 relative_distance_lt(resp_body, original_resp_body, 0.15): self._filtered.append(offending_URL) else: self._not_filtered.append(offending_URL) def _analyze_results( self, filtered, not_filtered ): ''' Analyze the test results and save the conclusion to the kb. ''' if len( filtered ) >= len(self._get_offending_strings()) / 5.0: i = info.info() i.setPluginName(self.getName()) i.setName('Active filter detected') msg = 'The remote network has an active filter. IMPORTANT: The result of all the other' msg += ' plugins will be unaccurate, web applications could be vulnerable but '
return fr def _do_request( self, fuzzableRequest, orig_resp ): ''' Sends the request. @parameter fuzzableRequest: The fuzzable request object to modify. @parameter orig_resp: The response for the original request that was sent. ''' try: resp = self._urlOpener.GET(fuzzableRequest.getURI(), useCache=True) except KeyboardInterrupt, e: raise e else: resp_body = resp.getBody() orig_resp = orig_resp.getBody() if relative_distance_lt(resp_body, orig_resp, 0.7) and \ not is_404(resp): self._fuzzableRequests.extend(self._createFuzzableRequests(resp)) om.out.debug('slash plugin found new URI: "' + fuzzableRequest.getURI() + '".') def getOptions( self ): ''' @return: A list of option objects for this plugin. ''' ol = optionList() return ol def setOptions( self, OptionList ): ''' This method sets all the options that are configured using the user interface generated by the framework using the result of getOptions().