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 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: mutant = self._xss_vuln.get_mutant() mutant = mutant.copy() mutant.set_token_value(file_content) return mutant.get_uri().url_string 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 BaseFrameworkException('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 _is_404_with_extra_request(self, http_response, clean_resp_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 clean_resp_body: The original HTML body you could find in http_response 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 = self._generate_404_filename(filename) url_404 = response_url.copy() url_404.set_file_name(relative_url) response_404 = self._send_404(url_404) 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 fuzzy_equal(clean_response_404_body, clean_resp_body, IS_EQUAL_RATIO)
def _audit(self, function_id, plugin, fuzzable_request, orig_resp): """ Since threadpool's apply_async runs the callback only when the call to this method ends without any exceptions, it is *very important* to handle exceptions correctly here. Failure to do so will end up in _task_done not called, which will make has_pending_work always return True. Python 3 has an error_callback in the apply_async method, which we could use in the future. """ args = (plugin.get_name(), fuzzable_request.get_uri()) om.out.debug('%s.audit(%s)' % args) debugging_id = rand_alnum(8) took_line = TookLine(self._w3af_core, plugin.get_name(), 'audit', debugging_id=debugging_id, method_params={'uri': fuzzable_request.get_uri()}) try: plugin.audit_with_copy(fuzzable_request, orig_resp, debugging_id) except Exception, e: self.handle_exception('audit', plugin.get_name(), fuzzable_request, e)
def _configure_debug(self): """ Configure debugging for the scans to be run. """ ptype = 'output' pname = 'text_file' enabled_output = self.w3afcore.plugins.get_enabled_plugins(ptype) enabled_output += [pname] self.w3afcore.plugins.set_plugins(enabled_output, ptype) # Now we configure the output file to point to CircleCI's artifact # directory (when run on circle) and /tmp/ when run on our # workstation output_dir = os.environ.get('CIRCLE_ARTIFACTS', tempfile.gettempdir()) rnd = rand_alnum(6) text_output = os.path.join(output_dir, 'output-%s.txt' % rnd) http_output = os.path.join(output_dir, 'output-http-%s.txt' % rnd) text_file_inst = self.w3afcore.plugins.get_plugin_inst(ptype, pname) default_opts = text_file_inst.get_options() default_opts['output_file'].set_value(text_output) default_opts['http_output_file'].set_value(http_output) default_opts['verbose'].set_value(True) print('Logging to %s' % text_output) self.w3afcore.plugins.set_plugin_options(ptype, pname, default_opts)
def __init__(self): self.func = None self.args = None self.kwargs = None self.start_time = None self.job = None self.id = rand_alnum(8)
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 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://github.com/andresriancho/w3af/issues/new \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) headers = Headers([('content-type', 'text/plain')]) put_response = self._uri_opener.PUT(url, data=rnd_content, headers=headers) # 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 _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 = '<?php echo "%(p1)s"; echo "%(p2)s"; ?>' php_jsp_code += '<? echo "%(p1)s"; echo "%(p2)s"; ?>' php_jsp_code += '<%% out.print("%(p1)s"); out.print("%(p2)s"); %%>' php_jsp_code = php_jsp_code % {'p1': rfi_result_part_1, 'p2': rfi_result_part_2} # 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 BaseFrameworkException, bfe: msg = 'Active filter detection plugin failed to receive a'\ ' response for the first request. The exception was: "%s".' \ ' Can not perform analysis.' raise BaseFrameworkException(msg % bfe)
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 _id_failed_login_page(self, mutant): """ 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 = [] form = mutant.get_dc() self._true_extra_fields(form) user_token, pass_token = form.get_login_tokens() # 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_token is not None: form.set_login_username(user) form.set_login_password(passwd) response = self._uri_opener.send_mutant(mutant, grep=False) # Save it body = self.clean_body(response, user, passwd) 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_token is not None: form.set_login_username(user) form.set_login_password(passwd) response = self._uri_opener.send_mutant(mutant, grep=False) body = self.clean_body(response, user, passwd) if not self._matches_failed_login(body, login_failed_result_list): msg = ('Failed to generate a response that matches the' ' failed login page.') raise BaseFrameworkException(msg) return login_failed_result_list
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 BaseFrameworkException: 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 create_fuzzable_request(_id): path_count = _id * 5 paths = [rand_alnum(9) for _ in xrange(path_count)] url = 'http://example.com/%s' % '/'.join(paths) form_params = FormParameters() form_params.add_field_by_attr_items([("name", "username"), ("value", "abc")]) form_params.add_field_by_attr_items([("name", "address"), ("value", "")]) form_params.set_action(URL(url)) form_params.set_method('post') form = dc_from_form_params(form_params) return FuzzableRequest.from_form(form)
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 BaseFrameworkException(msg)
def write_crash_file(self, edata): """ Writes the exception data to a random file in /tmp/ right after the exception is found. Very similar to the create_crash_file but for internal/debugging usage :return: None """ filename = 'w3af-crash-%s.txt' % rand_alnum(5) filename = os.path.join(tempfile.gettempdir(), filename) crash_dump = file(filename, "w") crash_dump.write(edata.get_details()) crash_dump.close() return filename
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 BaseFrameworkException, e: msg = 'An exception was raised while requesting "%s", the error' msg += 'message is: "%s"' om.out.error(msg % (uri, e))
def audit_return_vulns(self, fuzzable_request): """ :param fuzzable_request: The fuzzable_request instance to analyze for vulnerabilities. :return: The vulnerabilities found when running this audit plugin. """ with self._audit_return_vulns_lock: self._store_kb_vulns = True debugging_id = rand_alnum(8) try: orig_response = self.get_original_response(fuzzable_request) self.audit_with_copy(fuzzable_request, orig_response, debugging_id) except Exception, e: om.out.error(str(e)) finally:
def _get_limit_response(self, mutant): """ 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 """ mutant_copy = mutant.copy() is_digit = mutant.get_token_original_value().isdigit() value = rand_number(length=8) if is_digit else rand_alnum(length=8) mutant_copy.set_token_value(value) limit_response = self._uri_opener.send_mutant(mutant_copy) return limit_response
def bruteforce_wrapper(self, fuzzable_request): """ :param fuzzable_request: The FuzzableRequest instance to analyze :return: A list with FuzzableRequests (if we were able to bruteforce any forms/basic auth present in fuzzable_request). """ debugging_id = rand_alnum(8) self.audit(safe_deepcopy(fuzzable_request), debugging_id=debugging_id) res = [] for v in kb.kb.get(self.get_name(), 'auth'): if v.get_url() not in self._already_reported: self._already_reported.append(v.get_url()) res.append(v['request']) return res
def get_url_for_404_request(http_response, seed=1): """ :param http_response: The HTTP response to modify :return: A new URL with randomly generated filename or path that will trigger a 404. """ response_url = http_response.get_url() path = response_url.get_path() filename = response_url.get_file_name() if path == '/' or filename: relative_url = generate_404_filename(filename, seed=seed) url_404 = response_url.copy() url_404.set_file_name(relative_url) else: relative_url = '../%s/' % rand_alnum(8) url_404 = response_url.url_join(relative_url) return url_404
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 _identify_persistent_xss(self): """ This method is called to check for persistent xss. Many times a xss isn't on the page we get after the GET/POST of the xss string. This method searches for the xss string on all the pages that are known to the framework. :return: None, Vuln (if any) are saved to the kb. """ # Get all known fuzzable requests from the core fuzzable_requests = kb.kb.get_all_known_fuzzable_requests() debugging_id = rand_alnum(8) om.out.debug('Starting stored XSS search (did=%s)' % debugging_id) self._send_mutants_in_threads(self._uri_opener.send_mutant, fuzzable_requests, self._analyze_persistent_result, grep=False, cache=False, debugging_id=debugging_id)
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 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()