def _mutate(self, data): ''' Add a random parameter. :param data: A dict-like object. :return: The same object with one new key-value. ''' key = rand_alnum(5) value = rand_alnum(8) data[key] = value return data
def _single_404_check(self, http_response, html_body): ''' Performs a very simple check to verify if this response is a 404 or not. It takes the original URL and modifies it by pre-pending a "not-" to the filename, then performs a request to that URL and compares the original response with the modified one. If they are equal then the original request is a 404. :param http_response: The original HTTP response :param html_body: The original HTML body after passing it by a cleaner :return: True if the original response was a 404 ! ''' response_url = http_response.get_url() filename = response_url.get_file_name() if not filename: relative_url = '../%s/' % rand_alnum(8) url_404 = response_url.url_join(relative_url) else: relative_url = 'not-%s' % filename url_404 = response_url.url_join(relative_url) response_404 = self._send_404(url_404, store=False) clean_response_404_body = get_clean_body(response_404) if response_404.get_code() == 404 and \ url_404.get_domain_path() not in self._directory_uses_404_codes: self._directory_uses_404_codes.add(url_404.get_domain_path()) return relative_distance_ge(clean_response_404_body, html_body, IS_EQUAL_RATIO)
def _gen_url_to_include(self, file_content, extension): ''' Generate the URL to include, based on the configuration it will return a URL pointing to a XSS bug, or our local webserver. ''' if self._use_XSS_vuln and self._xss_vuln: url = self._xss_vuln.get_url().uri2url() data_container = self._xss_vuln.get_dc() data_container = data_container.copy() data_container[self._xss_vuln.get_var()] = file_content url_to_include = url + '?' + str(data_container) return url_to_include else: # Write the php to the webroot filename = rand_alnum() filepath = os.path.join(get_home_dir(), 'webroot', filename) try: file_handler = open(filepath, 'w') file_handler.write(file_content) file_handler.close() except: raise w3afException('Could not create file in webroot.') else: url_to_include = 'http://%s:%s/%s' % ( self._listen_address, self._listen_port, filename) return url_to_include
def _create_files(self): """ If the extension is in the templates dir, open it and return the handler. If the extension isn't in the templates dir, create a file with random content, open it and return the handler. :return: A list of tuples with (file handler, file name) """ result = [] for ext in self._extensions: # Open target temp_dir = get_temp_dir() low_level_fd, file_name = tempfile.mkstemp(prefix="w3af_", suffix="." + ext, dir=temp_dir) file_handler = os.fdopen(low_level_fd, "w+b") template_filename = "template." + ext if template_filename in os.listdir(self.TEMPLATE_DIR): content = file(os.path.join(self.TEMPLATE_DIR, template_filename)).read() else: # Since I don't have a template for this file extension, I'll simply # put some random alnum inside the file content = rand_alnum(64) # Write content to target file_handler.write(content) file_handler.close() # Open the target again, should never fail. file_handler = file(file_name, "r") _, file_name = os.path.split(file_name) result.append((file_handler, file_name)) return result
def test_find(self): find_id = random.randint(1, 499) url = URL("http://w3af.org/a/b/foobar.php?foo=123") tag_value = rand_alnum(10) for i in xrange(0, 500): request = HTTPRequest(url, data="a=1") code = 200 if i == find_id: code = 302 hdr = Headers([("Content-Type", "text/html")]) res = HTTPResponse(code, "<html>", hdr, url, url) h1 = HistoryItem() h1.request = request res.set_id(i) h1.response = res if i == find_id: h1.toggle_mark() h1.update_tag(tag_value) h1.save() h2 = HistoryItem() self.assertEqual(len(h2.find([("tag", "%" + tag_value + "%", "like")])), 1) self.assertEqual(len(h2.find([("code", 302, "=")])), 1) self.assertEqual(len(h2.find([("mark", 1, "=")])), 1) self.assertEqual(len(h2.find([("has_qs", 1, "=")])), 500) self.assertEqual(len(h2.find([("has_qs", 1, "=")], result_limit=10)), 10) results = h2.find([("has_qs", 1, "=")], result_limit=1, orderData=[("id", "desc")]) self.assertEqual(results[0].id, 499) search_data = [] search_data.append(("id", find_id + 1, "<")) search_data.append(("id", find_id - 1, ">")) self.assertEqual(len(h2.find(search_data)), 1)
def _gen_url_to_include(self, file_content, extension): ''' Generate the URL to include, based on the configuration it will return a URL pointing to a XSS bug, or our local webserver. ''' if self._use_XSS_vuln and self._xss_vuln: url = self._xss_vuln.get_url().uri2url() data_container = self._xss_vuln.get_dc() data_container = data_container.copy() data_container[self._xss_vuln.get_var()] = file_content url_to_include = url + '?' + str(data_container) return url_to_include else: # Write the php to the webroot filename = rand_alnum() filepath = os.path.join(get_home_dir(), 'webroot', filename) try: file_handler = open(filepath, 'w') file_handler.write(file_content) file_handler.close() except: raise w3afException('Could not create file in webroot.') else: url_to_include = 'http://%s:%s/%s' % (self._listen_address, self._listen_port, filename) return url_to_include
def create_crash_file(exception): filename = "w3af_crash-" + rand_alnum(5) + ".txt" filename = os.path.join(gettempdir(), filename) crash_dump = file(filename, "w") crash_dump.write(_('Submit this bug here: https://sourceforge.net/apps/trac/w3af/newticket \n')) crash_dump.write(get_versions()) crash_dump.write(exception) crash_dump.close() return filename
def create_crash_file(exception): filename = "w3af_crash-" + rand_alnum(5) + ".txt" filename = os.path.join(gettempdir(), filename) crash_dump = file(filename, "w") crash_dump.write( _('Submit this bug here: https://sourceforge.net/apps/trac/w3af/newticket \n' )) crash_dump.write(get_versions()) crash_dump.write(exception) crash_dump.close() return filename
def _PUT(self, domain_path): ''' Tests PUT method. ''' # upload url = domain_path.url_join(rand_alpha(5)) rnd_content = rand_alnum(6) put_response = self._uri_opener.PUT(url, data=rnd_content) # check if uploaded res = self._uri_opener.GET(url, cache=True) if res.get_body() == rnd_content: msg = 'File upload with HTTP PUT method was found at resource:' \ ' "%s". A test file was uploaded to: "%s".' msg = msg % (domain_path, res.get_url()) v = Vuln('Insecure DAV configuration', msg, severity.HIGH, [put_response.id, res.id], self.get_name()) v.set_url(url) v.set_method('PUT') self.kb_append(self, 'dav', v) # Report some common errors elif put_response.get_code() == 500: msg = 'DAV seems to be incorrectly configured. The web server' \ ' answered with a 500 error code. In most cases, this means'\ ' that the DAV extension failed in some way. This error was'\ ' found at: "%s".' % put_response.get_url() i = Info('DAV incorrect configuration', msg, res.id, self.get_name()) i.set_url(url) i.set_method('PUT') self.kb_append(self, 'dav', i) # Report some common errors elif put_response.get_code() == 403: msg = 'DAV seems to be correctly configured and allowing you to'\ ' use the PUT method but the directory does not have the'\ ' correct permissions that would allow the web server to'\ ' write to it. This error was found at: "%s".' msg = msg % put_response.get_url() i = Info('DAV incorrect configuration', msg, [put_response.id, res.id], self.get_name()) i.set_url(url) i.set_method('PUT') self.kb_append(self, 'dav', i)
def get_file_from_template(extension): file_name = "%s.%s" % (rand_alpha(7), extension) template_file = os.path.join(TEMPLATE_DIR, 'template.%s' % extension) if os.path.exists(template_file): file_content = file(template_file).read() success = True else: file_content = rand_alnum(64) success = False return success, file_content, file_name
def _replace_JUNK(self, query): ''' Replace the JUNK(x) variable with random alphanum. ''' match_obj = self._junk_re.search(query) if match_obj is not None: if match_obj.group(1).isdigit(): length = int(match_obj.group(1)) query = self._junk_re.sub(rand_alnum(length), query) return query
def _create_file(self): ''' Create random name file php with random php content. To be used in the remote file inclusion test. :return: The file content to be served via the webserver. Please note that the generated code works both in PHP and JSP without any issues, since PHP will run everything between "<?" and "?>" and JSP will run code between "<%" and "%>". TODO: make this code compatible with: asp/aspx, jsp, js (nodejs), pl, py, rb, etc. Some code snippets that might help to achieve this task: asp_code = 'response.write("%s");\n response.write("%s");' % ( rand1, rand2) asp_code = '<% \n '+asp_code+'\n %>' ''' with self._plugin_lock: # First, generate the php file to be included. rfi_result_part_1 = rand1 = rand_alnum(9) rfi_result_part_2 = rand2 = rand_alnum(9) rfi_result = rand1 + rand2 filename = rand_alnum(8) php_jsp_code = '<? echo "%s"; echo "%s"; ?>' php_jsp_code += '<%% out.print("%s"); out.print("%s"); %%>' php_jsp_code = php_jsp_code % (rand1, rand2, rand1, rand2) # Define the required parameters netloc = self._listen_address + ':' + str(self._listen_port) path = '/' + filename rfi_url = URL.from_parts('http', netloc, path, None, None, None) rfi_data = RFIData( rfi_url, rfi_result_part_1, rfi_result_part_2, rfi_result) return php_jsp_code, rfi_data
def _id_failed_login_page(self, freq, user_field, passwd_field): ''' Generate TWO different response bodies that are the result of failed logins. The first result is for logins with filled user and password fields; the second one is for a filled user and a blank passwd. ''' # The result is going to be stored here login_failed_result_list = [] data_container = freq.get_dc() data_container = self._true_extra_fields(data_container, user_field, passwd_field) # The first tuple is an invalid username and a password # The second tuple is an invalid username with a blank password tests = [(rand_alnum(8), rand_alnum(8)), (rand_alnum(8), '')] for user, passwd in tests: # Setup the data_container # Remember that we can have password only forms! if user_field is not None: data_container[user_field][0] = user data_container[passwd_field][0] = passwd freq.set_dc(data_container) response = self._uri_opener.send_mutant(freq, grep=False) body = response.get_body() body = body.replace(user, '') body = body.replace(passwd, '') # Save it login_failed_result_list.append(body) # Now I perform a self test, before starting with the actual bruteforcing # The first tuple is an invalid username and a password # The second tuple is an invalid username with a blank password tests = [(rand_alnum(8), rand_alnum(8)), (rand_alnum(8), '')] for user, passwd in tests: # Now I do a self test of the result I just created. # Remember that we can have password only forms! if user_field is not None: data_container[user_field][0] = user data_container[passwd_field][0] = passwd freq.set_dc(data_container) response = self._uri_opener.send_mutant(freq, grep=False) body = response.get_body() body = body.replace(user, '') body = body.replace(passwd, '') if not self._matches_failed_login(body, login_failed_result_list): raise w3afException('Failed to generate a response that ' 'matches the failed login page.') return login_failed_result_list
def _create_file(self): ''' Create random name file php with random php content. To be used in the remote file inclusion test. :return: The file content to be served via the webserver. Please note that the generated code works both in PHP and JSP without any issues, since PHP will run everything between "<?" and "?>" and JSP will run code between "<%" and "%>". TODO: make this code compatible with: asp/aspx, jsp, js (nodejs), pl, py, rb, etc. Some code snippets that might help to achieve this task: asp_code = 'response.write("%s");\n response.write("%s");' % ( rand1, rand2) asp_code = '<% \n '+asp_code+'\n %>' ''' with self._plugin_lock: # First, generate the php file to be included. rfi_result_part_1 = rand1 = rand_alnum(9) rfi_result_part_2 = rand2 = rand_alnum(9) rfi_result = rand1 + rand2 filename = rand_alnum(8) php_jsp_code = '<? echo "%s"; echo "%s"; ?>' php_jsp_code += '<%% out.print("%s"); out.print("%s"); %%>' php_jsp_code = php_jsp_code % (rand1, rand2, rand1, rand2) # Define the required parameters netloc = self._listen_address + ':' + str(self._listen_port) path = '/' + filename rfi_url = URL.from_parts('http', netloc, path, None, None, None) rfi_data = RFIData(rfi_url, rfi_result_part_1, rfi_result_part_2, rfi_result) return php_jsp_code, rfi_data
def _send_requests(self, fuzzable_request): ''' Actually send the requests that might be blocked. :param fuzzable_request: The FuzzableRequest to modify in order to see if it's blocked ''' rnd_param = rand_alnum(7) rnd_value = rand_alnum(7) fmt = '%s?%s=%s' original_url_str = fmt % (fuzzable_request.get_url(), rnd_param, rnd_value) original_url = URL(original_url_str) try: http_resp = self._uri_opener.GET(original_url, cache=True) except w3afException: msg = 'Active filter detection plugin failed to receive a'\ ' response for the first request. Can not perform analysis.' om.out.error(msg) else: original_response_body = http_resp.get_body() original_response_body = original_response_body.replace( rnd_param, '') original_response_body = original_response_body.replace( rnd_value, '') tests = [] for offending_string in self._get_offending_strings(): offending_URL = fmt % (fuzzable_request.get_url(), rnd_param, offending_string) offending_URL = URL(offending_URL) tests.append((offending_string, offending_URL, original_response_body, rnd_param)) self.worker_pool.map_multi_args(self._send_and_analyze, tests) return self._filtered, self._not_filtered
def get_remote_temp_file(exec_method): ''' :return: The name of a file in the remote file system that the user that I'm executing commands with can write, read and execute. The normal responses for this are files in /tmp/ or %TEMP% depending on the remote OS. ''' os = os_detection_exec(exec_method) if os == 'windows': _filename = exec_method('echo %TEMP%').strip() + '\\' _filename += rand_alnum(6) # verify exists dir_res = exec_method('dir ' + _filename).strip().lower() if 'not found' in dir_res: return _filename else: # Shit, the file exists, run again and see what we can do return get_remote_temp_file(exec_method) return _filename elif os == 'linux': _filename = '/tmp/' + rand_alnum(6) # verify exists ls_res = exec_method('ls ' + _filename).strip() if 'No such file' in ls_res: return _filename else: # Shit, the file exists, run again and see what we can do return get_remote_temp_file(exec_method) else: msg = 'Failed to create filename for a temporary file in the remote host.' raise w3afException(msg)
def _return_without_eval(self, uri): ''' This method tries to lower the false positives. ''' if not uri.has_query_string(): return False uri.set_file_name(uri.get_file_name() + rand_alnum(7)) try: response = self._uri_opener.GET(uri, cache=True, headers=self._headers) except w3afException, e: msg = 'An exception was raised while requesting "%s", the error' msg += 'message is: "%s"' om.out.error(msg % (uri, e))
def test_rand_alnum(self): x = rand_alnum(length=10) self.assertEqual(len(x), 10) x = rand_alnum(length=20) self.assertEqual(len(x), 20) x = rand_alnum(length=5) y = rand_alnum(length=5) z = rand_alnum(length=5) w = rand_alnum(length=5) self.assertTrue(x != y != z != w)
def test_rand_alnum(self): x = rand_alnum(length=10) self.assertEqual(len(x), 10) x = rand_alnum( length=20 ) self.assertEqual(len(x), 20) x = rand_alnum( length=5 ) y = rand_alnum( length=5 ) z = rand_alnum( length=5 ) w = rand_alnum( length=5 ) self.assertTrue(x != y != z != w)
def test_tag(self): tag_id = random.randint(501, 999) tag_value = rand_alnum(10) url = URL("http://w3af.org/a/b/c.php") for i in xrange(501, 1000): request = HTTPRequest(url, data="a=1") hdr = Headers([("Content-Type", "text/html")]) res = HTTPResponse(200, "<html>", hdr, url, url) h1 = HistoryItem() h1.request = request res.set_id(i) h1.response = res if i == tag_id: h1.update_tag(tag_value) h1.save() h2 = HistoryItem() h2.load(tag_id) self.assertEqual(h2.tag, tag_value)
def _get_limit_response(self, m): ''' We request the limit (something that doesn't exist) - If http://localhost/a.php?b=1 ; then I should request b=12938795 (random number) - If http://localhost/a.php?b=abc ; then I should request b=hnv98yks (random alnum) :return: The limit response object ''' # Copy the dc, needed to make a good vuln report dc = copy.deepcopy(m.get_dc()) if m.get_original_value().isdigit(): m.set_mod_value(rand_number(length=8)) else: m.set_mod_value(rand_alnum(length=8)) limit_response = self._uri_opener.send_mutant(m) # restore the dc m.set_dc(dc) return limit_response
def modify_request(self, request): ''' Mangles the request :param request: HTTPRequest instance that is going to be modified by the evasion plugin :return: The modified request ''' # We mangle the URL path = request.url_object.get_path() if re.match('^/', path): random_alnum = rand_alnum() path = '/' + random_alnum + '/..' + path # Finally, we set all the mutants to the request in order to return it new_url = request.url_object.copy() new_url.set_path(path) # Finally, we set all the mutants to the request in order to return it new_req = HTTPRequest(new_url, request.data, request.headers, request.get_origin_req_host()) return new_req
def _create_files(self): ''' If the extension is in the templates dir, open it and return the handler. If the extension isn't in the templates dir, create a file with random content, open it and return the handler. :return: A list of tuples with (file handler, file name) ''' result = [] for ext in self._extensions: # Open target temp_dir = get_temp_dir() low_level_fd, file_name = tempfile.mkstemp(prefix='w3af_', suffix='.' + ext, dir=temp_dir) file_handler = os.fdopen(low_level_fd, "w+b") template_filename = 'template.' + ext if template_filename in os.listdir(self.TEMPLATE_DIR): content = file( os.path.join(self.TEMPLATE_DIR, template_filename)).read() else: # Since I don't have a template for this file extension, I'll simply # put some random alnum inside the file content = rand_alnum(64) # Write content to target file_handler.write(content) file_handler.close() # Open the target again, should never fail. file_handler = file(file_name, 'r') _, file_name = os.path.split(file_name) result.append((file_handler, file_name)) return result
def generate_404_knowledge(self, url): ''' Based on a URL, request something that we know is going to be a 404. Afterwards analyze the 404's and summarise them. :return: A list with 404 bodies. ''' # # This is the case when nobody has properly configured # the object in order to use it. # if self._uri_opener is None: msg = '404 fingerprint database was incorrectly initialized.' raise RuntimeError(msg) # Get the filename extension and create a 404 for it extension = url.get_extension() domain_path = url.get_domain_path() # the result self._response_body_list = [] # # This is a list of the most common handlers, in some configurations, the 404 # depends on the handler, so I want to make sure that I catch the 404 for each one # handlers = set() handlers.update( ['py', 'php', 'asp', 'aspx', 'do', 'jsp', 'rb', 'do']) handlers.update( ['gif', 'htm', 'pl', 'cgi', 'xhtml', 'htmls', 'foobar']) if extension: handlers.add(extension) args_list = [] for extension in handlers: rand_alnum_file = rand_alnum(8) + '.' + extension url404 = domain_path.url_join(rand_alnum_file) args_list.append(url404) self._worker_pool.map(self._send_404, args_list) # # I have the bodies in self._response_body_list , but maybe they # all look the same, so I'll filter the ones that look alike. # result = [self._response_body_list[0], ] for i in self._response_body_list: for j in self._response_body_list: if relative_distance_ge(i, j, IS_EQUAL_RATIO): # They are equal, we are ok with that continue else: # They are no equal, this means that we'll have to add this to the list result.append(j) # I don't need these anymore self._response_body_list = None # And I return the ones I need result = list(set(result)) om.out.debug('The 404 body result database has a length of ' + str(len(result)) + '.') self._404_bodies = result self._already_analyzed = True self._fingerprinted_paths.add(domain_path)
Performs a GET and verifies that the response is not a 404. :return: None, data is stored in self.output_queue ''' try: http_response = self._uri_opener.GET(dir_url, cache=False) except: pass else: if is_404(http_response): return # # Looking good, but lets see if this is a false positive # or not... # dir_url = base_path.url_join(directory_name + rand_alnum(5) + '/') invalid_http_response = self._uri_opener.GET(dir_url, cache=False) if is_404(invalid_http_response): # # Good, the directory_name + rand_alnum(5) return a # 404, the original directory_name is not a false positive. # for fr in self._create_fuzzable_requests(http_response): self.output_queue.put(fr) msg = 'dir_file_brute plugin found "%s" with HTTP response ' \ 'code %s and Content-Length: %s.' \
def _get_non_exist(self, fuzzable_request): base_url = fuzzable_request.get_url().base_url() non_existant_domain = 'iDoNotExistPleaseGoAwayNowOrDie' + rand_alnum(4) return self._http_get_vhost(base_url, non_existant_domain)
def generate_404_knowledge(self, url): ''' Based on a URL, request something that we know is going to be a 404. Afterwards analyze the 404's and summarise them. :return: A list with 404 bodies. ''' # # This is the case when nobody has properly configured # the object in order to use it. # if self._uri_opener is None: msg = '404 fingerprint database was incorrectly initialized.' raise RuntimeError(msg) # Get the filename extension and create a 404 for it extension = url.get_extension() domain_path = url.get_domain_path() # the result self._response_body_list = [] # # This is a list of the most common handlers, in some configurations, the 404 # depends on the handler, so I want to make sure that I catch the 404 for each one # handlers = set() handlers.update(['py', 'php', 'asp', 'aspx', 'do', 'jsp', 'rb', 'do']) handlers.update( ['gif', 'htm', 'pl', 'cgi', 'xhtml', 'htmls', 'foobar']) if extension: handlers.add(extension) args_list = [] for extension in handlers: rand_alnum_file = rand_alnum(8) + '.' + extension url404 = domain_path.url_join(rand_alnum_file) args_list.append(url404) self._worker_pool.map(self._send_404, args_list) # # I have the bodies in self._response_body_list , but maybe they # all look the same, so I'll filter the ones that look alike. # result = [ self._response_body_list[0], ] for i in self._response_body_list: for j in self._response_body_list: if relative_distance_ge(i, j, IS_EQUAL_RATIO): # They are equal, we are ok with that continue else: # They are no equal, this means that we'll have to add this to the list result.append(j) # I don't need these anymore self._response_body_list = None # And I return the ones I need result = list(set(result)) om.out.debug('The 404 body result database has a length of ' + str(len(result)) + '.') self._404_bodies = result self._already_analyzed = True self._fingerprinted_paths.add(domain_path)
def _brute_worker(self, freq, user_field, passwd_field, login_failed_result_list, combination): ''' :param freq: A FuzzableRequest :param combination: A tuple with (user, pass) or a pass if this is a password only form. ''' if freq.get_url() not in self._found or not self._stop_on_first: freq = freq.copy() data_container = freq.get_dc() data_container = self._true_extra_fields(data_container, user_field, passwd_field) # Handle password-only forms! if user_field is not None: user, pwd = combination data_container[user_field][0] = user data_container[passwd_field][0] = pwd else: user = '******' pwd = combination data_container[passwd_field][0] = pwd freq.set_dc(data_container) try: resp = self._uri_opener.send_mutant(freq, cookies=False, grep=False) except w3afMustStopOnUrlError: return else: body = resp.get_body() body = body.replace(user, '').replace(pwd, '') if not self._matches_failed_login(body, login_failed_result_list): # Ok, this might be a valid combination. # Now test with a new invalid password to ensure our # previous possible found credentials are valid data_container[passwd_field][0] = rand_alnum(8) freq.set_dc(data_container) verif_resp = self._uri_opener.send_mutant(freq, cookies=False, grep=False) body = verif_resp.get_body() body = body.replace(user, '').replace(pwd, '') if self._matches_failed_login(body, login_failed_result_list): freq_url = freq.get_url() self._found.add(freq_url) if user_field is not None: desc = ( 'Found authentication credentials to: ' '"%s". A correct user and password combination' ' is: %s/%s' % (freq_url, user, pwd)) else: # There is no user field! desc = ('Found authentication credentials to: ' '"%s". The correct password is: "%s".' % (freq_url, pwd)) v = Vuln.from_fr('Guessable credentials', desc, severity.HIGH, resp.id, self.get_name(), freq) v['user'] = user v['pass'] = pwd v['response'] = resp kb.kb.append(self, 'auth', v) om.out.vulnerability(desc, severity=severity.HIGH) return
def replace_randomize(data): return data.replace("RANDOMIZE", rand_alnum(5))