def set_options(self, options_list): """ This method sets all the options that are configured using the user interface generated by the framework using the result of get_options(). :param options_list: A dict with the options for the plugin. :return: No value is returned. """ self.username = options_list['username'].get_value() self.password = options_list['password'].get_value() self.username_field = options_list['username_field'].get_value() self.password_field = options_list['password_field'].get_value() self.check_string = options_list['check_string'].get_value() self.auth_url = options_list['auth_url'].get_value() self.check_url = options_list['check_url'].get_value() missing_options = [] for o in options_list: if not o.get_value(): missing_options.append(o.get_name()) if missing_options: msg = ('All plugin configuration parameters are required.' ' The missing parameters are: %s') raise BaseFrameworkException(msg % ', '.join(missing_options))
def _id_failed_login_page(self, freq, user_field, passwd_field): """ 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. """ # The result is going to be stored here login_failed_result_list = [] data_container = freq.get_dc() data_container = self._true_extra_fields(data_container, user_field, passwd_field) # The first tuple is an invalid username and a password # The second tuple is an invalid username with a blank password tests = [(rand_alnum(8), rand_alnum(8)), (rand_alnum(8), '')] for user, passwd in tests: # Setup the data_container # Remember that we can have password only forms! if user_field is not None: data_container[user_field][0] = user data_container[passwd_field][0] = passwd freq.set_dc(data_container) response = self._uri_opener.send_mutant(freq, grep=False) body = response.get_body() body = body.replace(user, '') body = body.replace(passwd, '') # Save it 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 = [(rand_alnum(8), rand_alnum(8)), (rand_alnum(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 user_field is not None: data_container[user_field][0] = user data_container[passwd_field][0] = passwd freq.set_dc(data_container) response = self._uri_opener.send_mutant(freq, grep=False) body = response.get_body() body = body.replace(user, '') body = body.replace(passwd, '') if not self._matches_failed_login(body, login_failed_result_list): raise BaseFrameworkException('Failed to generate a response' 'that matches the failed login' ' page.') return login_failed_result_list
def validate(self, value): try: re.compile(value) except Exception, e: msg = 'The regular expression "%s" is invalid, the compilation' \ ' error was: "%s".' raise BaseFrameworkException(msg % (value, e))
def _signature_test(self, mutant, session, login_failed_bodies, debugging_id): """ Perform a signature test before starting the brute-force process. This test makes sure that the signatures captured in _id_failed_login_pages are usable. The basic idea is to send more failed login attempts and all should be identified as failed logins. :param mutant: The mutant that holds the login form :param session: The HTTP session / cookies to use in the test :param login_failed_bodies: The login failed bodies signatures :return: True if success, raises exception on failure """ tests = [(rand_alnum(8), rand_alnum(8)), (rand_alnum(8), '')] form = mutant.get_dc() for user, passwd in tests: self._fill_form(form, user, passwd) response = self._uri_opener.send_mutant(mutant, grep=False, session=session, debugging_id=debugging_id) body = self._clean_body(response, user, passwd) if self._matches_any_failed_page(body, login_failed_bodies): continue msg = 'Failed to generate a response that matches the failed login page' raise BaseFrameworkException(msg) return True
def testServer(ssl, server, port, matchCount, generateFP, threads): global VERBOSE global PORT global useSSL VERBOSE = 0 PORT = port useSSL = ssl MATCH_COUNT = matchCount fingerprintDir = os.path.join(ROOT_PATH, 'plugins', 'infrastructure', 'oHmap', 'known.servers/') # Get the fingerprint target_url = server fp = get_fingerprint(target_url, threads) # Read the fingerprint db known_servers = [] for f in glob.glob(fingerprintDir + '*'): ksf = file(f) try: ### FIXME: This eval is awful, I should change it to pickle. ks = eval(ksf.read()) except Exception, e: raise BaseFrameworkException('The signature file "' + f + '" has an invalid syntax.') else: known_servers.append(ks) ksf.close()
def set_what_not_to_trap(self, regex): """Set regular expression that indicates what URLs TO trap.""" try: self.what_not_to_trap = re.compile(regex) except re.error: error = 'The regular expression you configured is invalid.' raise BaseFrameworkException(error)
def _get_x_settings(self, section, configurable_instance): """ :return: An OptionList with the options for a configurable object. """ configurable_options = configurable_instance.get_options() try: profile_options = self._config.options(section) except ConfigParser.NoSectionError: # Some profiles don't have an http-settings or misc-settings # section, so we return the defaults as returned by the configurable # instance return configurable_options for option in profile_options: try: value = self._config.get(section, option) except KeyError: # We should never get here... msg = 'The option "%s" is unknown for the "%s" section.' raise BaseFrameworkException(msg % (option, section)) else: configurable_options[option].set_value(value) return configurable_options
def set_options(self, options_list): """ This method sets all the options that are configured using the user interface generated by the framework using the result of get_options(). :param options_list: A dict with the options for the plugin. :return: No value is returned. """ self.username = options_list['username'].get_value() self.password = options_list['password'].get_value() self.username_field = options_list['username_field'].get_value() self.password_field = options_list['password_field'].get_value() self.data_format = options_list['data_format'].get_value() self.check_string = options_list['check_string'].get_value() self.method = options_list['method'].get_value() self.auth_url = options_list['auth_url'].get_value() self.check_url = options_list['check_url'].get_value() self.follow_redirects = options_list['follow_redirects'].get_value() self.url_encode_params = options_list['url_encode_params'].get_value() for o in options_list: if o.get_value() == '': msg = "All parameters are required and can't be empty." raise BaseFrameworkException(msg)
def _do_google_search(self): start = self._start res_pages = [] max_start = start + self._count param_dict = {'q': self._query, 'start': 0} there_is_more = True while start < max_start and there_is_more: param_dict['start'] = start params = urllib.urlencode(param_dict) gm_url = self.GOOGLE_SEARCH_URL + params gm_url_instance = URL(gm_url) response = self._do_GET(gm_url_instance, with_rand_ua=False) for google_sorry_page in GOOGLE_SORRY_PAGES: if google_sorry_page in response: msg = 'Google is telling us to stop doing automated tests.' raise BaseFrameworkException(msg) if not self._has_more_items(response.get_body()): there_is_more = False res_pages.append(response) start += 10 return res_pages
def get_plugin_options(self, plugin_type, plugin_name): """ :return: A dict with the options for a plugin. For example: { 'LICENSE_KEY':'AAAA' } """ # Get the plugin defaults with their types plugin = 'w3af.plugins.%s.%s' % (plugin_type, plugin_name) plugin_instance = factory(plugin) options_list = plugin_instance.get_options() for section in self._config.sections(): # Section is something like audit.xss or crawl.web_spider try: _type, name = section.split('.') except: pass else: if _type == plugin_type and name == plugin_name: for option in self._config.options(section): try: value = self._config.get(section, option) except KeyError: # We should never get here... msg = ('The option "%s" is unknown for the' ' "%s" plugin.') args = (option, plugin_name) raise BaseFrameworkException(msg % args) else: options_list[option].set_value(value) return options_list
def _do_google_search(self): res_pages = [] start = self._start max_start = start + self._count there_is_more = True while start < max_start and there_is_more: params = urllib.urlencode({ 'hl': 'en', 'q': self._query, 'start': start, 'sa': 'N' }) google_url_instance = URL(self.GOOGLE_SEARCH_URL + params) response = self._do_GET(google_url_instance, with_rand_ua=False) # Remember that HTTPResponse objects have a faster "__in__" than # the one in strings; so string in response.get_body() is slower # than string in response for google_sorry_page in GOOGLE_SORRY_PAGES: if google_sorry_page in response: msg = 'Google is telling us to stop doing automated tests.' raise BaseFrameworkException(msg) if not self._has_more_items(response.get_body()): there_is_more = False # Save the result page res_pages.append(response) start += 10 return res_pages
def _gen_url_to_include(self, file_content, extension): """ Generate the URL to include, based on the configuration it will return a URL pointing to a XSS bug, or our local webserver. """ if self._use_XSS_vuln and self._xss_vuln: mutant = self._xss_vuln.get_mutant() mutant = mutant.copy() mutant.set_token_value(file_content) return mutant.get_uri().url_string else: # Write the php to the webroot filename = rand_alnum() filepath = os.path.join(get_home_dir(), 'webroot', filename) try: file_handler = open(filepath, 'w') file_handler.write(file_content) file_handler.close() except: raise BaseFrameworkException( 'Could not create file in webroot.') else: url_to_include = 'http://%s:%s/%s' % ( self._listen_address, self._listen_port, filename) return url_to_include
def copy(self, copy_profile_name): """ Create a copy of the profile file into copy_profile_name. The directory of the profile is kept unless specified. """ new_profile_path_name = copy_profile_name # Check path if os.path.sep not in copy_profile_name: dir = os.path.dirname(self.profile_file_name) new_profile_path_name = os.path.join(dir, copy_profile_name) # Check extension if not new_profile_path_name.endswith(self.EXTENSION): new_profile_path_name += self.EXTENSION try: shutil.copyfile(self.profile_file_name, new_profile_path_name) except Exception as e: msg = 'An exception occurred while copying the profile. Exception:' msg += ' "%s".' % e raise BaseFrameworkException(msg % e) else: # Now I have to change the data inside the copied profile, to # reflect the changes. new_profile = profile(new_profile_path_name) new_profile.set_name(copy_profile_name) new_profile.save(new_profile_path_name) return True
def _exec_payload(self, remote_filename): """ This method should be implemented according to the remote operating system. The idea here is to execute the payload that was sent using _send_exe_to_server and generated by _generate_exe . In lnxVd I should run "chmod +x file; ./file" :return: None """ cH = crontabHandler(self._exec_method) if not cH.can_delay(): msg = '[lnxVd] Failed to create cron entry.' om.out.debug(msg) raise BaseFrameworkException(msg) else: wait_time = cH.add_to_schedule(remote_filename) om.out.console( 'Crontab entry successfully added. Waiting for shellcode execution.' ) time.sleep(wait_time + 3) om.out.debug( 'Shellcode successfully executed, restoring old crontab.') cH.restore_old_schedule() om.out.debug('All done, check metasploit for results.')
def crawl(self, fuzzable_request): """ Get the sitemap.xml file and parse it. :param fuzzable_request: A fuzzable_request instance that contains (among other things) the URL to test. """ base_url = fuzzable_request.get_url().base_url() sitemap_url = base_url.url_join('sitemap.xml') response = self._uri_opener.GET(sitemap_url, cache=True) if '</urlset>' in response and not is_404(response): # Send response to core fr = FuzzableRequest.from_http_response(response) self.output_queue.put(fr) om.out.debug('Parsing xml file with xml.dom.minidom.') try: dom = xml.dom.minidom.parseString(response.get_body()) except: raise BaseFrameworkException('Error while parsing sitemap.xml') else: raw_url_list = dom.getElementsByTagName("loc") parsed_url_list = [] for url in raw_url_list: try: url = url.childNodes[0].data url = URL(url) except ValueError, ve: msg = 'Sitemap file had an invalid URL: "%s"' om.out.debug(msg % ve) except: om.out.debug('Sitemap file had an invalid format')
def _init(self): self._file_name = os.path.expanduser(self._file_name) try: self._file = open(self._file_name, "w") except IOError, io: msg = 'Can\'t open report file "%s" for writing, error: %s.' raise BaseFrameworkException( msg % (os.path.abspath(self._file_name), io.strerror))
def open_file(self): self._file_name = os.path.expanduser(self._file_name) try: self._file = open(self._file_name, 'w') except IOError, io: msg = 'Can\'t open report file "%s" for writing, error: %s.' args = (os.path.abspath(self._file_name), io.strerror) raise BaseFrameworkException(msg % args)
def set_wsdl(self, xmlData): """ :param xmlData: The WSDL to parse. At this point, we really don't know if it really is a WSDL document. """ if not self.is_WSDL(xmlData): raise BaseFrameworkException('The body content is not a WSDL.') else: try: self._proxy = SOAPpy.WSDL.Proxy(xmlData) except expat.ExpatError: raise BaseFrameworkException('The body content is not a WSDL.') except Exception as e: msg = 'The body content is not a WSDL.' msg += ' Unhandled exception in SOAPpy: "' + str(e) + '".' om.out.debug(msg) raise BaseFrameworkException(msg)
def _id_failed_login_page(self, mutant): """ 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. """ # The result is going to be stored here login_failed_result_list = [] form = mutant.get_dc() self._true_extra_fields(form) user_token, pass_token = form.get_login_tokens() # The first tuple is an invalid username and a password # The second tuple is an invalid username with a blank password tests = [(rand_alnum(8), rand_alnum(8)), (rand_alnum(8), '')] for user, passwd in tests: # Setup the data_container # Remember that we can have password only forms! if user_token is not None: form.set_login_username(user) form.set_login_password(passwd) response = self._uri_opener.send_mutant(mutant, grep=False) # Save it body = self.clean_body(response, user, passwd) 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 = [(rand_alnum(8), rand_alnum(8)), (rand_alnum(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 user_token is not None: form.set_login_username(user) form.set_login_password(passwd) response = self._uri_opener.send_mutant(mutant, grep=False) body = self.clean_body(response, user, passwd) if not self._matches_failed_login(body, login_failed_result_list): raise BaseFrameworkException('Failed to generate a response' 'that matches the failed login' ' page.') return login_failed_result_list
def factory(module_name, *args): """ This function creates an instance of a class that's inside a module with the same name. Example : >>> spider = factory( 'w3af.plugins.crawl.google_spider' ) >>> spider.get_name() 'google_spider' :param module_name: Which plugin do you need? :return: An instance. """ module_path = module_name.replace('.', '/') module_path = module_path.replace('w3af/', '') module_path = '%s.py' % module_path module_path = os.path.join(ROOT_PATH, module_path) if not os.path.exists(module_path): msg = 'The %s plugin does not exist.' raise BaseFrameworkException(msg % module_name) try: __import__(module_name) except SyntaxError: # Useful for development raise except ImportError: # Useful for development and users which failed to install all # dependencies # # https://github.com/andresriancho/w3af/issues/9688 msg = ( 'It seems that your Python installation does not have all the' ' modules required by the w3af framework. For more information' ' about how to install and debug dependency issues please browse' ' to http://docs.w3af.org/en/latest/install.html') print(msg) # Raise so the user sees the whole traceback raise except Exception, e: msg = 'There was an error while importing %s: "%s".' raise BaseFrameworkException(msg % (module_name, e))
def set_options(self, option_list): """ Sets the Options given on the OptionList to self. The options are the result of a user entering some data on a window that was constructed using the XML Options that was retrieved from the plugin using get_options() This method MUST be implemented on every plugin. :return: No value is returned. """ self._user_option_fix_content_len = option_list[ 'fix_content_len'].get_value() self._expressions = ','.join(option_list['expressions'].get_value()) found_expressions = re.findall('([qs])([bh])/(.*?)/(.*?)/;?', self._expressions) if len(found_expressions) == 0 and len( option_list['expressions'].get_value()) != 0: msg = 'The user specified expression is invalid.' raise BaseFrameworkException(msg) for exp in found_expressions: req_res, body_header, regex_str, target_str = exp if req_res not in ('q', 's'): msg = 'The first letter of the sed expression should be "q"'\ ' for indicating request or "s" for response, got "%s"'\ ' instead.' raise BaseFrameworkException(msg % req_res) if body_header not in ('b', 'h'): msg = 'The second letter of the expression should be "b"'\ ' for body or "h" for header, got "%s" instead.' raise BaseFrameworkException(msg % body_header) try: regex = re.compile(regex_str) except re.error, re_err: msg = 'Regular expression compilation error at "%s", the'\ ' original exception was "%s".' raise BaseFrameworkException(msg % (regex_str, re_err)) self._manglers[req_res][body_header].add((regex, target_str))
def __init__(self, w3af, response_list, distance_function=LEVENSHTEIN, custom_code=None): """ :param response_list: A list with the responses to graph. """ self.w3af = w3af w3afDotWindow.__init__(self) self.widget.connect('clicked', self.on_url_clicked) # Now I generate the dotcode based on the data if distance_function == LEVENSHTEIN: dotcode = self._generateDotCode( response_list, distance_function=self._relative_distance) elif distance_function == HTTP_RESPONSE: dotcode = self._generateDotCode( response_list, distance_function=self._http_code_distance) elif distance_function == CONTENT_LENGTH: dotcode = self._generateDotCode(response_list, distance_function= self._response_length_distance) elif distance_function == CUSTOM_FUNCTION: try: callable_object = self._create_callable_object(custom_code) except Exception, e: # TODO: instead of hiding..., which may consume memory... # why don't killing? self.hide() msg = 'Please review your customized code. An error was raised'\ ' while compiling: "%s".' % e raise BaseFrameworkException(msg) try: dotcode = self._generateDotCode(response_list, distance_function=callable_object) except Exception, e: # TODO: instead of hiding..., which may consume memory... # why don't killing? self.hide() msg = 'Please review your customized code. An error was raised'\ ' on run time: "%s"' raise BaseFrameworkException(msg % e)
def _do_google_search(self): res_pages = [] start = self._start max_start = min(start + self._count, self.GOOGLE_AJAX_MAX_START_INDEX + self.GOOGLE_AJAX_MAX_RES_PER_PAGE) while start < max_start: size = min(max_start - start, self.GOOGLE_AJAX_MAX_RES_PER_PAGE) # Build param dict; then encode it params_dict = {'v': '1.0', 'q': self._query, 'rsz': size, 'start': start} params = urllib.urlencode(params_dict) google_url_instance = URL(self.GOOGLE_AJAX_SEARCH_URL + params) # Do the request try: resp = self._do_GET(google_url_instance) except Exception, e: msg = 'Failed to GET google.com AJAX API: "%s"' raise BaseFrameworkException(msg % e) try: # Parse the response. Convert the json string into a py dict. parsed_resp = json.loads(resp.get_body()) except ValueError: # ValueError: No JSON object could be decoded msg = 'Invalid JSON returned by Google, got "%s"' raise BaseFrameworkException(msg % resp.get_body()) # Expected response code is 200; otherwise raise Exception if parsed_resp.get('responseStatus') != 200: msg = ('Invalid JSON format returned by Google, response status' ' needs to be 200, got "%s" instead.') msg %= parsed_resp.get('responseDetails') raise BaseFrameworkException(msg) # Update result pages res_pages.append(resp) # Update 'start' and continue loop start += self.GOOGLE_AJAX_MAX_RES_PER_PAGE
def set_options(self, options_list): origin_header_value = options_list['origin_header_value'] self.origin_header_value = origin_header_value.get_value() # Check set options if self.origin_header_value is None or \ len(self.origin_header_value.strip()) == 0: msg = 'Please enter a valid value for the "Origin" HTTP header.' raise BaseFrameworkException(msg)
def get_real_profile_path(self, profile_name, workdir): """ Return the complete path for `profile_name`. @raise BaseFrameworkException: If no existing profile file is found this exception is raised with the proper desc message. >>> p = profile() >>> p.get_real_profile_path('OWASP_TOP10', '.') './profiles/OWASP_TOP10.pw3af' """ # Add extension if necessary if not profile_name.endswith(self.EXTENSION): profile_name += self.EXTENSION if os.path.exists(profile_name): return profile_name # Let's try to find the profile in different paths, using the # profile_name as a filename for profile_path in self.get_profile_paths(workdir): _path = os.path.join(profile_path, profile_name) if os.path.exists(_path): return _path # This is the worse case scenario, where the file name is different from # the "name = ..." value which is inside the file # # https://github.com/andresriancho/w3af/issues/561 for profile_path in self.get_profile_paths(workdir): for profile_file in os.listdir(profile_path): if not profile_file.endswith(self.EXTENSION): continue profile_path_file = os.path.join(profile_path, profile_file) with codecs.open(profile_path_file, "rb", UTF8) as fp: config = ConfigParser.ConfigParser() try: config.readfp(fp) except: # Any errors simply break name detection continue try: name = config.get(self.PROFILE_SECTION, 'name') except: # Any errors simply break name detection continue else: if '%s%s' % (name, self.EXTENSION) == profile_name: return profile_path_file msg = 'The profile "%s" wasn\'t found.' raise BaseFrameworkException(msg % profile_name)
def _cmd_config(self, params): if len(params) == 0: raise BaseFrameworkException("Plugin name is required") name = params[0] if name not in self._plugins: raise BaseFrameworkException("Unknown plugin: '%s'" % name) if name in self._configs: config = self._configs[name] else: config = ConfigMenu(name, self._console, self._w3af, self, self._w3af.plugins.get_plugin_inst(self._name, params[0])) self._configs[name] = config return config
def validate(self, value): if value is None: return None if not is_ip_address(value): msg = 'Invalid IP address specified ("%s")' % value raise BaseFrameworkException(msg) return value
def validate(self, value): if isinstance(value, Headers): return value try: return Headers.from_string(value) except Exception: msg = 'Invalid HTTP header configured by user.' raise BaseFrameworkException(msg)
def validate(self, value): if isinstance(value, QueryString): return value try: return parse_qs(value) except Exception: msg = 'Invalid query string configured by user.' raise BaseFrameworkException(msg)
def get_ns(self, method): """ @method: The method name :return: The namespace of the WSDL """ if method in self._proxy.methods.keys(): return str(self._proxy.methods[method].namespace) else: raise BaseFrameworkException('Unknown method name.')