def __parse_conf_file(self): parser = DefaultConfigParser() # Utf-8 to avoid encoding issues parser.read(EXPLOITS_CONF, 'utf8') for section in parser.sections(): # Vulnerable product name product = parser.safe_get(section, 'product', '', None) if not product: raise SettingsException('No vulnerable product name specified for ' \ '[{}]'.format(section)) # Vulnerability description description = parser.safe_get(section, 'description', '', None) if not description: raise SettingsException('Missing vulnerability description for ' \ '[{}]'.format(section)) # Vulnerability type type_ = parser.safe_get(section, 'type', '', None) if type_ not in SUPPORTED_TYPES: raise SettingsException( 'Unsupported vulnerability type for [{}]'.format(section)) # Detection command detection_rawcmd = parser.safe_get(section, 'detection_cmd', '', None) # Detection command output success detection_success = parser.safe_get(section, 'detection_success', '', None) if detection_rawcmd and len( detection_rawcmd) > 0 and not detection_success: raise SettingsException('Missing "detection_success" for [{}] since ' \ '"detection_cmd" is defined'.format(section)) # Exploit command exploit_rawcmd = parser.safe_get(section, 'exploit_cmd', '', None) # Exploit RCE output exploit_rce_output = parser.safe_get_boolean( section, 'exploit_rce_output', True) # Exploit command output success (for auto test when exploit_rce_output == True) exploit_success = parser.safe_get(section, 'exploit_success', '', None) if exploit_rawcmd and \ len(exploit_rawcmd) > 0 and \ exploit_rce_output and \ not exploit_success: raise SettingsException( 'Missing "exploit_success" for [{}] since ' '"exploit_cmd" is defined and "exploit_rce_output=true"'. format(section)) exploit = Exploit(section, product, description, type_, detection_rawcmd, detection_success, exploit_rawcmd, exploit_rce_output, exploit_success) self.exploits.append(exploit)
def __parse_section_supported_list_options(self, service, service_config): """ Parse section [supported_list_options] in <service_name>.conf and update service configuration with supported values for specific options of type list. Must be called after self.__parse_section_config() and self.__parse_section_specific_options(). :param defaultdict(str) service_config: Information about the service, updated into this method :return: None :raises SettingsException: Exception raised if any unrecoverable error is encountered while parsing the section """ # Get names of specific options of type list options_list = list( filter( lambda x: service_config['specific_options'][x] == OptionType. LIST, service_config['specific_options'].keys())) if not options_list: return elif not self.config_parsers[service].has_section( 'supported_list_options'): raise SettingsException('[{filename}{ext}] Missing section ' \ '[supported_list_options] to store supported values for specific ' \ 'options of type "list"'.format(filename=service, ext=CONF_EXT)) log_prefix = '[{filename}{ext} | Section "supported_list_options"]'.format( filename=service, ext=CONF_EXT) supported_list_options = dict() optparsed = self.config_parsers[service].options( 'supported_list_options') # Loop over specific options of type list for opt in options_list: # If missing option if 'supported_' + opt not in optparsed: raise SettingsException('{prefix} No option "supported_{option}" ' \ 'is defined'.format(prefix=log_prefix, option=opt)) # Values are put in lowercase, no spaces, no special chars (except -, _) values = list( map( lambda x: StringUtils.clean(x.lower(), allowed_specials=('-', '_')), self.config_parsers[service].safe_get_list( 'supported_list_options', 'supported_' + opt, ',', []))) if not values: raise SettingsException('{prefix} Option "supported_{option}" is ' \ 'empty'.format(prefix=log_prefix, option=opt)) supported_list_options[opt] = values # Update service configuration with lists of supported values service_config['supported_list_options'] = supported_list_options
def __init__(self): """ :raises SettingsException: """ self.config_parsers = dict() # dict of DefaultConfigParser indexed by filename self.toolbox = None # Receives Toolbox object self.services = None # Receives ServicesConfig object # Check directory and presence of *.conf files if not FileUtils.is_dir(SETTINGS_DIR): raise SettingsException('Configuration directory ({dir}) does not exist'.format(dir=SETTINGS_DIR)) files = FileUtils.list_directory(SETTINGS_DIR) for f in files: if not FileUtils.check_extension(f, CONF_EXT): files.remove(f) if not files: raise SettingsException('Configuration directory ({dir}) does not store any *.conf file'.format( dir=SETTINGS_DIR)) if TOOLBOX_CONF_FILE+CONF_EXT not in files: raise SettingsException('Missing mandatory {toolbox}{ext} settings file in directory "{dir}"'.format( toolbox=TOOLBOX_CONF_FILE, ext=CONF_EXT, dir=SETTINGS_DIR)) if INSTALL_STATUS_CONF_FILE+CONF_EXT not in files: open(SETTINGS_DIR+'/'+INSTALL_STATUS_CONF_FILE+CONF_EXT, 'a').close() logger.info('{status}{ext} settings file created in directory "{dir}"'.format( status=INSTALL_STATUS_CONF_FILE, ext=CONF_EXT, dir=SETTINGS_DIR)) files.append(INSTALL_STATUS_CONF_FILE+CONF_EXT) # Parse settings files, add tools inside toolbox and create scan configs self.__parse_all_conf_files(files) self.__create_toolbox() self.__create_all_services_checks()
def __parse_section_config(self, service, service_config): """ Parse section [config] in <service_name>.conf, retrieve basic info about service (default port/protocol) and retrieve list of supported categories of checks for this service. :param str service: Service name :param defaultdict(str) service_config: Information about the service, updated into this method :return: List of categories of checks :rtype: list(str) :raises SettingsException: Exception raised if any unrecoverable error is encountered while parsing the section """ log_prefix = '[{filename}{ext} | Section "config"]'.format( filename=service, ext=CONF_EXT) # Check presence of mandatory options in [config] optparsed = self.config_parsers[service].options('config') for opt in SERVICE_CHECKS_CONFIG_OPTIONS[MANDATORY]: if opt not in optparsed: raise SettingsException('{prefix} Missing mandatory option "{option}"' \ ', check the file'.format(prefix=log_prefix, option=opt)) # Get port number default_port = self.config_parsers[service].safe_get_int( 'config', 'default_port', None, None) if default_port is None or default_port < 0 or default_port > 65535: raise SettingsException('{prefix} Invalid value for option "default_port",' \ ' must be in the range [0-65535]'.format(prefix=log_prefix)) # Get protocol protocol = self.config_parsers[service].safe_get_lower( 'config', 'protocol', 'tcp', ['tcp', 'udp']) # Get categories of checks as a list, clean each element categories = list(map(lambda x: StringUtils.clean( x.lower(), allowed_specials=('-', '_')), self.config_parsers[service].safe_get_list('config', 'categories', ',', []))) if not categories: raise SettingsException('{prefix} Option "categories" must have at least '\ 'one category'.format(prefix=log_prefix)) # Get authentication type (for HTTP) as a list, clean each element if 'auth_types' in optparsed: auth_types = list(map(lambda x: StringUtils.clean( x.lower(), allowed_specials=('-', '_')), self.config_parsers[service].safe_get_list( 'config', 'auth_types', ',', []))) else: auth_types = None # Update service configuration with parsed information service_config['default_port'] = default_port service_config['protocol'] = protocol service_config['auth_types'] = auth_types return categories
def __init__(self): """ Start the parsing of settings files and create the Settings object. :raises SettingsException: Exception raised if any error is encountered while parsing files (syntax error, missing mandatory file...) """ self.config_parsers = dict( ) # Dict of DefaultConfigParser indexed by filename self.toolbox = None # Receives Toolbox object self.services = None # Receives ServicesConfig object self.attack_profiles = None # Receives AttackProfiles object # Check directory if not FileUtils.is_dir(SETTINGS_DIR): raise SettingsException('Configuration directory ({dir}) does not ' \ 'exist'.format(dir=SETTINGS_DIR)) # Check presence of *.conf files files = FileUtils.list_directory(SETTINGS_DIR) for f in files: if not FileUtils.check_extension(f, CONF_EXT): files.remove(f) if not files: raise SettingsException('Configuration directory ({dir}) does not ' \ 'store any *.conf file'.format(dir=SETTINGS_DIR)) if TOOLBOX_CONF_FILE + CONF_EXT not in files: raise SettingsException('Missing mandatory {toolbox}{ext} settings ' \ 'file in directory "{dir}"'.format( toolbox=TOOLBOX_CONF_FILE, ext=CONF_EXT, dir=SETTINGS_DIR)) if ATTACK_PROFILES_CONF_FILE + CONF_EXT not in files: raise SettingsException('Missing mandatory {profiles}{ext} settings ' \ 'file in directory "{dir}"'.format( profiles=ATTACK_PROFILES_CONF_FILE, ext=CONF_EXT, dir=SETTINGS_DIR)) # Create _install_status.conf file if necessary if INSTALL_STATUS_CONF_FILE + CONF_EXT not in files: open(SETTINGS_DIR + '/' + INSTALL_STATUS_CONF_FILE + CONF_EXT, 'a').close() logger.info('{status}{ext} settings file created in directory ' \ '"{dir}"'.format(status=INSTALL_STATUS_CONF_FILE, ext=CONF_EXT, dir=SETTINGS_DIR)) files.append(INSTALL_STATUS_CONF_FILE + CONF_EXT) # Parse configuration files and create objects from them self.__parse_all_conf_files(files) self.__create_toolbox() self.__create_all_services_config_and_checks() self.__create_attack_profiles()
def change_installed_status(self, target_service, tool_name, install_status): """ Change the install status for a given tool. Change is made into the INSTALL_STATUS_CONF_FILE If tool installed, put the current datetime. :param str target_service: Name of service targeted by the tool :param str tool_name: Name of the tool :param bool install_status: New install status to set :return: Status of change :rtype: bool """ if install_status: value = datetime.now().strftime('%Y-%m-%d %H:%M:%S') else: value = 'False' parser = self.config_parsers[INSTALL_STATUS_CONF_FILE] # Create the section [service] if needed if target_service not in parser.sections(): parser.add_section(target_service) # Add/Update the install status if not parser.safe_set(target_service, tool_name, value): raise SettingsException('Unable to change install status value for the ' \ 'tool {tool}'.format(tool=tool_name)) # Save change permanently into the file return self.save(INSTALL_STATUS_CONF_FILE)
def __parse_section_specific_options(self, service, service_config): """ Parse section [specific_options] in <service_name>.conf and update service config :param service: Service name :param service_config: Dict storing info about service, updated into this method :return: None :raises SettingsException: """ try: optparsed = self.config_parsers[service].options('specific_options') except configparser.NoSectionError: service_config['specific_options'] = dict() return specific_options = dict() for opt in optparsed: option_type = self.config_parsers[service].safe_get_lower('specific_options', opt, None, None) if option_type.count(':') == 1: option_type, default_value = option_type.split(':') opt_clean = StringUtils.clean(opt.lower(), allowed_specials=('-', '_')) if option_type == 'boolean' : specific_options[opt_clean] = OptionType.BOOLEAN elif option_type == 'list' : specific_options[opt_clean] = OptionType.LIST elif option_type == 'var' : specific_options[opt_clean] = OptionType.VAR else: raise SettingsException('[{filename}{ext} | Section "specific_options"] Specific option named "{option}" has ' \ 'an invalid type. Supported types are: boolean, list, var'.format( filename = service, ext=CONF_EXT, option=opt)) service_config['specific_options'] = specific_options
def __parse_conf_file(self): parser = DefaultConfigParser() # Utf-8 to avoid encoding issues parser.read(EXPLOITS_CONF, 'utf8') for section in parser.sections(): type_ = parser.safe_get(section, 'type', '', None) if type_ not in SUPPORTED_TYPES: raise SettingsException('Unsupported exploit type for [{}]'.format(type_)) rawcmd = parser.safe_get(section, 'command', '', None) if not rawcmd: raise SettingsException('No command specified for [{}]'.format(rawcmd)) description = parser.safe_get(section, 'description', '', None) success = parser.safe_get(section, 'success', '', None) exploit = Exploit(section, description, type_, rawcmd, success) self.exploits.append(exploit)
def __parse_section_specific_options(self, service, service_config): """ Parse section [specific_options] in <service_name>.conf and update service configuration with supported specific options for the service and their respective types. :param str service: Service name :param defaultdict(str) service_config: Information about the service, updated into this method :return: None :raises SettingsException: Exception raised if any unrecoverable error is encountered while parsing the section """ # Case when no [specific_options] can be found try: optparsed = self.config_parsers[service].options( 'specific_options') except configparser.NoSectionError: service_config['specific_options'] = dict() return specific_options = dict() # Loop over supported specific options for opt in optparsed: # Get option type option_type = self.config_parsers[service].safe_get_lower( 'specific_options', opt, None, None) # Handle case when default value is specified (for boolean) if option_type.count(':') == 1: option_type, default_value = option_type.split(':') opt_clean = StringUtils.clean(opt.lower(), allowed_specials=('-', '_')) if option_type == 'boolean': specific_options[opt_clean] = OptionType.BOOLEAN elif option_type == 'list': specific_options[opt_clean] = OptionType.LIST elif option_type == 'var': specific_options[opt_clean] = OptionType.VAR else: raise SettingsException('[{filename}{ext} | Section ' \ '"specific_options"] Specific option named "{option}" has ' \ 'an invalid type. Supported types are: boolean, list, var'.format( filename = service, ext=CONF_EXT, option=opt)) # Update service configuration with specific options names and types service_config['specific_options'] = specific_options
def __parse_section_products(self, service, service_config): """ Parse section [products] in <service_name>.conf and retrieve supported values for each product type. :param str service: Service name :param dict service_config: Service configuration, updated into this method :return: None :raises SettingsException: Exception raised if unconsistent values detected """ # First, check if config file has a [products] section if not self.config_parsers[service].has_section('products'): service_config['products'] = dict() return log_prefix = '[{filename}{ext} | Section "products"]'.format( filename=service, ext=CONF_EXT) products = dict() optparsed = self.config_parsers[service].options('products') # Loop over product types in [products] for product_type in optparsed: # Clean the product type product_type = StringUtils.clean(product_type.lower(), allowed_specials=('-', '_')) # Get supported product names as a list. # Only some special chars allowed, spaces allowed # '/' is used to separate vendor name (optional) and product name product_names = self.config_parsers[service].safe_get_list( 'products', product_type, ',', []) product_names = list( map( lambda x: StringUtils.clean( x, allowed_specials=('-', '_', '.', '/', '\\', ' ')), product_names)) if not product_names: raise SettingsException( '{prefix} Option "{option}" is empty'.format( prefix=log_prefix, option=opt)) products[product_type] = product_names # Update service configuration with supported products service_config['products'] = products return
def __parse_section_config(self, service, service_config): """ Parse section [config] in <service_name>.conf, retrieve basic info about service (default port/protocol) and retrieve list of categories. :param service: Service name :param service_config: Dict storing info about service, updated into this method :return: List of categories of checks :raises SettingsException: """ log_prefix = '[{filename}{ext} | Section "config"]'.format(filename=service, ext=CONF_EXT) optparsed = self.config_parsers[service].options('config') for opt in SERVICE_CHECKS_CONFIG_OPTIONS[MANDATORY]: if opt not in optparsed: raise SettingsException('{prefix} Missing mandatory option "{option}", check the file'.format( prefix=log_prefix, option=opt)) default_port = self.config_parsers[service].safe_get_int('config', 'default_port', None, None) protocol = self.config_parsers[service].safe_get_lower('config', 'protocol', 'tcp', ['tcp', 'udp']) categories = list(map(lambda x: StringUtils.clean(x.lower(), allowed_specials=('-', '_')), self.config_parsers[service].safe_get_list('config', 'categories', ',', []))) auth_types = list(map(lambda x: StringUtils.clean(x.lower(), allowed_specials=('-', '_')), self.config_parsers[service].safe_get_list('config', 'auth_types', ',', []))) \ if 'auth_types' in optparsed else None if default_port is None or default_port < 0 or default_port > 65535: raise SettingsException('{prefix} Invalid value for option "default_port", must be in the range ' \ '[0-65535]'.format(prefix=log_prefix)) if not categories: raise SettingsException('{prefix} Option "categories" must have at least one category'.format( prefix=log_prefix)) service_config['default_port'] = default_port service_config['protocol'] = protocol service_config['auth_types'] = auth_types return categories
def __init__(self): """ Start the parsing of settings files and create the Settings object. :raises SettingsException: Exception raised if any error is encountered while parsing files """ self.exploits = list() # Check presence of exploits.conf files if not os.access(EXPLOITS_CONF, os.F_OK): raise SettingsException('Missing configuration file exploits.conf') # Parse configuration file self.__parse_conf_file()
def __parse_section_supported_list_options(self, service, service_config): """ Parse section [supported_list_options] in <service_name>.conf and retrieve supported values for specific options of type list. Must be called after self.__parse_section_config() and self.__parse_section_specific_options() :param service: Service name :param service_config: Dict storing info about service, updated into this method :return: None :raises SettingsException: """ options_list = list(filter(lambda x: service_config['specific_options'][x] == OptionType.LIST, service_config['specific_options'].keys())) if not options_list: return dict() elif not self.config_parsers[service].has_section('supported_list_options'): raise SettingsException('[{filename}{ext}] Missing section [supported_list_options] to store supported ' \ 'values for specific options of type list'.format(filename=service, ext=CONF_EXT)) log_prefix = '[{filename}{ext} | Section "supported_list_options"]'.format(filename=service, ext=CONF_EXT) supported_list_options = dict() optparsed = self.config_parsers[service].options('supported_list_options') for opt in options_list: if 'supported_'+opt not in optparsed: raise SettingsException('{prefix} No option "supported_{option}" is defined'.format( prefix=log_prefix, option=opt)) values = list(map(lambda x: StringUtils.clean(x.lower(), allowed_specials=('-', '_')), self.config_parsers[service].safe_get_list('supported_list_options', 'supported_'+opt, ',', []))) if not values: raise SettingsException('{prefix} Option "supported_{option}" is empty'.format( prefix=log_prefix, option=opt)) supported_list_options[opt] = values service_config['supported_list_options'] = supported_list_options
def change_installed_status(self, target_service, tool_name, install_status): """ Change the install status for a given tool. Change is made into the INSTALL_STATUS_CONF_FILE If tool installed, put the current datetime :param target_service: Name of service targeted by the tool :param tool_name: Tool name (Attention: must be the clean name !) :param install_status: New install status to set :return: Boolean indicating change status """ # value = datetime.now().strftime('%Y-%m-%d %H:%M:%S') if install_status else 'False' parser = self.config_parsers[INSTALL_STATUS_CONF_FILE] # Create the section [service] if needed if target_service not in parser.sections(): parser.add_section(target_service) if not parser.safe_set(target_service, tool_name, value): raise SettingsException('Unable to change install status value for the tool {tool}'.format( tool=tool_name)) return self.save(INSTALL_STATUS_CONF_FILE)