def _analyze_result(self, mutant, response): """ Analyze results of the send_mutant method. """ orig_resp_body = mutant.get_original_response_body() body = response.get_body() for pattern_match in self._find_patterns(body): # Remove false positives if pattern_match in orig_resp_body: continue # Only report vulnerabilities once if self._has_bug(mutant): return # Create the vulnerability! desc = 'An XML External Entity injection was found at: %s' desc %= mutant.found_at() v = Vuln.from_mutant('XML External Entity', desc, severity.HIGH, response.id, self.get_name(), mutant) v.add_to_highlight(pattern_match) self.kb_append_uniq(self, 'xxe', v) return # We get here when there are no vulnerabilities in the response # but we still want to flag any parsing errors which might be # pointers to other (more complex to identify and exploit) # vulnerabilities for parser_error in self.parser_errors_multi_in.query(body): # Do not report that we found an error when we already found # something with higher priority in the same mutant if self._has_bug(mutant): return # Do not report the same error twice if self._has_bug(mutant, kb_varname='errors'): return desc = ('An XML library parsing error was found at: %s. These' ' errors usually indicate that an XML injection is' ' possible.') desc %= mutant.found_at() v = Vuln.from_mutant('XML Parsing Error', desc, severity.LOW, response.id, self.get_name(), mutant) v.add_to_highlight(parser_error) self.kb_append_uniq(self, 'errors', v) return
def _analyze_result(self, mutant, response): """ Analyze the result of the previously sent request. :return: None, save the vuln to the kb. """ # Store the mutants in order to be able to analyze the persistent case # later expected_results = self._get_expected_results(mutant) for expected_result in expected_results: self._expected_mutant_dict[expected_result] = mutant # Now we analyze the "reflected" case if self._has_bug(mutant): return for expected_result in expected_results: if expected_result not in response: continue if expected_result in mutant.get_original_response_body(): continue desc = "Server side include (SSI) was found at: %s" 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(expected_result) self.kb_append_uniq(self, "ssi", v)
def _analyze_persistent(self, freq, response): """ Analyze the response of sending each fuzzable request found by the framework, trying to identify any locations where we might have injected a payload. :param freq: The fuzzable request :param response: The HTTP response :return: None, vulns are stored in KB """ for matched_expected_result in self._persistent_multi_in.query(response.get_body()): # We found one of the expected results, now we search the # self._expected_mutant_dict to find which of the mutants sent it # and create the vulnerability mutant = self._expected_mutant_dict[matched_expected_result] desc = ('Server side include (SSI) was found at: %s' ' The result of that injection is shown by browsing' ' to "%s".') 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): for error in self.ERROR_STRINGS: # Check if the error string is in the response if error in response.body and \ error not in mutant.get_original_response_body(): desc = 'A possible (detection is really hard...) format'\ ' string vulnerability was found at: %s' desc = desc % mutant.found_at() v = Vuln.from_mutant('Format string vulnerability', desc, severity.MEDIUM, response.id, self.get_name(), mutant) v.add_to_highlight(error) self.kb_append_uniq(self, 'format_string', v) break
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 _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 _with_time_delay(self, freq): """ Tests an URLs for shell shock vulnerabilities using time delays. :param freq: A FuzzableRequest :return: True if a vulnerability was found """ mutant = self.create_mutant(freq, TEST_HEADER) for delay_obj in self.DELAY_TESTS: ed = ExactDelayController(mutant, delay_obj, self._uri_opener) success, responses = ed.delay_is_controlled() if success: mutant.set_token_value(delay_obj.get_string_for_delay(3)) desc = u"Shell shock was found at: %s" % mutant.found_at() v = Vuln.from_mutant( u"Shell shock vulnerability", desc, severity.HIGH, [r.id for r in responses], self.get_name(), mutant, ) self.kb_append_uniq(self, "shell_shock", v) return True
def _analyze_persistent(self, freq, response): """ Analyze the response of sending each fuzzable request found by the framework, trying to identify any locations where we might have injected a payload. :param freq: The fuzzable request :param response: The HTTP response :return: None, vulns are stored in KB """ multi_in_inst = multi_in(self._expected_mutant_dict.keys()) for matched_expected_result in multi_in_inst.query( response.get_body()): # We found one of the expected results, now we search the # self._expected_mutant_dict to find which of the mutants sent it # and create the vulnerability mutant = self._expected_mutant_dict[matched_expected_result] desc = ('Server side include (SSI) was found at: %s' ' The result of that injection is shown by browsing' ' to "%s".') 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 audit(self, freq, orig_response): if not self._dns_zone: om.out.debug("DNS zone not configured!") return self.fqdn = "xxe.{target}.{domain}".format( target=freq.get_uri().get_domain(), domain=self._dns_zone) for mutant in create_mutants(freq, [ '&a;', ]): if isinstance(mutant, XMLMutant): mutant.get_dc().doctype = '<!DOCTYPE aa [\n' mutant.get_dc( ).doctype += ' <!ENTITY a SYSTEM "http://{FQDN}">\n'.format( FQDN=self.fqdn) mutant.get_dc().doctype += ']>' try: response = self._uri_opener.send_mutant(mutant, cache=False, timeout=10) if self.check('get.' + self.fqdn): desc = 'XXE injection at: "%s", using'\ ' HTTP method %s. The injectable parameter may be: "%s"' desc = desc % (mutant.get_url(), mutant.get_method(), mutant.get_token_name()) vuln = Vuln.from_mutant('XXE injection vulnerability', desc, severity.HIGH, response.id, 'xxe', mutant) om.out.debug(vuln.get_desc()) om.out.vulnerability("XXE injection", severity=severity.HIGH) except HTTPRequestException: om.out.debug("HTTPRequestException") except Exception as e: om.out.debug(str(e))
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, mutant, response): """ Analyze results of the _send_mutant method. """ # # I will only report the vulnerability once. # if self._has_no_bug(mutant): for error in self.ERROR_STRINGS: # Check if the error string is in the response if error in response.body and \ error not in mutant.get_original_response_body(): desc = 'A possible (detection is really hard...) format' \ ' string vulnerability was found at: %s' desc = desc % mutant.found_at() v = Vuln.from_mutant('Format string vulnerability', desc, severity.MEDIUM, response.id, self.get_name(), mutant) v.add_to_highlight(error) self.kb_append_uniq(self, 'format_string', 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): 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)
def batch_injection_test(self, freq, orig_response): """ Uses the batch injection technique to find memcache injections """ # shortcuts send_clean = self._uri_opener.send_clean orig_body = orig_response.get_body() for mutant in create_mutants(freq, ['']): # trying to break normal execution flow with ERROR_1 payload mutant.set_token_value(self.ERROR_1) error_1_response, body_error_1_response = send_clean(mutant) if fuzzy_equal(orig_body, body_error_1_response, self._eq_limit): # # if we manage to break execution flow, there is a potential # injection otherwise - no injection! # continue # trying the correct injection request, to confirm that we've found # it! mutant.set_token_value(self.OK) ok_response, body_ok_response = send_clean(mutant) if fuzzy_equal(body_error_1_response, body_ok_response, self._eq_limit): # # The "OK" and "ERROR_1" responses are equal, this means that # we're not in a memcached injection # continue # ERROR_2 request to just make sure that we're in a memcached case mutant.set_token_value(self.ERROR_2) error_2_response, body_error_2_response = send_clean(mutant) if fuzzy_equal(orig_body, body_error_2_response, self._eq_limit): # # now requests should be different again, otherwise injection # is not confirmed # continue response_ids = [error_1_response.id, ok_response.id, error_2_response.id] desc = ('Memcache injection was found at: "%s", using' ' HTTP method %s. The injectable parameter is: "%s"') desc %= (mutant.get_url(), mutant.get_method(), mutant.get_token_name()) v = Vuln.from_mutant('Memcache injection vulnerability', desc, severity.HIGH, response_ids, 'memcachei', mutant) self.kb_append_uniq(self, 'memcachei', v)
def _analyze_result(self, mutant, response): """ Analyze the result of the previously sent request. :return: None, save the vuln to the kb. """ # Store the mutants in order to be able to analyze the persistent case # later expected_results = self._get_expected_results(mutant) for expected_result in expected_results: self._expected_mutant_dict[expected_result] = mutant # Now we analyze the "reflected" case if self._has_bug(mutant): return for expected_result in expected_results: if expected_result not in response: continue if expected_result in mutant.get_original_response_body(): continue desc = 'Server side include (SSI) was found at: %s' 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(expected_result) self.kb_append_uniq(self, 'ssi', v)
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, debugging_id): """ 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 associated with sending mutant """ response = self._uri_opener.GET(path, cache=False, grep=False, debugging_id=debugging_id) if mutant.file_payload not in response.body: return if self._has_bug(mutant): return desc = 'A file upload to a directory inside the webroot was found at: %s' desc %= mutant.found_at() v = Vuln.from_mutant('Insecure file upload', desc, severity.HIGH, [http_response.id, response.id], self.get_name(), mutant) v['file_dest'] = response.get_url() v['file_vars'] = mutant.get_file_vars() self.kb_append_uniq(self, 'file_upload', v)
def _analyze_result(self, mutant, response): """ Analyze results of the _send_mutant method. """ if self._has_bug(mutant): return 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".' ) args = (mutant.get_token_name(), mutant.get_url(), error) desc = desc % args 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 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 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, 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 _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_bug(mutant): return 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 _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 _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)
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 _with_header_echo_injection(self, freq): """ We're sending a payload that will trigger the injection of various headers in the HTTP response body. :param freq: A FuzzableRequest :return: True if a vulnerability was found """ injected_header = 'shellshock' injected_value = 'check' payload = '() { :;}; echo "%s: %s"' % (injected_header, injected_value) mutant = self.create_mutant(freq, TEST_HEADER) mutant.set_token_value(payload) response = self._uri_opener.send_mutant(mutant) header_value, header_name = response.get_headers().iget(injected_header) if header_value is not None and injected_value in header_value.lower(): desc = u'Shell shock was found at: %s' % mutant.found_at() v = Vuln.from_mutant(u'Shell shock vulnerability', desc, severity.HIGH, [response.id], self.get_name(), mutant) self.kb_append_uniq(self, 'shell_shock', v) return True
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 _find_delay_in_mutant(self, mutant, delay_obj, debugging_id): """ Try to delay the response and save a vulnerability if successful :param mutant: The mutant to modify and test :param delay_obj: The delay to use :param debugging_id: The debugging ID for logging """ adc = AproxDelayController(mutant, delay_obj, self._uri_opener, delay_setting=EXPONENTIALLY) adc.set_debugging_id(debugging_id) success, responses = adc.delay_is_controlled() if not success: return # 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)
def _analyze_result(self, mutant, response): """ Analyze results of the _send_mutant method. """ if not response.is_text_or_html(): return if self._has_bug(mutant): return for tag in mp_doc_parser.get_tags_by_filter(response, self.TAGS): src_attr = tag.attrib.get('src', None) if src_attr is None: continue for url in self._test_urls: if not src_attr.startswith(url): continue # Vuln vuln! desc = 'A phishing vector was found at: %s' 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) break
def _analyze_result(self, mutant, response): """ Analyze results of the _send_mutant method. """ if self._has_bug(mutant): return 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".') args = (mutant.get_token_name(), mutant.get_url(), error) desc = desc % args 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 _with_header_echo_injection(self, freq, debugging_id): """ We're sending a payload that will trigger the injection of various headers in the HTTP response body. :param freq: A FuzzableRequest :return: True if a vulnerability was found """ injected_header = 'shellshock' injected_value = 'check' payload = '() { :;}; echo "%s: %s"' % (injected_header, injected_value) mutant = self.create_mutant(freq, TEST_HEADER) mutant.set_token_value(payload) response = self._uri_opener.send_mutant(mutant, debugging_id=debugging_id) header_value, header_name = response.get_headers().iget(injected_header) if header_value is not None and injected_value in header_value.lower(): desc = u'Shell shock was found at: %s' % mutant.found_at() v = Vuln.from_mutant(u'Shell shock vulnerability', desc, severity.HIGH, [response.id], self.get_name(), mutant) self.kb_append_uniq(self, 'shell_shock', v) return True
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 _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 %= 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 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 not success: continue # 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) # Only test regular expressions until we find a delay 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 batch_injection_test(self, freq, orig_response): """ Uses the batch injection technique to find memcache injections """ # shortcuts send_clean = self._uri_opener.send_clean orig_body = orig_response.get_body() for mutant in create_mutants(freq, ['']): # trying to break normal execution flow with ERROR_1 payload mutant.set_token_value(self.ERROR_1) error_1_response, body_error_1_response = send_clean(mutant) if fuzzy_equal(orig_body, body_error_1_response, self._eq_limit): # # if we manage to break execution flow, there is a potential # injection otherwise - no injection! # continue # trying the correct injection request, to confirm that we've found # it! mutant.set_token_value(self.OK) ok_response, body_ok_response = send_clean(mutant) if fuzzy_equal(body_error_1_response, body_ok_response, self._eq_limit): # # The "OK" and "ERROR_1" responses are equal, this means that # we're not in a memcached injection # continue # ERROR_2 request to just make sure that we're in a memcached case mutant.set_token_value(self.ERROR_2) error_2_response, body_error_2_response = send_clean(mutant) if fuzzy_equal(orig_body, body_error_2_response, self._eq_limit): # # now requests should be different again, otherwise injection # is not confirmed # continue response_ids = [ error_1_response.id, ok_response.id, error_2_response.id ] desc = ('Memcache injection was found at: "%s", using' ' HTTP method %s. The injectable parameter is: "%s"') desc %= (mutant.get_url(), mutant.get_method(), mutant.get_token_name()) v = Vuln.from_mutant('Memcache injection vulnerability', desc, severity.HIGH, response_ids, 'memcachei', mutant) self.kb_append_uniq(self, 'memcachei', v)
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, 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
class redos(AuditPlugin): """ Find ReDoS vulnerabilities. :author: Sebastien Duquette ( [email protected] ) :author: Andres Riancho ([email protected]) """ def audit(self, freq, orig_response, debugging_id): """ Tests an URL for ReDoS vulnerabilities using time delays. :param freq: A FuzzableRequest :param orig_response: The HTTP response associated with the fuzzable request :param debugging_id: A unique identifier for this call to audit() """ if self.ignore_this_request(freq): return self._send_mutants_in_threads(func=self._find_delay_in_mutant, iterable=self._generate_delay_tests( freq, debugging_id), callback=lambda x, y: None) def _generate_delay_tests(self, freq, debugging_id): for mutant in create_mutants(freq, [ '', ]): for delay_obj in self.get_delays(): yield mutant, delay_obj, debugging_id def _find_delay_in_mutant(self, (mutant, delay_obj, debugging_id)): """ Try to delay the response and save a vulnerability if successful :param mutant: The mutant to modify and test :param delay_obj: The delay to use :param debugging_id: The debugging ID for logging """ adc = AproxDelayController(mutant, delay_obj, self._uri_opener, delay_setting=EXPONENTIALLY) adc.set_debugging_id(debugging_id) success, responses = adc.delay_is_controlled() if not success: return # 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)
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 _from_csv_get_vulns(self): file_vulns = [] vuln_reader = csv.reader(open(self.OUTPUT_FILE, 'rb'), delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL) for name, method, uri, var, post_data, _id, desc in vuln_reader: mutant = create_mutant_from_params(method, uri, var, post_data) v = Vuln.from_mutant(name, desc, 'High', json.loads(_id), 'TestCase', mutant) file_vulns.append(v) return file_vulns
def is_injectable(self, mutant): is_vuln = False resp_len_orig = len(self._orig_response.get_body()) mutant.set_token_value(self._get_random_letters(10)) resp_valid = self._do_request(mutant) resp_len_valid = len(resp_valid.get_body()) resp_time_valid = resp_valid.get_wait_time() for injection in BOOLEAN_BASED: mutant.set_token_value(injection) resp_injection = self._do_request(mutant) if not resp_injection: is_vuln = True break resp_len_injection = len(resp_injection.get_body()) if resp_len_injection != resp_len_valid != resp_len_orig: is_vuln = True break sleep(self._timeout) if not is_vuln: for injection in TIME_BASED: mutant.set_token_value(injection) resp_injection = self._do_request(mutant) if not resp_injection: is_vuln = True break resp_time_injection = resp_injection.get_wait_time() if resp_time_injection > resp_time_valid + 1: is_vuln = True break sleep(self._timeout) if is_vuln: response_ids = [resp_valid.id, resp_injection.id] desc = 'NOSQL injection at: "%s", using'\ ' HTTP method %s. The injectable parameter may be: "%s"' desc = desc % (mutant.get_url(), mutant.get_method(), mutant.get_token_name()) vuln = Vuln.from_mutant('NOSQL injection vulnerability', desc, severity.HIGH, response_ids, 'nosqli', mutant) om.out.debug(vuln.get_desc()) om.out.vulnerability("NOSQL injection", severity=severity.HIGH) vuln['valid_html'] = self.resp_valid.get_body() vuln['error_html'] = self.resp_injection.get_body() return vuln
def _from_csv_get_vulns(self): file_vulns = [] vuln_reader = csv.reader(open(self.OUTPUT_FILE, 'rb'), delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL) for severity, name, method, uri, var, post_data, _id, desc in vuln_reader: mutant = create_mutant_from_params(method, uri, var, post_data) v = Vuln.from_mutant(name, desc, severity, json.loads(_id), 'TestCase', mutant) file_vulns.append(v) return file_vulns
def audit(self, freq, orig_response): if not self._dns_zone: om.out.debug("DNS zone not configured!") return self.fqdn_imagemagic_exist = "im.{target}.{domain}".format( target=freq.get_uri().get_domain(), domain=self._dns_zone ) self.fqdn_imagemagic_vuln = "rce.{target}.{domain}".format( target=freq.get_uri().get_domain(), domain=self._dns_zone ) PAYLOAD = imagemagic.PAYLOAD.format( detect=self.fqdn_imagemagic_exist, rce=self.fqdn_imagemagic_vuln ) for mutant in create_mutants(freq, [PAYLOAD, ]): if isinstance(mutant, FileContentMutant): try: response = self._uri_opener.send_mutant( mutant, cache=False, timeout=10 ) if self.check('get.' + self.fqdn_imagemagic_exist): desc = 'Imagemagic found: "%s"' % response.get_uri() i = Vuln.from_mutant('Imagemagic detected', desc, severity.INFORMATION, response.id, self.get_name(), mutant) i.add_to_highlight('Imagemagic') self.kb_append_uniq(self, 'imagemagic_detect', i) if self.check('get.' + self.fqdn_imagemagic_vuln): desc = 'Imagemagic OS-injection at: "%s", using'\ ' HTTP method %s. The injectable parameter may be: "%s"' desc = desc % ( mutant.get_url(), mutant.get_method(), mutant.get_token_name() ) vuln = Vuln.from_mutant('Imagemagic OS-injection vulnerability', desc, severity.HIGH, response.id, self.get_name(), mutant) self.kb_append_uniq(self, 'imagemagic_rce', vuln) except HTTPRequestException: om.out.debug("HTTPRequestException") except Exception as e: om.out.debug( str(e) )
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_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_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_token_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_result(self, mutant, response): """ Analyze results of the _send_mutant method. """ if self._has_bug(mutant): return self._report_php_errors(mutant, response) if not self._header_was_injected(mutant, response): return 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)
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, in order for a Rosetta Flash vulnerability to be present we need to have the FLASH attribute reflected at the beginning of the response body """ if self._has_bug(mutant): return if not response.get_body().startswith(self.FLASH): return desc = 'Rosetta flash vulnerability found in JSONP endpoint at: %s' desc %= mutant.found_at() v = Vuln.from_mutant('Rosetta Flash', desc, severity.LOW, response.id, self.get_name(), mutant) v.add_to_highlight(self.FLASH) self.kb_append_uniq(self, 'rosetta_flash', v)
def end(self): """ This method is called when the plugin wont be used anymore. """ all_findings = kb.kb.get_all_findings() for url, variable, mutant, id_list in self._potential_vulns: for info in all_findings: if info.get_token_name() == 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 end(self): """ This method is called when the plugin wont be used anymore. """ all_findings = kb.kb.get_all_findings() for url, variable, mutant, id_list in self._potential_vulns: for info in all_findings: if info.get_token_name() == variable and info.get_url() == url: break else: desc = ('An unhandled error, which could potentially translate' ' to a vulnerability, was found at: %s') desc %= mutant.found_at() v = Vuln.from_mutant('Unhandled error in web application', desc, severity.LOW, id_list, self.get_name(), mutant) self.kb_append_uniq(self, 'generic', v) self._potential_vulns.cleanup()