def _analyze_echo(self, mutant, response): ''' Analyze results of the _send_mutant method that was sent in the _with_echo method. ''' # # I will only report the vulnerability once. # if self._has_no_bug(mutant): for file_pattern_match in self._multi_in.query(response.get_body()): if file_pattern_match not in mutant.get_original_response_body(): # Search for the correct command and separator sentOs, sentSeparator = self._get_os_separator(mutant) desc = 'OS Commanding was found at: %s' % mutant.found_at() # Create the vuln obj v = Vuln.from_mutant('OS commanding vulnerability', desc, severity.HIGH, response.id, self.get_name(), mutant) v['os'] = sentOs v['separator'] = sentSeparator v.add_to_highlight(file_pattern_match) self.kb_append_uniq(self, 'os_commanding', v) break
def _with_time_delay(self, freq): ''' Tests an URL for OS Commanding vulnerabilities using time delays. :param freq: A FuzzableRequest ''' fake_mutants = create_mutants(freq, ['', ]) for mutant in fake_mutants: if self._has_bug(mutant): continue for delay_obj in self._get_wait_commands(): ed = ExactDelayController(mutant, delay_obj, self._uri_opener) success, responses = ed.delay_is_controlled() if success: desc = 'OS Commanding was found at: %s' % mutant.found_at() v = Vuln.from_mutant('OS commanding vulnerability', desc, severity.HIGH, [r.id for r in responses], self.get_name(), mutant) v['os'] = delay_obj.get_OS() v['separator'] = delay_obj.get_separator() self.kb_append_uniq(self, 'os_commanding', v) break
def _with_time_delay(self, freq): ''' Tests an URL for OS Commanding vulnerabilities using time delays. :param freq: A FuzzableRequest ''' fake_mutants = create_mutants(freq, [ '', ]) for mutant in fake_mutants: if self._has_bug(mutant): continue for delay_obj in self._get_wait_commands(): ed = ExactDelayController(mutant, delay_obj, self._uri_opener) success, responses = ed.delay_is_controlled() if success: desc = 'OS Commanding was found at: %s' % mutant.found_at() v = Vuln.from_mutant('OS commanding vulnerability', desc, severity.HIGH, [r.id for r in responses], self.get_name(), mutant) v['os'] = delay_obj.get_OS() v['separator'] = delay_obj.get_separator() self.kb_append_uniq(self, 'os_commanding', v) break
def _confirm_file_upload(self, path, mutant, http_response): """ Confirms if the file was uploaded to path :param path: The URL where we suspect that a file was uploaded to. :param mutant: The mutant that originated the file on the remote end :param http_response: The HTTP response asociated with sending mutant """ get_response = self._uri_opener.GET(path, cache=False) if not is_404(get_response) and self._has_no_bug(mutant): # This is necessary, if I don't do this, the session # saver will break cause REAL file objects can't # be picked mutant.set_mod_value("<file_object>") desc = "A file upload to a directory inside the webroot" " was found at: %s" % mutant.found_at() v = Vuln.from_mutant( "Insecure file upload", desc, severity.HIGH, [http_response.id, get_response.id], self.get_name(), mutant, ) v["file_dest"] = get_response.get_url() v["file_vars"] = mutant.get_file_vars() self.kb_append_uniq(self, "file_upload", v) return
def is_injectable(self, mutant): """ Check if this mutant is delay injectable or not. @mutant: The mutant object that I have to inject to :return: A vulnerability object or None if nothing is found """ for delay_obj in self._get_delays(): ed = ExactDelayController(mutant, delay_obj, self._uri_opener) success, responses = ed.delay_is_controlled() if success: # Now I can be sure that I found a vuln, we control the response # time with the delay desc = "Blind SQL injection using time delays was found at: %s" desc = desc % mutant.found_at() response_ids = [r.id for r in responses] v = Vuln.from_mutant( "Blind SQL injection vulnerability", desc, severity.HIGH, response_ids, "blind_sqli", mutant ) om.out.debug(v.get_desc()) return v return None
def _analyze_result(self, mutant, response): ''' Analyze results of the _send_mutant method. ''' # # I will only report the vulnerability once. # if self._has_no_bug(mutant): if self._header_was_injected(mutant, response): desc = 'Response splitting was found at: %s' % mutant.found_at() v = Vuln.from_mutant('Response splitting vulnerability', desc, severity.MEDIUM, response.id, self.get_name(), mutant) self.kb_append_uniq(self, 'response_splitting', v) # When trying to send a response splitting to php 5.1.2 I get : # Header may not contain more than a single header, new line detected for error in self.HEADER_ERRORS: if error in response: desc = 'The variable "%s" at URL "%s" modifies the HTTP'\ ' response headers, but this error was sent while'\ ' testing for response splitting: "%s".' desc = desc % (mutant.get_var(), mutant.get_url(), error) i = Info.from_mutant('Parameter modifies response headers', desc, response.id, self.get_name(), mutant) self.kb_append_uniq(self, 'response_splitting', i) return
def _report_vuln(self, mutant, response, mod_value): ''' Create a Vuln object and store it in the KB. :return: None ''' csp_protects = site_protected_against_xss_by_csp(response) vuln_severity = severity.LOW if csp_protects else severity.MEDIUM desc = 'A Cross Site Scripting vulnerability was found at: %s' desc = desc % mutant.found_at() if csp_protects: desc += 'The risk associated with this vulnerability was lowered'\ ' because the site correctly implements CSP. The'\ ' vulnerability is still a risk for the application since'\ ' only the latest versions of some browsers implement CSP'\ ' checking.' v = Vuln.from_mutant('Cross site scripting vulnerability', desc, vuln_severity, response.id, self.get_name(), mutant) v.add_to_highlight(mod_value) self.kb_append_uniq(self, 'xss', v)
def _confirm_file_upload(self, path, mutant, http_response): ''' Confirms if the file was uploaded to path :param path: The URL where we suspect that a file was uploaded to. :param mutant: The mutant that originated the file on the remote end :param http_response: The HTTP response asociated with sending mutant ''' get_response = self._uri_opener.GET(path, cache=False) if not is_404(get_response) and self._has_no_bug(mutant): # This is necessary, if I don't do this, the session # saver will break cause REAL file objects can't # be picked mutant.set_mod_value('<file_object>') desc = 'A file upload to a directory inside the webroot' \ ' was found at: %s' % mutant.found_at() v = Vuln.from_mutant('Insecure file upload', desc, severity.HIGH, [http_response.id, get_response.id], self.get_name(), mutant) v['file_dest'] = get_response.get_url() v['file_vars'] = mutant.get_file_vars() self.kb_append_uniq(self, 'file_upload', v) return
def _analyze_result(self, mutant, response): ''' Analyze results of the _send_mutant method. ''' if self._has_bug(mutant): return dom = response.get_dom() if response.is_text_or_html() and dom is not None: elem_list = self._tag_xpath(dom) for element in elem_list: if 'src' not in element.attrib: return [] src_attr = element.attrib['src'] for url in self._test_urls: if src_attr.startswith(url): # Vuln vuln! desc = 'A phishing vector was found at: %s' desc = desc % mutant.found_at() v = Vuln.from_mutant('Phishing vector', desc, severity.LOW, response.id, self.get_name(), mutant) v.add_to_highlight(src_attr) self.kb_append_uniq(self, 'phishing_vector', v)
def audit(self, freq, orig_response): ''' Tests an URL for ReDoS vulnerabilities using time delays. :param freq: A FuzzableRequest ''' if self.ignore_this_request(freq): return fake_mutants = create_mutants(freq, ['', ]) for mutant in fake_mutants: for delay_obj in self.get_delays(): adc = AproxDelayController(mutant, delay_obj, self._uri_opener, delay_setting=EXPONENTIALLY) success, responses = adc.delay_is_controlled() if success: # Now I can be sure that I found a vuln, we control the # response time with the delay desc = 'ReDoS was found at: %s' % mutant.found_at() response_ids = [r.id for r in responses] v = Vuln.from_mutant('ReDoS vulnerability', desc, severity.MEDIUM, response_ids, self.get_name(), mutant) self.kb_append_uniq(self, 'redos', v) break
def test_from_mutant(self): dc = DataContainer() url = URL('http://moth/') payloads = ['abc', 'def'] dc['a'] = ['1', ] dc['b'] = ['2', ] freq = FuzzableRequest(url, dc=dc) fuzzer_config = {} created_mutants = Mutant.create_mutants(freq, payloads, [], False, fuzzer_config) mutant = created_mutants[0] inst = Vuln.from_mutant('TestCase', 'desc' * 30, 'High', 1, 'plugin_name', mutant) self.assertIsInstance(inst, Vuln) self.assertEqual(inst.get_uri(), mutant.get_uri()) self.assertEqual(inst.get_url(), mutant.get_url()) self.assertEqual(inst.get_method(), mutant.get_method()) self.assertEqual(inst.get_dc(), mutant.get_dc()) self.assertEqual(inst.get_var(), mutant.get_var())
def _analyze_result(self, rfi_data, mutant, response): ''' Analyze results of the _send_mutant method. ''' if rfi_data.rfi_result in response: desc = 'A remote file inclusion vulnerability that allows remote' \ ' code execution was found at: %s' % mutant.found_at() v = Vuln.from_mutant('Remote code execution', desc, severity.HIGH, response.id, self.get_name(), mutant) self._vulns.append(v) elif rfi_data.rfi_result_part_1 in response \ and rfi_data.rfi_result_part_2 in response: # This means that both parts ARE in the response body but the # rfi_data.rfi_result is NOT in it. In other words, the remote # content was embedded but not executed desc = 'A remote file inclusion vulnerability without code' \ ' execution was found at: %s' % mutant.found_at() v = Vuln.from_mutant('Remote file inclusion', desc, severity.MEDIUM, response.id, self.get_name(), mutant) self._vulns.append(v) else: # # Analyze some errors that indicate that there is a RFI but # with some "configuration problems" # for error in self.RFI_ERRORS: if error in response and not error in mutant.get_original_response_body(): desc = 'A potential remote file inclusion vulnerability' \ ' was identified by the means of application error' \ ' messages at: %s' % mutant.found_at() v = Vuln.from_mutant('Potential remote file inclusion', desc, severity.LOW, response.id, self.get_name(), mutant) v.add_to_highlight(error) self._vulns.append(v) break
def _analyze_result(self, mutant, response): ''' Analyze results of the _send_mutant method. ''' if self._find_redirect(response): desc = 'Global redirect was found at: ' + mutant.found_at() v = Vuln.from_mutant('Insecure redirection', desc, severity.MEDIUM, response.id, self.get_name(), mutant) self.kb_append_uniq(self, 'global_redirect', v)
def _analyze_result(self, mutant, response): ''' Analyze results of the _send_mutant method. ''' for error_str in self._multi_in.query(response.body): # And not in the original response if error_str not in mutant.get_original_response_body() and \ self._has_no_bug(mutant): desc = 'A potential buffer overflow (accurate detection is' \ ' hard...) was found at: %s' % mutant.found_at() v = Vuln.from_mutant('Buffer overflow vulnerability', desc, severity.MEDIUM, response.id, self.get_name(), mutant) v.add_to_highlight(error_str) self.kb_append_uniq(self, 'buffer_overflow', v)
def _analyze_result(self, mutant, response): ''' Analyze the result of the previously sent request. :return: None, save the vuln to the kb. ''' if self._has_no_bug(mutant): e_res = self._extract_result_from_payload(mutant.get_mod_value()) if e_res in response and not e_res in mutant.get_original_response_body(): desc = 'Server side include (SSI) was found at: %s' desc = 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(e_res) self.kb_append_uniq(self, 'ssi', v)
def _analyze_echo(self, mutant, response): ''' Analyze results of the _send_mutant method that was sent in the _fuzz_with_echo method. ''' eval_error_list = self._find_eval_result(response) for eval_error in eval_error_list: if not re.search(eval_error, mutant.get_original_response_body(), re.I): desc = 'eval() input injection was found at: %s' desc = desc % mutant.found_at() v = Vuln.from_mutant('eval() input injection vulnerability', desc, severity.HIGH, response.id, self.get_name(), mutant) self.kb_append_uniq(self, 'eval', v)
def analyze_persistent(freq, response): for matched_expected_result in multi_in_inst.query(response.get_body()): # We found one of the expected results, now we search the # self._persistent_data to find which of the mutants sent it # and create the vulnerability mutant = self._expected_res_mutant[matched_expected_result] desc = 'Server side include (SSI) was found at: %s' \ ' The result of that injection is shown by browsing'\ ' to "%s".' desc = 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 _analyze_result(self, mutant, response): ''' Analyze results of the _send_mutant method. ''' # I will only report the vulnerability once. if self._has_no_bug(mutant): mx_error_list = self._multi_in.query(response.body) for mx_error in mx_error_list: if mx_error not in mutant.get_original_response_body(): desc = 'MX injection was found at: %s' % mutant.found_at() v = Vuln.from_mutant('MX injection vulnerability', desc, severity.MEDIUM, response.id, self.get_name(), mutant) v.add_to_highlight(mx_error) self.kb_append_uniq(self, 'mx_injection', v) break
def _analyze_result(self, mutant, response): ''' Analyze results of the _send_mutant method. ''' # # I will only report the vulnerability once. # if self._has_no_bug(mutant): xpath_error_list = self._find_xpath_error(response) for xpath_error in xpath_error_list: if xpath_error not in mutant.get_original_response_body(): desc = 'XPATH injection was found at: %s' % mutant.found_at() v = Vuln.from_mutant('XPATH injection vulnerability', desc, severity.MEDIUM, response.id, self.get_name(), mutant) v.add_to_highlight(xpath_error) self.kb_append_uniq(self, 'xpath', v) break
def end(self): ''' This method is called when the plugin wont be used anymore. ''' all_vulns_and_infos = kb.kb.get_all_vulns() all_vulns_and_infos.extend(kb.kb.get_all_infos()) for url, variable, mutant, id_list in self._potential_vulns: for info in all_vulns_and_infos: if info.get_var() == variable and info.get_url() == url: break else: desc = 'An unidentified vulnerability was found at: %s' desc = desc % mutant.found_at() v = Vuln.from_mutant('Unidentified vulnerability', desc, severity.MEDIUM, id_list, self.get_name(), mutant) self.kb_append_uniq(self, 'generic', v) self._potential_vulns.cleanup()
def _analyze_result(self, mutant, response): ''' Analyze results of the _send_mutant method. ''' # # I will only report the vulnerability once. # if self._has_no_bug(mutant): for preg_error_string in self._find_preg_error(response): if preg_error_string not in mutant.get_original_response_body(): desc = 'Unsafe usage of preg_replace was found at: %s' desc = desc % mutant.found_at() v = Vuln.from_mutant('Unsafe preg_replace usage', desc, severity.HIGH, response.id, self.get_name(), mutant) v.add_to_highlight(preg_error_string) self.kb_append_uniq(self, 'preg_replace', v) break
def _analyze_result(self, mutant, response): ''' Analyze results of the _send_mutant method. ''' # # I will only report the vulnerability once. # if self._has_no_bug(mutant): ldap_error_list = self._find_ldap_error(response) for ldap_error_string in ldap_error_list: if ldap_error_string not in mutant.get_original_response_body(): desc = 'LDAP injection was found at: %s' % mutant.found_at() v = Vuln.from_mutant('LDAP injection vulnerability', desc, severity.HIGH, response.id, self.get_name(), mutant) v.add_to_highlight(ldap_error_string) self.kb_append_uniq(self, 'ldapi', v) break
def _analyze_result(self, mutant, response): ''' Analyze results of the _send_mutant method. ''' sql_error_list = self._findsql_error(response) orig_resp_body = mutant.get_original_response_body() for sql_regex, sql_error_string, dbms_type in sql_error_list: if not sql_regex.search(orig_resp_body): if self._has_no_bug(mutant): # Create the vuln, desc = 'SQL injection in a %s was found at: %s' desc = desc % (dbms_type, mutant.found_at()) v = Vuln.from_mutant('SQL injection', desc, severity.HIGH, response.id, self.get_name(), mutant) v.add_to_highlight(sql_error_string) v['error'] = sql_error_string v['db'] = dbms_type self.kb_append_uniq(self, 'sqli', v) break
def _report_persistent_vuln(self, mutant, response, mutant_response_id, mod_value, fuzzable_request): ''' Report a persistent XSS vulnerability to the core. :return: None, a vulnerability is saved in the KB. ''' response_ids = [response.id, mutant_response_id] name = 'Persistent Cross-Site Scripting vulnerability' desc = 'A persistent Cross Site Scripting vulnerability'\ ' was found by sending "%s" to the "%s" parameter'\ ' at %s, which is echoed when browsing to %s.' desc = desc % (mod_value, mutant.get_var(), mutant.get_url(), response.get_url()) csp_protects = site_protected_against_xss_by_csp(response) vuln_severity = severity.MEDIUM if csp_protects else severity.HIGH if csp_protects: desc += 'The risk associated with this vulnerability was lowered'\ ' because the site correctly implements CSP. The'\ ' vulnerability is still a risk for the application since'\ ' only the latest versions of some browsers implement CSP'\ ' checking.' v = Vuln.from_mutant(name, desc, vuln_severity, response_ids, self.get_name(), mutant) v['persistent'] = True v['write_payload'] = mutant v['read_payload'] = fuzzable_request v.add_to_highlight(mutant.get_mod_value()) om.out.vulnerability(v.get_desc()) self.kb_append_uniq(self, 'xss', v)
def _test_delay(self, mutant): ''' Try to delay the response and save a vulnerability if successful ''' if self._has_bug(mutant): return for delay_obj in self.WAIT_OBJ: ed_inst = ExactDelayController(mutant, delay_obj, self._uri_opener) success, responses = ed_inst.delay_is_controlled() if success: desc = 'eval() input injection was found at: %s' desc = desc % mutant.found_at() response_ids = [r.id for r in responses] v = Vuln.from_mutant('eval() input injection vulnerability', desc, severity.HIGH, response_ids, self.get_name(), mutant) self.kb_append_uniq(self, 'eval', v) break
def _find_bsql(self, mutant, statement_tuple, statement_type): ''' Is the main algorithm for finding blind SQL injections. :return: A vulnerability object or None if nothing is found ''' true_statement = statement_tuple[0] false_statement = statement_tuple[1] mutant.set_mod_value(true_statement) _, body_true_response = self.send_clean(mutant) mutant.set_mod_value(false_statement) _, body_false_response = self.send_clean(mutant) if body_true_response == body_false_response: # # There is NO CHANGE between the true and false responses. # NO WAY I'm going to detect a blind SQL injection using # response diffs in this case. # return None compare_diff = False om.out.debug('Comparing body_true_response and body_false_response.') if self.equal_with_limit(body_true_response, body_false_response, compare_diff): # # They might be equal because of various reasons, in the best # case scenario there IS a blind SQL injection but the % of the # HTTP response body controlled by it is so small that the equal # ratio is not catching it. # compare_diff = True syntax_error = "d'z'0" mutant.set_mod_value(syntax_error) syntax_error_response, body_syntax_error_response = self.send_clean( mutant) self.debug( 'Comparing body_true_response and body_syntax_error_response.') if self.equal_with_limit(body_true_response, body_syntax_error_response, compare_diff): return None # Verify the injection! statements = self._get_statements(mutant) second_true_stm = statements[statement_type][0] second_false_stm = statements[statement_type][1] mutant.set_mod_value(second_true_stm) second_true_response, body_second_true_response = self.send_clean( mutant) mutant.set_mod_value(second_false_stm) second_false_response, body_second_false_response = self.send_clean(mutant) self.debug( 'Comparing body_second_true_response and body_true_response.') if not self.equal_with_limit(body_second_true_response, body_true_response, compare_diff): return None self.debug('Comparing body_second_false_response and body_false_response.') if self.equal_with_limit(body_second_false_response, body_false_response, compare_diff): response_ids = [second_false_response.id, second_true_response.id] desc = 'Blind SQL injection was found at: "%s", using'\ ' HTTP method %s. The injectable parameter is: "%s"' desc = desc % (mutant.get_url(), mutant.get_method(), mutant.get_var()) v = Vuln.from_mutant('Blind SQL injection vulnerability', desc, severity.HIGH, response_ids, 'blind_sqli', mutant) om.out.debug(v.get_desc()) v['type'] = statement_type v['true_html'] = second_true_response.get_body() v['false_html'] = second_false_response.get_body() v['error_html'] = syntax_error_response.get_body() return v return None
def _analyze_result(self, mutant, response): ''' Analyze results of the _send_mutant method. Try to find the local file inclusions. ''' # I analyze the response searching for a specific PHP error string # that tells me that open_basedir is enabled, and our request triggered # the restriction. If open_basedir is in use, it makes no sense to keep # trying to read "/etc/passwd", that is why this variable is used to # determine which tests to send if it was possible to detect the usage # of this security feature. if not self._open_basedir: basedir_warning = 'open_basedir restriction in effect' if basedir_warning in response and \ basedir_warning not in mutant.get_original_response_body(): self._open_basedir = True # # I will only report the vulnerability once. # if self._has_bug(mutant): return # # Identify the vulnerability # file_content_list = self._find_file(response) for file_pattern_match in file_content_list: if file_pattern_match not in mutant.get_original_response_body(): desc = 'Local File Inclusion was found at: %s' desc = desc % mutant.found_at() v = Vuln.from_mutant('Local file inclusion vulnerability', desc, severity.MEDIUM, response.id, self.get_name(), mutant) v['file_pattern'] = file_pattern_match v.add_to_highlight(file_pattern_match) self.kb_append_uniq(self, 'lfi', v) return # # If the vulnerability could not be identified by matching strings that commonly # appear in "/etc/passwd", then I'll check one more thing... # (note that this is run if no vulns were identified) # # http://host.tld/show_user.php?id=show_user.php if mutant.get_mod_value() == mutant.get_url().get_file_name(): match, lang = is_source_file(response.get_body()) if match: # We were able to read the source code of the file that is # vulnerable to local file read desc = 'An arbitrary local file read vulnerability was'\ ' found at: %s' % mutant.found_at() v = Vuln.from_mutant('Local file inclusion vulnerability', desc, severity.MEDIUM, response.id, self.get_name(), mutant) # # Set which part of the source code to match # match_source_code = match.group(0) v['file_pattern'] = match_source_code self.kb_append_uniq(self, 'lfi', v) return # # Check for interesting errors (note that this is run if no vulns were # identified) # for regex in self.get_include_errors(): match = regex.search(response.get_body()) if match and not regex.search(mutant.get_original_response_body()): desc = 'A file read error was found at: %s' desc = desc % mutant.found_at() i = Info.from_mutant('File read error', desc, response.id, self.get_name(), mutant) self.kb_append_uniq(self, 'error', i)