def __init__(self, certfile, keyfile, cafile): """ Initializes the ospd-openvas daemon's internal data. """ super(OSPDopenvas, self).__init__(certfile=certfile, keyfile=keyfile, cafile=cafile) self.server_version = __version__ self.scanner_info['name'] = 'openvassd' self.scanner_info['version'] = '' # achieved during self.check() self.scanner_info['description'] = OSPD_DESC for name, param in OSPD_PARAMS.items(): self.add_scanner_param(name, param) self.main_kbindex = None self.openvas_db = OpenvasDB() self.nvti = NVTICache(self.openvas_db) self.openvas_db.db_init() self.pending_feed = None ctx = self.openvas_db.db_find(self.nvti.NVTICACHE_STR) if not ctx: self.redis_nvticache_init() ctx = self.openvas_db.db_find(self.nvti.NVTICACHE_STR) self.openvas_db.set_redisctx(ctx) self.load_vts()
def __init__(self, *, niceness=None, **kwargs): """ Initializes the ospd-openvas daemon's internal data. """ super().__init__(customvtfilter=OpenVasVtsFilter()) self.server_version = __version__ self._niceness = str(niceness) self.scanner_info['name'] = 'openvas' self.scanner_info['version'] = '' # achieved during self.check() self.scanner_info['description'] = OSPD_DESC for name, param in OSPD_PARAMS.items(): self.add_scanner_param(name, param) self._sudo_available = None self.scan_only_params = dict() self.main_kbindex = None self.openvas_db = OpenvasDB() self.nvti = NVTICache(self.openvas_db) self.pending_feed = None
def __init__(self, *, niceness=None, lock_file_dir='/var/run/ospd', **kwargs): """ Initializes the ospd-openvas daemon's internal data. """ self.main_db = MainDB() self.nvti = NVTICache(self.main_db) super().__init__( customvtfilter=OpenVasVtsFilter(self.nvti), storage=dict, file_storage_dir=lock_file_dir, **kwargs, ) self.server_version = __version__ self._niceness = str(niceness) self.feed_lock = LockFile(Path(lock_file_dir) / 'feed-update.lock') self.daemon_info['name'] = 'OSPd OpenVAS' self.scanner_info['name'] = 'openvas' self.scanner_info['version'] = '' # achieved during self.init() self.scanner_info['description'] = OSPD_DESC for name, param in OSPD_PARAMS.items(): self.set_scanner_param(name, param) self._sudo_available = None self._is_running_as_root = None self.scan_only_params = dict()
def __init__( self, *, niceness=None, lock_file_dir='/var/lib/openvas', mqtt_broker_address="localhost", mqtt_broker_port=1883, disable_notus_hashsum_verification=False, **kwargs, ): """Initializes the ospd-openvas daemon's internal data.""" self.main_db = MainDB() notus_dir = kwargs.get('notus_feed_dir') notus = None if notus_dir: ndir = Path(notus_dir) verifier = hashsum_verificator(ndir, disable_notus_hashsum_verification) notus = Notus(ndir, self.main_db.ctx, verifier) self.nvti = NVTICache( self.main_db, notus, ) super().__init__( customvtfilter=OpenVasVtsFilter(self.nvti), storage=dict, file_storage_dir=lock_file_dir, **kwargs, ) self.server_version = __version__ self._niceness = str(niceness) self.feed_lock = LockFile(Path(lock_file_dir) / 'feed-update.lock') self.daemon_info['name'] = 'OSPd OpenVAS' self.scanner_info['name'] = 'openvas' self.scanner_info['version'] = '' # achieved during self.init() self.scanner_info['description'] = OSPD_DESC for name, param in OSPD_PARAMS.items(): self.set_scanner_param(name, param) self._sudo_available = None self._is_running_as_root = None self.scan_only_params = dict() self._mqtt_broker_address = mqtt_broker_address self._mqtt_broker_port = mqtt_broker_port
def test_parse_metadata_tag(self, MockOpenvasDB): tags = 'tag1=value1' ret = ( NVTICache._parse_metadata_tags( # pylint: disable=protected-access tags, '1.2.3')) self.assertEqual(ret, {'tag1': 'value1'})
def test_parse_metadata_tag_missing_value(self, MockOpenvasDB): logging.Logger.error = Mock() tags = 'tag1' ret = NVTICache._parse_metadata_tags( # pylint: disable=protected-access tags, '1.2.3') self.assertEqual(ret, {}) assert_called(logging.Logger.error)
def nvti(self) -> NVTICache: if self._nvti is None: try: maindb = MainDB() self._nvti = NVTICache(maindb) except SystemExit: raise OspdOpenvasError( "Could not connect to the Redis KB") from None return self._nvti
class OSPDopenvas(OSPDaemon): """ Class for ospd-openvas daemon. """ def __init__(self, *, niceness=None, **kwargs): """ Initializes the ospd-openvas daemon's internal data. """ super().__init__(customvtfilter=OpenVasVtsFilter()) self.server_version = __version__ self._niceness = str(niceness) self.scanner_info['name'] = 'openvas' self.scanner_info['version'] = '' # achieved during self.check() self.scanner_info['description'] = OSPD_DESC for name, param in OSPD_PARAMS.items(): self.add_scanner_param(name, param) self._sudo_available = None self.scan_only_params = dict() self.main_kbindex = None self.openvas_db = OpenvasDB() self.nvti = NVTICache(self.openvas_db) self.pending_feed = None def init(self): self.openvas_db.db_init() ctx = self.openvas_db.db_find(self.nvti.NVTICACHE_STR) if not ctx: self.redis_nvticache_init() ctx = self.openvas_db.db_find(self.nvti.NVTICACHE_STR) self.openvas_db.set_redisctx(ctx) self.load_vts() def parse_param(self): """ Set OSPD_PARAMS with the params taken from the openvas_scanner. """ bool_dict = {'no': 0, 'yes': 1} result = subprocess.check_output(['openvas', '-s'], stderr=subprocess.STDOUT) result = result.decode('ascii') param_list = dict() for conf in result.split('\n'): elem = conf.split('=') if len(elem) == 2: value = str.strip(elem[1]) if str.strip(elem[1]) in bool_dict: value = bool_dict[value] param_list[str.strip(elem[0])] = value for elem in OSPD_PARAMS: if elem in param_list: OSPD_PARAMS[elem]['default'] = param_list[elem] for elem in param_list: if elem not in OSPD_PARAMS: self.scan_only_params[elem] = param_list[elem] def redis_nvticache_init(self): """ Loads NVT's metadata into Redis DB. """ try: logger.debug('Loading NVTs in Redis DB') subprocess.check_call(['openvas', '--update-vt-info']) except subprocess.CalledProcessError as err: logger.error('OpenVAS Scanner failed to load NVTs. %s', err) def feed_is_outdated(self, current_feed): """ Compare the current feed with the one in the disk. Return: False if there is no new feed. True if the feed version in disk is newer than the feed in redis cache. None if there is no feed the disk. """ plugins_folder = self.scan_only_params.get('plugins_folder') if not plugins_folder: raise OspdOpenvasError("Error: Path to plugins folder not found.") feed_info_file = Path(plugins_folder) / 'plugin_feed_info.inc' if not feed_info_file.exists(): self.parse_param() msg = 'Plugins feed file %s not found.' % feed_info_file logger.debug(msg) return None date = 0 with open(str(feed_info_file)) as fcontent: for line in fcontent: if "PLUGIN_SET" in line: date = line.split(' = ')[1] date = date.replace(';', '') date = date.replace('"', '') if int(current_feed) < int(date) or int(date) == 0: return True return False def check_feed(self): """ Check if there is a feed update. Wait until all the running scans finished. Set a flag to anounce there is a pending feed update, which avoid to start a new scan. """ current_feed = self.nvti.get_feed_version() # Check if the feed is already accessible in the disk. if self.feed_is_outdated(current_feed) is None: self.pending_feed = True return # Check if the nvticache in redis is outdated if not current_feed or self.feed_is_outdated(current_feed): self.redis_nvticache_init() ctx = self.openvas_db.db_find(self.nvti.NVTICACHE_STR) self.openvas_db.set_redisctx(ctx) self.pending_feed = True _running_scan = False for scan_id in self.scan_processes: if self.scan_processes[scan_id].is_alive(): _running_scan = True # Check if the NVT dict is outdated if self.pending_feed: _pending_feed = True else: _pending_feed = (self.get_vts_version() != self.nvti.get_feed_version()) if _running_scan and _pending_feed: if not self.pending_feed: self.pending_feed = True logger.debug('There is a running scan. Therefore the feed ' 'update will be performed later.') elif not _running_scan and _pending_feed: self.vts = dict() self.load_vts() def scheduler(self): """This method is called periodically to run tasks.""" self.check_feed() def load_vts(self): """ Load the NVT's metadata into the vts global dictionary. """ logger.debug('Loading vts in memory.') oids = dict(self.nvti.get_oids()) for _filename, vt_id in oids.items(): _vt_params = self.nvti.get_nvt_params(vt_id) _vt_refs = self.nvti.get_nvt_refs(vt_id) _custom = self.nvti.get_nvt_metadata(vt_id) _name = _custom.pop('name') _vt_creation_time = _custom.pop('creation_date') _vt_modification_time = _custom.pop('last_modification') _summary = None _impact = None _affected = None _insight = None _solution = None _solution_t = None _vuldetect = None _qod_t = None _qod_v = None if 'summary' in _custom: _summary = _custom.pop('summary') if 'impact' in _custom: _impact = _custom.pop('impact') if 'affected' in _custom: _affected = _custom.pop('affected') if 'insight' in _custom: _insight = _custom.pop('insight') if 'solution' in _custom: _solution = _custom.pop('solution') if 'solution_type' in _custom: _solution_t = _custom.pop('solution_type') if 'vuldetect' in _custom: _vuldetect = _custom.pop('vuldetect') if 'qod_type' in _custom: _qod_t = _custom.pop('qod_type') elif 'qod' in _custom: _qod_v = _custom.pop('qod') _severity = dict() if 'severity_base_vector' in _custom: _severity_vector = _custom.pop('severity_base_vector') else: _severity_vector = _custom.pop('cvss_base_vector') _severity['severity_base_vector'] = _severity_vector if 'severity_type' in _custom: _severity_type = _custom.pop('severity_type') else: _severity_type = 'cvss_base_v2' _severity['severity_type'] = _severity_type if 'severity_origin' in _custom: _severity['severity_origin'] = _custom.pop('severity_origin') _vt_dependencies = list() if 'dependencies' in _custom: _deps = _custom.pop('dependencies') _deps_list = _deps.split(', ') for dep in _deps_list: _vt_dependencies.append(oids.get('filename:' + dep)) try: self.add_vt( vt_id, name=_name, vt_params=_vt_params, vt_refs=_vt_refs, custom=_custom, vt_creation_time=_vt_creation_time, vt_modification_time=_vt_modification_time, vt_dependencies=_vt_dependencies, summary=_summary, impact=_impact, affected=_affected, insight=_insight, solution=_solution, solution_t=_solution_t, detection=_vuldetect, qod_t=_qod_t, qod_v=_qod_v, severities=_severity, ) except OspdError as e: logger.info("Error while adding vt. %s", e) _feed_version = self.nvti.get_feed_version() self.set_vts_version(vts_version=_feed_version) self.pending_feed = False logger.debug('Finish loading up vts.') @staticmethod def get_custom_vt_as_xml_str(vt_id, custom): """ Return an xml element with custom metadata formatted as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. custom (dict): Dictionary with the custom metadata. Return: string: xml element as string. """ _custom = Element('custom') for key, val in custom.items(): xml_key = SubElement(_custom, key) xml_key.text = val return tostring(_custom).decode('utf-8') @staticmethod def get_severities_vt_as_xml_str(vt_id, severities): """ Return an xml element with severities as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. severities (dict): Dictionary with the severities. Return: string: xml element as string. """ _severities = Element('severities') _severity = SubElement(_severities, 'severity') if 'severity_base_vector' in severities: _severity.text = severities.get('severity_base_vector') if 'severity_origin' in severities: _severity.set('origin', severities.get('severity_origin')) if 'severity_type' in severities: _severity.set('type', severities.get('severity_type')) return tostring(_severities).decode('utf-8') @staticmethod def get_params_vt_as_xml_str(vt_id, vt_params): """ Return an xml element with params formatted as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. vt_params (dict): Dictionary with the VT parameters. Return: string: xml element as string. """ vt_params_xml = Element('params') for _pref_id, prefs in vt_params.items(): vt_param = Element('param') vt_param.set('type', prefs['type']) vt_param.set('id', _pref_id) xml_name = SubElement(vt_param, 'name') xml_name.text = prefs['name'] if prefs['default']: xml_def = SubElement(vt_param, 'default') xml_def.text = prefs['default'] vt_params_xml.append(vt_param) return tostring(vt_params_xml).decode('utf-8') @staticmethod def get_refs_vt_as_xml_str(vt_id, vt_refs): """ Return an xml element with references formatted as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. vt_refs (dict): Dictionary with the VT references. Return: string: xml element as string. """ vt_refs_xml = Element('refs') for ref_type, ref_values in vt_refs.items(): for value in ref_values: vt_ref = Element('ref') if ref_type == "xref" and value: for xref in value.split(', '): try: _type, _id = xref.split(':', 1) except ValueError: logger.error( 'Not possible to parse xref %s for vt %s', xref, vt_id, ) continue vt_ref.set('type', _type.lower()) vt_ref.set('id', _id) elif value: vt_ref.set('type', ref_type.lower()) vt_ref.set('id', value) else: continue vt_refs_xml.append(vt_ref) return tostring(vt_refs_xml).decode('utf-8') @staticmethod def get_dependencies_vt_as_xml_str(vt_id, dep_list): # pylint: disable=arguments-differ """ Return an xml element with dependencies as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. dep_list (List): List with the VT dependencies. Return: string: xml element as string. """ vt_deps_xml = Element('dependencies') for dep in dep_list: _vt_dep = Element('dependency') try: _vt_dep.set('vt_id', dep) except TypeError: logger.error('Not possible to add dependency %s for vt %s', dep, vt_id) continue vt_deps_xml.append(_vt_dep) return tostring(vt_deps_xml).decode('utf-8') @staticmethod def get_creation_time_vt_as_xml_str(vt_id, creation_time): # pylint: disable=arguments-differ """ Return creation time as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. creation_time (str): String with the VT creation time. Return: string: xml element as string. """ _time = Element('creation_time') _time.text = creation_time return tostring(_time).decode('utf-8') @staticmethod def get_modification_time_vt_as_xml_str(vt_id, modification_time): # pylint: disable=arguments-differ """ Return modification time as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. modification_time (str): String with the VT modification time. Return: string: xml element as string. """ _time = Element('modification_time') _time.text = modification_time return tostring(_time).decode('utf-8') @staticmethod def get_summary_vt_as_xml_str(vt_id, summary): """ Return summary as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. summary (str): String with a VT summary. Return: string: xml element as string. """ _summary = Element('summary') _summary.text = summary return tostring(_summary).decode('utf-8') @staticmethod def get_impact_vt_as_xml_str(vt_id, impact): """ Return impact as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. impact (str): String which explain the vulneravility impact. Return: string: xml element as string. """ _impact = Element('impact') _impact.text = impact return tostring(_impact).decode('utf-8') @staticmethod def get_affected_vt_as_xml_str(vt_id, affected): """ Return affected as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. affected (str): String which explain what is affected. Return: string: xml element as string. """ _affected = Element('affected') _affected.text = affected return tostring(_affected).decode('utf-8') @staticmethod def get_insight_vt_as_xml_str(vt_id, insight): """ Return insight as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. insight (str): String giving an insight of the vulnerability. Return: string: xml element as string. """ _insight = Element('insight') _insight.text = insight return tostring(_insight).decode('utf-8') @staticmethod def get_solution_vt_as_xml_str(vt_id, solution, solution_type=None): """ Return solution as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. solution (str): String giving a possible solution. solution_type (str): A solution type Return: string: xml element as string. """ _solution = Element('solution') _solution.text = solution if solution_type: _solution.set('type', solution_type) return tostring(_solution).decode('utf-8') @staticmethod def get_detection_vt_as_xml_str(vt_id, vuldetect=None, qod_type=None, qod=None): # pylint: disable=arguments-differ """ Return detection as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. vuldetect (str, opt): String which explain how the vulnerability was detected. qod_type (str, opt): qod type. qod (str, opt): qod value. Return: string: xml element as string. """ _detection = Element('detection') if vuldetect: _detection.text = vuldetect if qod_type: _detection.set('qod_type', qod_type) elif qod: _detection.set('qod', qod) return tostring(_detection).decode('utf-8') @property def sudo_available(self): """ Checks that sudo is available """ if self._sudo_available is not None: return self._sudo_available try: subprocess.check_call(['sudo', '-n', 'openvas', '-s'], stdout=subprocess.PIPE) self._sudo_available = True except subprocess.CalledProcessError as e: logger.debug( 'It was not possible to call openvas with sudo. ' 'The scanner will run as non-root user. Reason %s', e, ) self._sudo_available = False return self._sudo_available def check(self): """ Checks that openvas command line tool is found and is executable. """ try: result = subprocess.check_output(['openvas', '-V'], stderr=subprocess.STDOUT) result = result.decode('ascii') except OSError: # The command is not available return False if result is None: return False version = result.split('\n') if version[0].find('OpenVAS') < 0: return False self.parse_param() self.scanner_info['version'] = version[0] return True def update_progress(self, scan_id, target, current_host, msg): """ Calculate percentage and update the scan status of a host for the progress bar. Arguments: scan_id (uuid): Scan ID to identify the current scan process. target (str): Target to be updated with the calculated scan progress. msg (str): String with launched and total plugins. """ try: launched, total = msg.split('/') except ValueError: return if float(total) == 0: return elif float(total) == -1: host_prog = 100 else: host_prog = (float(launched) / float(total)) * 100 self.set_scan_target_progress(scan_id, target, current_host, host_prog) def get_openvas_status(self, scan_id, target, current_host): """ Get all status entries from redis kb. Arguments: scan_id (uuid): Scan ID to identify the current scan. target (str): Target progress to be updated. """ res = self.openvas_db.get_status() while res: self.update_progress(scan_id, target, current_host, res) res = self.openvas_db.get_status() def get_severity_score(self, oid): """ Return the severity score for the given oid. Arguments: oid (str): VT OID from which to get the severity vector Returns: The calculated cvss base value. None if there is no severity vector or severity type is not cvss base version 2. """ severity_type = self.vts[oid]['severities'].get('severity_type') severity_vector = self.vts[oid]['severities'].get( 'severity_base_vector') if severity_type == "cvss_base_v2" and severity_vector: return CVSS.cvss_base_v2_value(severity_vector) return None def get_openvas_result(self, scan_id, current_host): """ Get all result entries from redis kb. """ res = self.openvas_db.get_result() while res: msg = res.split('|||') roid = msg[3] rqod = '' rname = '' rhostname = msg[1] if msg[1] else '' host_is_dead = "Host dead" in msg[4] if not host_is_dead: if self.vts[roid].get('qod_type'): qod_t = self.vts[roid].get('qod_type') rqod = self.nvti.QOD_TYPES[qod_t] elif self.vts[roid].get('qod'): rqod = self.vts[roid].get('qod') rname = self.vts[roid].get('name') if msg[0] == 'ERRMSG': self.add_scan_error( scan_id, host=current_host, hostname=rhostname, name=rname, value=msg[4], port=msg[2], ) if msg[0] == 'LOG': self.add_scan_log( scan_id, host=current_host, hostname=rhostname, name=rname, value=msg[4], port=msg[2], qod=rqod, test_id=roid, ) if msg[0] == 'HOST_DETAIL': self.add_scan_host_detail( scan_id, host=current_host, hostname=rhostname, name=rname, value=msg[4], ) if msg[0] == 'ALARM': rseverity = self.get_severity_score(roid) self.add_scan_alarm( scan_id, host=current_host, hostname=rhostname, name=rname, value=msg[4], port=msg[2], test_id=roid, severity=rseverity, qod=rqod, ) res = self.openvas_db.get_result() def get_openvas_timestamp_scan_host(self, scan_id, target): """ Get start and end timestamp of a host scan from redis kb. """ timestamp = self.openvas_db.get_host_scan_scan_end_time() if timestamp: self.add_scan_log(scan_id, host=target, name='HOST_END', value=timestamp) return timestamp = self.openvas_db.get_host_scan_scan_start_time() if timestamp: self.add_scan_log(scan_id, host=target, name='HOST_START', value=timestamp) return def scan_is_finished(self, scan_id): """ Check if the scan has finished. """ status = self.openvas_db.get_single_item('internal/%s' % scan_id) return status == 'finished' def scan_is_stopped(self, scan_id): """ Check if the parent process has received the stop_scan order. @in scan_id: ID to identify the scan to be stopped. @return 1 if yes, None in other case. """ ctx = self.openvas_db.kb_connect(dbnum=self.main_kbindex) self.openvas_db.set_redisctx(ctx) status = self.openvas_db.get_single_item('internal/%s' % scan_id) return status == 'stop_all' def stop_scan_cleanup(self, global_scan_id): # pylint: disable=arguments-differ """ Set a key in redis to indicate the wrapper is stopped. It is done through redis because it is a new multiprocess instance and it is not possible to reach the variables of the grandchild process. Send SIGUSR2 to openvas to stop each running scan.""" ctx = self.openvas_db.kb_connect() for current_kbi in range(0, self.openvas_db.max_dbindex): self.openvas_db.select_kb(ctx, str(current_kbi), set_global=True) scan_id = self.openvas_db.get_single_item( 'internal/%s/globalscanid' % global_scan_id) if scan_id: self.openvas_db.set_single_item('internal/%s' % scan_id, ['stop_all']) ovas_pid = self.openvas_db.get_single_item('internal/ovas_pid') parent = None try: parent = psutil.Process(int(ovas_pid)) except psutil.NoSuchProcess: logger.debug('Process with pid %s already stopped', ovas_pid) except TypeError: logger.debug( 'Scan with ID %s never started and stopped ' 'unexpectedly', scan_id, ) if parent: cmd = ['openvas', '--scan-stop', scan_id] if self.sudo_available: cmd = ['sudo', '-n'] + cmd try: subprocess.Popen(cmd, shell=False) except OSError as e: # the command is not available logger.debug( 'Not possible to Stopping process: %s.' 'Reason %s', parent, e, ) return False logger.debug('Stopping process: %s', parent) self.openvas_db.release_db(current_kbi) def get_vts_in_groups(self, filters): """ Return a list of vts which match with the given filter. @input filters A list of filters. Each filter has key, operator and a value. They are separated by a space. Supported keys: family @return Return a list of vts which match with the given filter. """ vts_list = list() families = dict() for oid in self.vts: family = self.vts[oid]['custom'].get('family') if family not in families: families[family] = list() families[family].append(oid) for elem in filters: key, value = elem.split('=') if key == 'family' and value in families: vts_list.extend(families[value]) return vts_list def get_vt_param_type(self, vtid, vt_param_id): """ Return the type of the vt parameter from the vts dictionary. """ vt_params_list = self.vts[vtid].get("vt_params") if vt_params_list.get(vt_param_id): return vt_params_list[vt_param_id]["type"] return None def get_vt_param_name(self, vtid, vt_param_id): """ Return the type of the vt parameter from the vts dictionary. """ vt_params_list = self.vts[vtid].get("vt_params") if vt_params_list.get(vt_param_id): return vt_params_list[vt_param_id]["name"] return None @staticmethod def check_param_type(vt_param_value, param_type): """ Check if the value of a vt parameter matches with the type founded. """ if param_type in [ 'entry', 'file', 'password', 'radio', 'sshlogin', ] and isinstance(vt_param_value, str): return None elif param_type == 'checkbox' and (vt_param_value == '0' or vt_param_value == '1'): return None elif param_type == 'integer': try: int(vt_param_value) except ValueError: return 1 return None return 1 def process_vts(self, vts): """ Add single VTs and their parameters. """ vts_list = [] vts_params = [] vtgroups = vts.pop('vt_groups') if vtgroups: vts_list = self.get_vts_in_groups(vtgroups) for vtid, vt_params in vts.items(): if vtid not in self.vts.keys(): logger.warning( 'The vt %s was not found and it will not be loaded.', vtid, ) continue vts_list.append(vtid) for vt_param_id, vt_param_value in vt_params.items(): param_type = self.get_vt_param_type(vtid, vt_param_id) param_name = self.get_vt_param_name(vtid, vt_param_id) if not param_type or not param_name: logger.debug( 'The vt parameter %s for %s could not be loaded.', vt_param_id, vtid, ) continue if vt_param_id == '0': type_aux = 'integer' else: type_aux = param_type if self.check_param_type(vt_param_value, type_aux): logger.debug( 'Expected %s type for parameter value %s', type_aux, str(vt_param_value), ) continue if type_aux == 'checkbox': vt_param_value = _from_bool_to_str(int(vt_param_value)) param = [ "{0}:{1}:{2}:{3}".format(vtid, vt_param_id, param_type, param_name), str(vt_param_value), ] vts_params.append(param) return vts_list, vts_params @staticmethod def build_credentials_as_prefs(credentials): """ Parse the credential dictionary. @param credentials: Dictionary with the credentials. @return A list with the credentials in string format to be added to the redis KB. """ cred_prefs_list = [] for credential in credentials.items(): service = credential[0] cred_params = credentials.get(service) cred_type = cred_params.get('type', '') username = cred_params.get('username', '') password = cred_params.get('password', '') if service == 'ssh': port = cred_params.get('port', '') cred_prefs_list.append('auth_port_ssh|||' + '{0}'.format(port)) cred_prefs_list.append(OID_SSH_AUTH + ':1:' + 'entry:SSH login ' + 'name:|||{0}'.format(username)) if cred_type == 'up': cred_prefs_list.append(OID_SSH_AUTH + ':3:' + 'password:SSH password ' + '(unsafe!):|||{0}'.format(password)) else: private = cred_params.get('private', '') cred_prefs_list.append(OID_SSH_AUTH + ':2:' + 'password:SSH key passphrase:|||' + '{0}'.format(password)) cred_prefs_list.append(OID_SSH_AUTH + ':4:' + 'file:SSH private key:|||' + '{0}'.format(private)) if service == 'smb': cred_prefs_list.append(OID_SMB_AUTH + ':1:entry' + ':SMB login:|||{0}'.format(username)) cred_prefs_list.append(OID_SMB_AUTH + ':2:' + 'password:SMB password:|||' + '{0}'.format(password)) if service == 'esxi': cred_prefs_list.append(OID_ESXI_AUTH + ':1:entry:' + 'ESXi login name:|||' + '{0}'.format(username)) cred_prefs_list.append(OID_ESXI_AUTH + ':2:' + 'password:ESXi login password:|||' + '{0}'.format(password)) if service == 'snmp': community = cred_params.get('community', '') auth_algorithm = cred_params.get('auth_algorithm', '') privacy_password = cred_params.get('privacy_password', '') privacy_algorithm = cred_params.get('privacy_algorithm', '') cred_prefs_list.append(OID_SNMP_AUTH + ':1:' + 'password:SNMP Community:' + '{0}'.format(community)) cred_prefs_list.append(OID_SNMP_AUTH + ':2:' + 'entry:SNMPv3 Username:'******'{0}'.format(username)) cred_prefs_list.append(OID_SNMP_AUTH + ':3:' 'password:SNMPv3 Password:'******'{0}'.format(password)) cred_prefs_list.append(OID_SNMP_AUTH + ':4:' + 'radio:SNMPv3 Authentication ' + 'Algorithm:{0}'.format(auth_algorithm)) cred_prefs_list.append(OID_SNMP_AUTH + ':5:' + 'password:SNMPv3 Privacy Password:'******'{0}'.format(privacy_password)) cred_prefs_list.append(OID_SNMP_AUTH + ':6:' + 'radio:SNMPv3 Privacy Algorithm:' + '{0}'.format(privacy_algorithm)) return cred_prefs_list def exec_scan(self, scan_id, target): """ Starts the OpenVAS scanner for scan_id scan. """ if self.pending_feed: logger.info( '%s: There is a pending feed update. ' 'The scan can not be started.', scan_id, ) self.add_scan_error( scan_id, name='', host=target, value=('It was not possible to start the scan,' 'because a pending feed update. Please try later'), ) return 2 ports = self.get_scan_ports(scan_id, target) if not ports: self.add_scan_error(scan_id, name='', host=target, value='No port list defined.') return 2 # Get scan options options = self.get_scan_options(scan_id) prefs_val = [] ctx = self.openvas_db.kb_new() self.openvas_db.set_redisctx(ctx) self.main_kbindex = self.openvas_db.db_index # To avoid interference between scan process during a parallel scanning # new uuid is used internally for each scan. openvas_scan_id = str(uuid.uuid4()) self.openvas_db.add_single_item('internal/%s' % openvas_scan_id, ['new']) self.openvas_db.add_single_item('internal/%s/globalscanid' % scan_id, [openvas_scan_id]) exclude_hosts = self.get_scan_exclude_hosts(scan_id, target) if exclude_hosts: options['exclude_hosts'] = exclude_hosts # Get unfinished hosts, in case it is a resumed scan. And added # into exclude_hosts scan preference. Set progress for the finished ones # to 100%. finished_hosts = self.get_scan_finished_hosts(scan_id) if finished_hosts: if exclude_hosts: finished_hosts_str = ','.join(finished_hosts) exclude_hosts = exclude_hosts + ',' + finished_hosts_str options['exclude_hosts'] = exclude_hosts else: options['exclude_hosts'] = ','.join(finished_hosts) # Set scan preferences for key, value in options.items(): item_type = '' if key in OSPD_PARAMS: item_type = OSPD_PARAMS[key].get('type') if item_type == 'boolean': val = _from_bool_to_str(value) else: val = str(value) prefs_val.append(key + "|||" + val) self.openvas_db.add_single_item( 'internal/%s/scanprefs' % openvas_scan_id, prefs_val) # Store main_kbindex as global preference ov_maindbid = 'ov_maindbid|||%d' % self.main_kbindex self.openvas_db.add_single_item( 'internal/%s/scanprefs' % openvas_scan_id, [ov_maindbid]) # Set target target_aux = 'TARGET|||%s' % target self.openvas_db.add_single_item( 'internal/%s/scanprefs' % openvas_scan_id, [target_aux]) # Set port range port_range = 'port_range|||%s' % ports self.openvas_db.add_single_item( 'internal/%s/scanprefs' % openvas_scan_id, [port_range]) # Set credentials credentials = self.get_scan_credentials(scan_id, target) if credentials: cred_prefs = self.build_credentials_as_prefs(credentials) self.openvas_db.add_single_item( 'internal/%s/scanprefs' % openvas_scan_id, cred_prefs) # Set plugins to run nvts = self.get_scan_vts(scan_id) if nvts != '': nvts_list, nvts_params = self.process_vts(nvts) # Add nvts list separ = ';' plugin_list = 'plugin_set|||%s' % separ.join(nvts_list) self.openvas_db.add_single_item( 'internal/%s/scanprefs' % openvas_scan_id, [plugin_list]) # Add nvts parameters for elem in nvts_params: item = '%s|||%s' % (elem[0], elem[1]) self.openvas_db.add_single_item( 'internal/%s/scanprefs' % openvas_scan_id, [item]) else: self.openvas_db.release_db(self.main_kbindex) self.add_scan_error(scan_id, name='', host=target, value='No VTS to run.') return 2 cmd = ['openvas', '--scan-start', openvas_scan_id] if self.sudo_available: cmd = ['sudo', '-n'] + cmd if self._niceness is not None: cmd = ['nice', '-n', self._niceness] + cmd logger.debug("Running scan with niceness %s", self._niceness) try: result = subprocess.Popen(cmd, shell=False) except OSError: # the command is not available return False ovas_pid = result.pid logger.debug('pid = %s', ovas_pid) self.openvas_db.add_single_item('internal/ovas_pid', [ovas_pid]) # Wait until the scanner starts and loads all the preferences. while (self.openvas_db.get_single_item('internal/' + openvas_scan_id) == 'new'): res = result.poll() if res and res < 0: self.stop_scan_cleanup(scan_id) msg = ( 'It was not possible run the task %s, since openvas ended ' 'unexpectedly with errors during launching.' % scan_id) logger.error(msg) return 1 time.sleep(1) no_id_found = False while True: time.sleep(3) # Check if the client stopped the whole scan if self.scan_is_stopped(openvas_scan_id): return 1 ctx = self.openvas_db.kb_connect(self.main_kbindex) self.openvas_db.set_redisctx(ctx) dbs = self.openvas_db.get_list_item('internal/dbindex') for i in list(dbs): if i == self.main_kbindex: continue self.openvas_db.select_kb(ctx, str(i), set_global=True) id_aux = self.openvas_db.get_single_item('internal/scan_id') if not id_aux: continue if id_aux == openvas_scan_id: no_id_found = False current_host = self.openvas_db.get_host_ip() self.get_openvas_result(scan_id, current_host) self.get_openvas_status(scan_id, target, current_host) self.get_openvas_timestamp_scan_host(scan_id, current_host) if self.scan_is_finished(openvas_scan_id): self.set_scan_host_finished(scan_id, target, current_host) self.get_openvas_status(scan_id, target, current_host) self.get_openvas_timestamp_scan_host( scan_id, current_host) self.openvas_db.select_kb(ctx, str(self.main_kbindex), set_global=False) self.openvas_db.remove_list_item('internal/dbindex', i) self.openvas_db.release_db(i) # Scan end. No kb in use for this scan id if no_id_found: break no_id_found = True # Delete keys from KB related to this scan task. self.openvas_db.release_db(self.main_kbindex) return 1
class OSPDopenvas(OSPDaemon): """ Class for ospd-openvas daemon. """ def __init__(self, *, niceness=None, lock_file_dir='/var/run/ospd', **kwargs): """ Initializes the ospd-openvas daemon's internal data. """ self.main_db = MainDB() self.nvti = NVTICache(self.main_db) super().__init__( customvtfilter=OpenVasVtsFilter(self.nvti), storage=dict, file_storage_dir=lock_file_dir, **kwargs, ) self.server_version = __version__ self._niceness = str(niceness) self.feed_lock = LockFile(Path(lock_file_dir) / 'feed-update.lock') self.daemon_info['name'] = 'OSPd OpenVAS' self.scanner_info['name'] = 'openvas' self.scanner_info['version'] = '' # achieved during self.init() self.scanner_info['description'] = OSPD_DESC for name, param in OSPD_PARAMS.items(): self.set_scanner_param(name, param) self._sudo_available = None self._is_running_as_root = None self.scan_only_params = dict() def init(self, server: BaseServer) -> None: self.scan_collection.init() server.start(self.handle_client_stream) self.scanner_info['version'] = Openvas.get_version() self.set_params_from_openvas_settings() with self.feed_lock.wait_for_lock(): Openvas.load_vts_into_redis() notushandler = NotusMetadataHandler(nvti=self.nvti) notushandler.update_metadata() current_feed = self.nvti.get_feed_version() self.set_vts_version(vts_version=current_feed) logger.debug("Calculating vts integrity check hash...") vthelper = VtHelper(self.nvti) self.vts.sha256_hash = vthelper.calculate_vts_collection_hash() self.initialized = True def set_params_from_openvas_settings(self): """Set OSPD_PARAMS with the params taken from the openvas executable.""" param_list = Openvas.get_settings() for elem in param_list: if elem not in OSPD_PARAMS: self.scan_only_params[elem] = param_list[elem] else: OSPD_PARAMS[elem]['default'] = param_list[elem] def feed_is_outdated(self, current_feed: str) -> Optional[bool]: """Compare the current feed with the one in the disk. Return: False if there is no new feed. True if the feed version in disk is newer than the feed in redis cache. None if there is no feed on the disk. """ plugins_folder = self.scan_only_params.get('plugins_folder') if not plugins_folder: raise OspdOpenvasError("Error: Path to plugins folder not found.") feed_info_file = Path(plugins_folder) / 'plugin_feed_info.inc' if not feed_info_file.exists(): self.set_params_from_openvas_settings() logger.debug('Plugins feed file %s not found.', feed_info_file) return None current_feed = safe_int(current_feed) if current_feed is None: logger.debug( "Wrong PLUGIN_SET format in plugins feed file %s. Format has to" " be yyyymmddhhmm. For example 'PLUGIN_SET = \"201910251033\"'", feed_info_file, ) feed_date = None with feed_info_file.open() as fcontent: for line in fcontent: if "PLUGIN_SET" in line: feed_date = line.split('=', 1)[1] feed_date = feed_date.strip() feed_date = feed_date.replace(';', '') feed_date = feed_date.replace('"', '') feed_date = safe_int(feed_date) break logger.debug("Current feed version: %s", current_feed) logger.debug("Plugin feed version: %s", feed_date) return ((not feed_date) or (not current_feed) or (current_feed < feed_date)) def check_feed(self): """Check if there is a feed update. Wait until all the running scans finished. Set a flag to announce there is a pending feed update, which avoids to start a new scan. """ if not self.vts.is_cache_available: return current_feed = self.nvti.get_feed_version() is_outdated = self.feed_is_outdated(current_feed) # Check if the nvticache in redis is outdated if not current_feed or is_outdated: with self.feed_lock as fl: if fl.has_lock(): self.initialized = False Openvas.load_vts_into_redis() notushandler = NotusMetadataHandler(nvti=self.nvti) notushandler.update_metadata() current_feed = self.nvti.get_feed_version() self.set_vts_version(vts_version=current_feed) vthelper = VtHelper(self.nvti) self.vts.sha256_hash = ( vthelper.calculate_vts_collection_hash()) self.initialized = True else: logger.debug("The feed was not upload or it is outdated, " "but other process is locking the update. " "Trying again later...") return def scheduler(self): """This method is called periodically to run tasks.""" self.check_feed() def get_vt_iterator(self, vt_selection: List[str] = None, details: bool = True) -> Iterator[Tuple[str, Dict]]: vthelper = VtHelper(self.nvti) return vthelper.get_vt_iterator(vt_selection, details) @staticmethod def get_custom_vt_as_xml_str(vt_id: str, custom: Dict) -> str: """Return an xml element with custom metadata formatted as string. Arguments: vt_id: VT OID. Only used for logging in error case. custom: Dictionary with the custom metadata. Return: Xml element as string. """ _custom = Element('custom') for key, val in custom.items(): xml_key = SubElement(_custom, key) try: xml_key.text = val except ValueError as e: logger.warning( "Not possible to parse custom tag for VT %s: %s", vt_id, e) return tostring(_custom).decode('utf-8') @staticmethod def get_severities_vt_as_xml_str(vt_id: str, severities: Dict) -> str: """Return an xml element with severities as string. Arguments: vt_id: VT OID. Only used for logging in error case. severities: Dictionary with the severities. Return: Xml element as string. """ _severities = Element('severities') _severity = SubElement(_severities, 'severity') if 'severity_base_vector' in severities: try: _value = SubElement(_severity, 'value') _value.text = severities.get('severity_base_vector') except ValueError as e: logger.warning( "Not possible to parse severity tag for vt %s: %s", vt_id, e) if 'severity_origin' in severities: _origin = SubElement(_severity, 'origin') _origin.text = severities.get('severity_origin') if 'severity_date' in severities: _date = SubElement(_severity, 'date') _date.text = severities.get('severity_date') if 'severity_type' in severities: _severity.set('type', severities.get('severity_type')) return tostring(_severities).decode('utf-8') @staticmethod def get_params_vt_as_xml_str(vt_id: str, vt_params: Dict) -> str: """Return an xml element with params formatted as string. Arguments: vt_id: VT OID. Only used for logging in error case. vt_params: Dictionary with the VT parameters. Return: Xml element as string. """ vt_params_xml = Element('params') for _pref_id, prefs in vt_params.items(): vt_param = Element('param') vt_param.set('type', prefs['type']) vt_param.set('id', _pref_id) xml_name = SubElement(vt_param, 'name') try: xml_name.text = prefs['name'] except ValueError as e: logger.warning("Not possible to parse parameter for VT %s: %s", vt_id, e) if prefs['default']: xml_def = SubElement(vt_param, 'default') try: xml_def.text = prefs['default'] except ValueError as e: logger.warning( "Not possible to parse default parameter for VT %s: %s", vt_id, e, ) vt_params_xml.append(vt_param) return tostring(vt_params_xml).decode('utf-8') @staticmethod def get_refs_vt_as_xml_str(vt_id: str, vt_refs: Dict) -> str: """Return an xml element with references formatted as string. Arguments: vt_id: VT OID. Only used for logging in error case. vt_refs: Dictionary with the VT references. Return: Xml element as string. """ vt_refs_xml = Element('refs') for ref_type, ref_values in vt_refs.items(): for value in ref_values: vt_ref = Element('ref') if ref_type == "xref" and value: for xref in value.split(', '): try: _type, _id = xref.split(':', 1) except ValueError: logger.error( 'Not possible to parse xref %s for VT %s', xref, vt_id, ) continue vt_ref.set('type', _type.lower()) vt_ref.set('id', _id) elif value: vt_ref.set('type', ref_type.lower()) vt_ref.set('id', value) else: continue vt_refs_xml.append(vt_ref) return tostring(vt_refs_xml).decode('utf-8') @staticmethod def get_dependencies_vt_as_xml_str(vt_id: str, vt_dependencies: List) -> str: """Return an xml element with dependencies as string. Arguments: vt_id: VT OID. Only used for logging in error case. vt_dependencies: List with the VT dependencies. Return: Xml element as string. """ vt_deps_xml = Element('dependencies') for dep in vt_dependencies: _vt_dep = Element('dependency') try: _vt_dep.set('vt_id', dep) except (ValueError, TypeError): logger.error('Not possible to add dependency %s for VT %s', dep, vt_id) continue vt_deps_xml.append(_vt_dep) return tostring(vt_deps_xml).decode('utf-8') @staticmethod def get_creation_time_vt_as_xml_str(vt_id: str, vt_creation_time: str) -> str: """Return creation time as string. Arguments: vt_id: VT OID. Only used for logging in error case. vt_creation_time: String with the VT creation time. Return: Xml element as string. """ _time = Element('creation_time') try: _time.text = vt_creation_time except ValueError as e: logger.warning("Not possible to parse creation time for VT %s: %s", vt_id, e) return tostring(_time).decode('utf-8') @staticmethod def get_modification_time_vt_as_xml_str(vt_id: str, vt_modification_time: str) -> str: """Return modification time as string. Arguments: vt_id: VT OID. Only used for logging in error case. vt_modification_time: String with the VT modification time. Return: Xml element as string. """ _time = Element('modification_time') try: _time.text = vt_modification_time except ValueError as e: logger.warning( "Not possible to parse modification time for VT %s: %s", vt_id, e, ) return tostring(_time).decode('utf-8') @staticmethod def get_summary_vt_as_xml_str(vt_id: str, summary: str) -> str: """Return summary as string. Arguments: vt_id: VT OID. Only used for logging in error case. summary: String with a VT summary. Return: Xml element as string. """ _summary = Element('summary') try: _summary.text = summary except ValueError as e: logger.warning("Not possible to parse summary tag for VT %s: %s", vt_id, e) return tostring(_summary).decode('utf-8') @staticmethod def get_impact_vt_as_xml_str(vt_id: str, impact) -> str: """Return impact as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. impact (str): String which explain the vulneravility impact. Return: string: xml element as string. """ _impact = Element('impact') try: _impact.text = impact except ValueError as e: logger.warning("Not possible to parse impact tag for VT %s: %s", vt_id, e) return tostring(_impact).decode('utf-8') @staticmethod def get_affected_vt_as_xml_str(vt_id: str, affected: str) -> str: """Return affected as string. Arguments: vt_id: VT OID. Only used for logging in error case. affected: String which explain what is affected. Return: Xml element as string. """ _affected = Element('affected') try: _affected.text = affected except ValueError as e: logger.warning("Not possible to parse affected tag for VT %s: %s", vt_id, e) return tostring(_affected).decode('utf-8') @staticmethod def get_insight_vt_as_xml_str(vt_id: str, insight: str) -> str: """Return insight as string. Arguments: vt_id: VT OID. Only used for logging in error case. insight: String giving an insight of the vulnerability. Return: Xml element as string. """ _insight = Element('insight') try: _insight.text = insight except ValueError as e: logger.warning("Not possible to parse insight tag for VT %s: %s", vt_id, e) return tostring(_insight).decode('utf-8') @staticmethod def get_solution_vt_as_xml_str( vt_id: str, solution: str, solution_type: Optional[str] = None, solution_method: Optional[str] = None, ) -> str: """Return solution as string. Arguments: vt_id: VT OID. Only used for logging in error case. solution: String giving a possible solution. solution_type: A solution type solution_method: A solution method Return: Xml element as string. """ _solution = Element('solution') try: _solution.text = solution except ValueError as e: logger.warning("Not possible to parse solution tag for VT %s: %s", vt_id, e) if solution_type: _solution.set('type', solution_type) if solution_method: _solution.set('method', solution_method) return tostring(_solution).decode('utf-8') @staticmethod def get_detection_vt_as_xml_str( vt_id: str, detection: Optional[str] = None, qod_type: Optional[str] = None, qod: Optional[str] = None, ) -> str: """Return detection as string. Arguments: vt_id: VT OID. Only used for logging in error case. detection: String which explain how the vulnerability was detected. qod_type: qod type. qod: qod value. Return: Xml element as string. """ _detection = Element('detection') if detection: try: _detection.text = detection except ValueError as e: logger.warning( "Not possible to parse detection tag for VT %s: %s", vt_id, e, ) if qod_type: _detection.set('qod_type', qod_type) elif qod: _detection.set('qod', qod) return tostring(_detection).decode('utf-8') @property def is_running_as_root(self) -> bool: """ Check if it is running as root user.""" if self._is_running_as_root is not None: return self._is_running_as_root self._is_running_as_root = False if geteuid() == 0: self._is_running_as_root = True return self._is_running_as_root @property def sudo_available(self) -> bool: """ Checks that sudo is available """ if self._sudo_available is not None: return self._sudo_available if self.is_running_as_root: self._sudo_available = False return self._sudo_available self._sudo_available = Openvas.check_sudo() return self._sudo_available def check(self) -> bool: """Checks that openvas command line tool is found and is executable.""" has_openvas = Openvas.check() if not has_openvas: logger.error( 'openvas executable not available. Please install openvas' ' into your PATH.') return has_openvas def report_openvas_scan_status(self, kbdb: BaseDB, scan_id: str): """Get all status entries from redis kb. Arguments: scan_id: Scan ID to identify the current scan. current_host: Host to be updated. """ all_status = kbdb.get_scan_status() all_hosts = dict() finished_hosts = list() for res in all_status: try: current_host, launched, total = res.split('/') except ValueError: continue try: if float(total) == 0: continue elif float(total) == ScanProgress.DEAD_HOST: host_prog = ScanProgress.DEAD_HOST else: host_prog = int((float(launched) / float(total)) * 100) except TypeError: continue all_hosts[current_host] = host_prog if (host_prog == ScanProgress.DEAD_HOST or host_prog == ScanProgress.FINISHED): finished_hosts.append(current_host) self.set_scan_progress_batch(scan_id, host_progress=all_hosts) self.sort_host_finished(scan_id, finished_hosts) def get_severity_score(self, vt_aux: dict) -> Optional[float]: """Return the severity score for the given oid. Arguments: vt_aux: VT element from which to get the severity vector Returns: The calculated cvss base value. None if there is no severity vector or severity type is not cvss base version 2. """ if vt_aux: severity_type = vt_aux['severities'].get('severity_type') severity_vector = vt_aux['severities'].get('severity_base_vector') if severity_type == "cvss_base_v2" and severity_vector: return CVSS.cvss_base_v2_value(severity_vector) elif severity_type == "cvss_base_v3" and severity_vector: return CVSS.cvss_base_v3_value(severity_vector) return None def report_openvas_results(self, db: BaseDB, scan_id: str) -> bool: """ Get all result entries from redis kb. """ vthelper = VtHelper(self.nvti) # Result messages come in the next form, with optional uri field # type ||| host ip ||| hostname ||| port ||| OID ||| value [|||uri] all_results = db.get_result() res_list = ResultList() total_dead = 0 for res in all_results: if not res: continue msg = res.split('|||') roid = msg[4].strip() rqod = '' rname = '' current_host = msg[1].strip() if msg[1] else '' rhostname = msg[2].strip() if msg[2] else '' host_is_dead = "Host dead" in msg[5] or msg[0] == "DEADHOST" host_deny = "Host access denied" in msg[5] start_end_msg = msg[0] == "HOST_START" or msg[0] == "HOST_END" vt_aux = None # URI is optional and msg list length must be checked ruri = '' if len(msg) > 6: ruri = msg[6] if (roid and not host_is_dead and not host_deny and not start_end_msg): vt_aux = vthelper.get_single_vt(roid) if (not vt_aux and not host_is_dead and not host_deny and not start_end_msg): logger.warning('Invalid VT oid %s for a result', roid) if vt_aux: if vt_aux.get('qod_type'): qod_t = vt_aux.get('qod_type') rqod = self.nvti.QOD_TYPES[qod_t] elif vt_aux.get('qod'): rqod = vt_aux.get('qod') rname = vt_aux.get('name') if msg[0] == 'ERRMSG': res_list.add_scan_error_to_list( host=current_host, hostname=rhostname, name=rname, value=msg[5], port=msg[3], test_id=roid, uri=ruri, ) elif msg[0] == 'HOST_START' or msg[0] == 'HOST_END': res_list.add_scan_log_to_list( host=current_host, name=msg[0], value=msg[5], ) elif msg[0] == 'LOG': res_list.add_scan_log_to_list( host=current_host, hostname=rhostname, name=rname, value=msg[5], port=msg[3], qod=rqod, test_id=roid, uri=ruri, ) elif msg[0] == 'HOST_DETAIL': res_list.add_scan_host_detail_to_list( host=current_host, hostname=rhostname, name=rname, value=msg[5], uri=ruri, ) elif msg[0] == 'ALARM': rseverity = self.get_severity_score(vt_aux) res_list.add_scan_alarm_to_list( host=current_host, hostname=rhostname, name=rname, value=msg[5], port=msg[3], test_id=roid, severity=rseverity, qod=rqod, uri=ruri, ) # To process non-scanned dead hosts when # test_alive_host_only in openvas is enable elif msg[0] == 'DEADHOST': try: total_dead = int(msg[5]) except TypeError: logger.debug('Error processing dead host count') # Insert result batch into the scan collection table. if len(res_list): self.scan_collection.add_result_list(scan_id, res_list) if total_dead: self.scan_collection.set_amount_dead_hosts(scan_id, total_dead=total_dead) return len(res_list) > 0 def is_openvas_process_alive(self, kbdb: BaseDB, ovas_pid: str, scan_id: str) -> bool: parent_exists = True parent = None try: parent = psutil.Process(int(ovas_pid)) except psutil.NoSuchProcess: logger.debug('Process with pid %s already stopped', ovas_pid) parent_exists = False except TypeError: logger.debug( 'Scan with ID %s never started and stopped unexpectedly', scan_id, ) parent_exists = False is_zombie = False if parent and parent.status() == psutil.STATUS_ZOMBIE: is_zombie = True if (not parent_exists or is_zombie) and kbdb: if kbdb and kbdb.scan_is_stopped(scan_id): return True return False return True def stop_scan_cleanup( # pylint: disable=arguments-differ self, scan_id: str): """Set a key in redis to indicate the wrapper is stopped. It is done through redis because it is a new multiprocess instance and it is not possible to reach the variables of the grandchild process. Send SIGUSR2 to openvas to stop each running scan.""" kbdb = self.main_db.find_kb_database_by_scan_id(scan_id) if kbdb: kbdb.stop_scan(scan_id) ovas_pid = kbdb.get_scan_process_id() parent = None try: parent = psutil.Process(int(ovas_pid)) except psutil.NoSuchProcess: logger.debug('Process with pid %s already stopped', ovas_pid) except TypeError: logger.debug( 'Scan with ID %s never started and stopped unexpectedly', scan_id, ) if parent: can_stop_scan = Openvas.stop_scan( scan_id, not self.is_running_as_root and self.sudo_available, ) if not can_stop_scan: logger.debug( 'Not possible to stop scan process: %s.', parent, ) return False logger.debug('Stopping process: %s', parent) while parent: try: parent = psutil.Process(int(ovas_pid)) except psutil.NoSuchProcess: parent = None for scan_db in kbdb.get_scan_databases(): self.main_db.release_database(scan_db) def exec_scan(self, scan_id: str): """ Starts the OpenVAS scanner for scan_id scan. """ do_not_launch = False kbdb = self.main_db.get_new_kb_database() scan_prefs = PreferenceHandler(scan_id, kbdb, self.scan_collection, self.nvti) kbdb.add_scan_id(scan_id) scan_prefs.prepare_target_for_openvas() if not scan_prefs.prepare_ports_for_openvas(): self.add_scan_error(scan_id, name='', host='', value='No port list defined.') do_not_launch = True # Set credentials if not scan_prefs.prepare_credentials_for_openvas(): self.add_scan_error(scan_id, name='', host='', value='Malformed credential.') do_not_launch = True if not scan_prefs.prepare_plugins_for_openvas(): self.add_scan_error(scan_id, name='', host='', value='No VTS to run.') do_not_launch = True scan_prefs.prepare_main_kbindex_for_openvas() scan_prefs.prepare_host_options_for_openvas() scan_prefs.prepare_scan_params_for_openvas(OSPD_PARAMS) scan_prefs.prepare_reverse_lookup_opt_for_openvas() scan_prefs.prepare_alive_test_option_for_openvas() scan_prefs.prepare_boreas_alive_test() # Release memory used for scan preferences. del scan_prefs if do_not_launch: self.main_db.release_database(kbdb) return result = Openvas.start_scan( scan_id, not self.is_running_as_root and self.sudo_available, self._niceness, ) if result is None: self.main_db.release_database(kbdb) return ovas_pid = result.pid kbdb.add_scan_process_id(ovas_pid) logger.debug('pid = %s', ovas_pid) # Wait until the scanner starts and loads all the preferences. while kbdb.get_status(scan_id) == 'new': res = result.poll() if res and res < 0: self.stop_scan_cleanup(scan_id) logger.error( 'It was not possible run the task %s, since openvas ended ' 'unexpectedly with errors during launching.', scan_id, ) return time.sleep(1) got_results = False while True: if not kbdb.target_is_finished( scan_id) and not self.is_openvas_process_alive( kbdb, ovas_pid, scan_id): logger.error( 'Task %s was unexpectedly stopped or killed.', scan_id, ) self.add_scan_error( scan_id, name='', host='', value='Task was unexpectedly stopped or killed.', ) kbdb.stop_scan(scan_id) for scan_db in kbdb.get_scan_databases(): self.main_db.release_database(scan_db) self.main_db.release_database(kbdb) return # Wait a second before trying to get result from redis if there # was no results before. # Otherwise, wait 50 msec to give access other process to redis. if not got_results: time.sleep(1) else: time.sleep(0.05) got_results = False # Check if the client stopped the whole scan if kbdb.scan_is_stopped(scan_id): # clean main_db, but wait for scanner to finish. while not kbdb.target_is_finished(scan_id): time.sleep(1) self.main_db.release_database(kbdb) return got_results = self.report_openvas_results(kbdb, scan_id) self.report_openvas_scan_status(kbdb, scan_id) # Scan end. No kb in use for this scan id if kbdb.target_is_finished(scan_id): break # Delete keys from KB related to this scan task. self.main_db.release_database(kbdb)
class OSPDopenvas(OSPDaemon): """ Class for ospd-openvas daemon. """ def __init__( self, *, niceness=None, lock_file_dir='/var/run/ospd', **kwargs ): """ Initializes the ospd-openvas daemon's internal data. """ super().__init__(customvtfilter=OpenVasVtsFilter(), **kwargs) self.server_version = __version__ self._niceness = str(niceness) self.feed_lock = LockFile(Path(lock_file_dir) / 'feed-update.lock') self.daemon_info['name'] = 'OSPd OpenVAS' self.scanner_info['name'] = 'openvas' self.scanner_info['version'] = '' # achieved during self.init() self.scanner_info['description'] = OSPD_DESC for name, param in OSPD_PARAMS.items(): self.set_scanner_param(name, param) self._sudo_available = None self._is_running_as_root = None self.scan_only_params = dict() self.main_db = MainDB() self.nvti = NVTICache(self.main_db) self.pending_feed = None def init(self, server: BaseServer) -> None: server.start(self.handle_client_stream) self.scanner_info['version'] = Openvas.get_version() self.set_params_from_openvas_settings() if not self.nvti.ctx: with self.feed_lock.wait_for_lock(): Openvas.load_vts_into_redis() self.load_vts() self.initialized = True def set_params_from_openvas_settings(self): """ Set OSPD_PARAMS with the params taken from the openvas executable. """ param_list = Openvas.get_settings() for elem in param_list: if elem not in OSPD_PARAMS: self.scan_only_params[elem] = param_list[elem] else: OSPD_PARAMS[elem]['default'] = param_list[elem] def feed_is_outdated(self, current_feed: str) -> Optional[bool]: """ Compare the current feed with the one in the disk. Return: False if there is no new feed. True if the feed version in disk is newer than the feed in redis cache. None if there is no feed on the disk. """ plugins_folder = self.scan_only_params.get('plugins_folder') if not plugins_folder: raise OspdOpenvasError("Error: Path to plugins folder not found.") feed_info_file = Path(plugins_folder) / 'plugin_feed_info.inc' if not feed_info_file.exists(): self.set_params_from_openvas_settings() logger.debug('Plugins feed file %s not found.', feed_info_file) return None current_feed = safe_int(current_feed) feed_date = None with feed_info_file.open() as fcontent: for line in fcontent: if "PLUGIN_SET" in line: feed_date = line.split('=', 1)[1] feed_date = feed_date.strip() feed_date = feed_date.replace(';', '') feed_date = feed_date.replace('"', '') feed_date = safe_int(feed_date) break logger.debug("Current feed version: %s", current_feed) logger.debug("Plugin feed version: %s", feed_date) return ( (not feed_date) or (not current_feed) or (current_feed < feed_date) ) def feed_is_healthy(self): """ Compare the amount of filename keys and nvt keys in redis with the amount of oid loaded in memory. Return: True if the count is matching. False on failure. """ filename_count = self.nvti.get_nvt_files_count() nvt_count = self.nvti.get_nvt_count() return len(self.vts) == filename_count == nvt_count def check_feed(self): """ Check if there is a feed update. Wait until all the running scans finished. Set a flag to announce there is a pending feed update, which avoids to start a new scan. """ current_feed = self.nvti.get_feed_version() is_outdated = self.feed_is_outdated(current_feed) # Check if the feed is already accessible from the disk. if current_feed and is_outdated is None: self.pending_feed = True return # Check if the nvticache in redis is outdated if not current_feed or is_outdated: self.pending_feed = True with self.feed_lock as fl: if fl.has_lock(): Openvas.load_vts_into_redis() else: logger.debug( "The feed was not upload or it is outdated, " "but other process is locking the update. " "Trying again later..." ) return _running_scan = False for scan_id in self.scan_processes: if self.scan_processes[scan_id].is_alive(): _running_scan = True # Check if the NVT dict is outdated if self.pending_feed: _pending_feed = True else: _pending_feed = ( self.get_vts_version() != self.nvti.get_feed_version() ) _feed_is_healthy = self.feed_is_healthy() if _running_scan and not _feed_is_healthy: _pending_feed = True with self.feed_lock as fl: if fl.has_lock(): self.nvti.force_reload() Openvas.load_vts_into_redis() else: logger.debug( "The VT Cache in memory is not healthy " "and other process is locking the update. " "Trying again later..." ) return if _running_scan and _pending_feed: if not self.pending_feed: self.pending_feed = True logger.info( 'There is a running scan process locking the feed update. ' 'Therefore the feed update will be performed later.' ) elif ( _pending_feed and not _running_scan and not self.feed_lock.is_locked() ): self.vts.clear() self.load_vts() def scheduler(self): """This method is called periodically to run tasks.""" self.check_feed() def get_single_vt(self, vt_id, oids=None): _vt_params = self.nvti.get_nvt_params(vt_id) _vt_refs = self.nvti.get_nvt_refs(vt_id) _custom = self.nvti.get_nvt_metadata(vt_id) _name = _custom.pop('name') _vt_creation_time = _custom.pop('creation_date') _vt_modification_time = _custom.pop('last_modification') if oids: _vt_dependencies = list() if 'dependencies' in _custom: _deps = _custom.pop('dependencies') _deps_list = _deps.split(', ') for dep in _deps_list: _vt_dependencies.append(oids.get(dep)) else: _vt_dependencies = None _summary = None _impact = None _affected = None _insight = None _solution = None _solution_t = None _vuldetect = None _qod_t = None _qod_v = None if 'summary' in _custom: _summary = _custom.pop('summary') if 'impact' in _custom: _impact = _custom.pop('impact') if 'affected' in _custom: _affected = _custom.pop('affected') if 'insight' in _custom: _insight = _custom.pop('insight') if 'solution' in _custom: _solution = _custom.pop('solution') if 'solution_type' in _custom: _solution_t = _custom.pop('solution_type') if 'vuldetect' in _custom: _vuldetect = _custom.pop('vuldetect') if 'qod_type' in _custom: _qod_t = _custom.pop('qod_type') elif 'qod' in _custom: _qod_v = _custom.pop('qod') _severity = dict() if 'severity_base_vector' in _custom: _severity_vector = _custom.pop('severity_base_vector') else: _severity_vector = _custom.pop('cvss_base_vector') _severity['severity_base_vector'] = _severity_vector if 'severity_type' in _custom: _severity_type = _custom.pop('severity_type') else: _severity_type = 'cvss_base_v2' _severity['severity_type'] = _severity_type if 'severity_origin' in _custom: _severity['severity_origin'] = _custom.pop('severity_origin') if _name is None: _name = '' vt = {'name': _name} if _custom is not None: vt["custom"] = _custom if _vt_params is not None: vt["vt_params"] = _vt_params if _vt_refs is not None: vt["vt_refs"] = _vt_refs if _vt_dependencies is not None: vt["vt_dependencies"] = _vt_dependencies if _vt_creation_time is not None: vt["creation_time"] = _vt_creation_time if _vt_modification_time is not None: vt["modification_time"] = _vt_modification_time if _summary is not None: vt["summary"] = _summary if _impact is not None: vt["impact"] = _impact if _affected is not None: vt["affected"] = _affected if _insight is not None: vt["insight"] = _insight if _solution is not None: vt["solution"] = _solution if _solution_t is not None: vt["solution_type"] = _solution_t if _vuldetect is not None: vt["detection"] = _vuldetect if _qod_t is not None: vt["qod_type"] = _qod_t elif _qod_v is not None: vt["qod"] = _qod_v if _severity is not None: vt["severities"] = _severity return vt def get_vt_iterator( self, vt_selection: List[str] = None, details: bool = True ) -> Iterator[Tuple[str, Dict]]: """ Yield the vts from the Redis NVTicache. """ oids = None if details: oids = dict(self.nvti.get_oids()) for vt_id in vt_selection: vt = self.get_single_vt(vt_id, oids) yield (vt_id, vt) def load_vts(self): """ Load the VT's metadata into the vts global dictionary. """ with self.feed_lock as fl: if not fl.has_lock(): logger.warning( 'Error acquiring feed lock. Trying again later...' ) return self.initialized = False logger.info('Loading VTs in memory.') oids = dict(self.nvti.get_oids()) logger.debug('Found %s NVTs in redis.', len(oids)) for _, vt_id in oids.items(): vt = self.get_single_vt(vt_id, oids) if ( not vt or vt.get('vt_params') is None or vt.get('custom') is None ): logger.warning( 'Error loading VTs in memory. Trying again later...' ) return custom = {'family': vt['custom'].get('family')} try: self.add_vt( vt_id, name=vt.get('name'), qod_t=vt.get('qod_type'), qod_v=vt.get('qod'), severities=vt.get('severities'), vt_modification_time=vt.get('modification_time'), vt_params=vt.get('vt_params'), custom=custom, ) except OspdError as e: logger.warning("Error while adding VT %s. %s", vt_id, e) _feed_version = self.nvti.get_feed_version() self.set_vts_version(vts_version=_feed_version) self.vts.calculate_vts_collection_hash() self.pending_feed = False self.initialized = True logger.info('Finish loading up vts.') logger.debug('Loaded %s vts.', len(self.vts)) @staticmethod def get_custom_vt_as_xml_str(vt_id: str, custom: Dict) -> str: """ Return an xml element with custom metadata formatted as string. Arguments: vt_id: VT OID. Only used for logging in error case. custom: Dictionary with the custom metadata. Return: Xml element as string. """ _custom = Element('custom') for key, val in custom.items(): xml_key = SubElement(_custom, key) try: xml_key.text = val except ValueError as e: logger.warning( "Not possible to parse custom tag for VT %s: %s", vt_id, e ) return tostring(_custom).decode('utf-8') @staticmethod def get_severities_vt_as_xml_str(vt_id: str, severities: Dict) -> str: """ Return an xml element with severities as string. Arguments: vt_id: VT OID. Only used for logging in error case. severities: Dictionary with the severities. Return: Xml element as string. """ _severities = Element('severities') _severity = SubElement(_severities, 'severity') if 'severity_base_vector' in severities: try: _severity.text = severities.get('severity_base_vector') except ValueError as e: logger.warning( "Not possible to parse severity tag for vt %s: %s", vt_id, e ) if 'severity_origin' in severities: _severity.set('origin', severities.get('severity_origin')) if 'severity_type' in severities: _severity.set('type', severities.get('severity_type')) return tostring(_severities).decode('utf-8') @staticmethod def get_params_vt_as_xml_str(vt_id: str, vt_params: Dict) -> str: """ Return an xml element with params formatted as string. Arguments: vt_id: VT OID. Only used for logging in error case. vt_params: Dictionary with the VT parameters. Return: Xml element as string. """ vt_params_xml = Element('params') for _pref_id, prefs in vt_params.items(): vt_param = Element('param') vt_param.set('type', prefs['type']) vt_param.set('id', _pref_id) xml_name = SubElement(vt_param, 'name') try: xml_name.text = prefs['name'] except ValueError as e: logger.warning( "Not possible to parse parameter for VT %s: %s", vt_id, e ) if prefs['default']: xml_def = SubElement(vt_param, 'default') try: xml_def.text = prefs['default'] except ValueError as e: logger.warning( "Not possible to parse default parameter for VT %s: %s", vt_id, e, ) vt_params_xml.append(vt_param) return tostring(vt_params_xml).decode('utf-8') @staticmethod def get_refs_vt_as_xml_str(vt_id: str, vt_refs: Dict) -> str: """ Return an xml element with references formatted as string. Arguments: vt_id: VT OID. Only used for logging in error case. vt_refs: Dictionary with the VT references. Return: Xml element as string. """ vt_refs_xml = Element('refs') for ref_type, ref_values in vt_refs.items(): for value in ref_values: vt_ref = Element('ref') if ref_type == "xref" and value: for xref in value.split(', '): try: _type, _id = xref.split(':', 1) except ValueError: logger.error( 'Not possible to parse xref %s for VT %s', xref, vt_id, ) continue vt_ref.set('type', _type.lower()) vt_ref.set('id', _id) elif value: vt_ref.set('type', ref_type.lower()) vt_ref.set('id', value) else: continue vt_refs_xml.append(vt_ref) return tostring(vt_refs_xml).decode('utf-8') @staticmethod def get_dependencies_vt_as_xml_str( vt_id: str, vt_dependencies: List ) -> str: """ Return an xml element with dependencies as string. Arguments: vt_id: VT OID. Only used for logging in error case. vt_dependencies: List with the VT dependencies. Return: Xml element as string. """ vt_deps_xml = Element('dependencies') for dep in vt_dependencies: _vt_dep = Element('dependency') try: _vt_dep.set('vt_id', dep) except (ValueError, TypeError): logger.error( 'Not possible to add dependency %s for VT %s', dep, vt_id ) continue vt_deps_xml.append(_vt_dep) return tostring(vt_deps_xml).decode('utf-8') @staticmethod def get_creation_time_vt_as_xml_str( vt_id: str, vt_creation_time: str ) -> str: """ Return creation time as string. Arguments: vt_id: VT OID. Only used for logging in error case. vt_creation_time: String with the VT creation time. Return: Xml element as string. """ _time = Element('creation_time') try: _time.text = vt_creation_time except ValueError as e: logger.warning( "Not possible to parse creation time for VT %s: %s", vt_id, e ) return tostring(_time).decode('utf-8') @staticmethod def get_modification_time_vt_as_xml_str( vt_id: str, vt_modification_time: str ) -> str: """ Return modification time as string. Arguments: vt_id: VT OID. Only used for logging in error case. vt_modification_time: String with the VT modification time. Return: Xml element as string. """ _time = Element('modification_time') try: _time.text = vt_modification_time except ValueError as e: logger.warning( "Not possible to parse modification time for VT %s: %s", vt_id, e, ) return tostring(_time).decode('utf-8') @staticmethod def get_summary_vt_as_xml_str(vt_id: str, summary: str) -> str: """ Return summary as string. Arguments: vt_id: VT OID. Only used for logging in error case. summary: String with a VT summary. Return: Xml element as string. """ _summary = Element('summary') try: _summary.text = summary except ValueError as e: logger.warning( "Not possible to parse summary tag for VT %s: %s", vt_id, e ) return tostring(_summary).decode('utf-8') @staticmethod def get_impact_vt_as_xml_str(vt_id: str, impact) -> str: """ Return impact as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. impact (str): String which explain the vulneravility impact. Return: string: xml element as string. """ _impact = Element('impact') try: _impact.text = impact except ValueError as e: logger.warning( "Not possible to parse impact tag for VT %s: %s", vt_id, e ) return tostring(_impact).decode('utf-8') @staticmethod def get_affected_vt_as_xml_str(vt_id: str, affected: str) -> str: """ Return affected as string. Arguments: vt_id: VT OID. Only used for logging in error case. affected: String which explain what is affected. Return: Xml element as string. """ _affected = Element('affected') try: _affected.text = affected except ValueError as e: logger.warning( "Not possible to parse affected tag for VT %s: %s", vt_id, e ) return tostring(_affected).decode('utf-8') @staticmethod def get_insight_vt_as_xml_str(vt_id: str, insight: str) -> str: """ Return insight as string. Arguments: vt_id: VT OID. Only used for logging in error case. insight: String giving an insight of the vulnerability. Return: Xml element as string. """ _insight = Element('insight') try: _insight.text = insight except ValueError as e: logger.warning( "Not possible to parse insight tag for VT %s: %s", vt_id, e ) return tostring(_insight).decode('utf-8') @staticmethod def get_solution_vt_as_xml_str( vt_id: str, solution: str, solution_type: Optional[str] = None, solution_method: Optional[str] = None, ) -> str: """ Return solution as string. Arguments: vt_id: VT OID. Only used for logging in error case. solution: String giving a possible solution. solution_type: A solution type solution_method: A solution method Return: Xml element as string. """ _solution = Element('solution') try: _solution.text = solution except ValueError as e: logger.warning( "Not possible to parse solution tag for VT %s: %s", vt_id, e ) if solution_type: _solution.set('type', solution_type) if solution_method: _solution.set('method', solution_method) return tostring(_solution).decode('utf-8') @staticmethod def get_detection_vt_as_xml_str( vt_id: str, detection: Optional[str] = None, qod_type: Optional[str] = None, qod: Optional[str] = None, ) -> str: """ Return detection as string. Arguments: vt_id: VT OID. Only used for logging in error case. detection: String which explain how the vulnerability was detected. qod_type: qod type. qod: qod value. Return: Xml element as string. """ _detection = Element('detection') if detection: try: _detection.text = detection except ValueError as e: logger.warning( "Not possible to parse detection tag for VT %s: %s", vt_id, e, ) if qod_type: _detection.set('qod_type', qod_type) elif qod: _detection.set('qod', qod) return tostring(_detection).decode('utf-8') @property def is_running_as_root(self) -> bool: """ Check if it is running as root user.""" if self._is_running_as_root is not None: return self._is_running_as_root self._is_running_as_root = False if geteuid() == 0: self._is_running_as_root = True return self._is_running_as_root @property def sudo_available(self) -> bool: """ Checks that sudo is available """ if self._sudo_available is not None: return self._sudo_available if self.is_running_as_root: self._sudo_available = False return self._sudo_available self._sudo_available = Openvas.check_sudo() return self._sudo_available def check(self) -> bool: """ Checks that openvas command line tool is found and is executable. """ has_openvas = Openvas.check() if not has_openvas: logger.error( 'openvas executable not available. Please install openvas' ' into your PATH.' ) return has_openvas def update_progress(self, scan_id: str, current_host: str, msg: str): """ Calculate percentage and update the scan status of a host for the progress bar. Arguments: scan_id: Scan ID to identify the current scan process. current_host: Host in the target to be updated. msg: String with launched and total plugins. """ try: launched, total = msg.split('/') except ValueError: return if float(total) == 0: return elif float(total) == -1: host_prog = 100 else: host_prog = (float(launched) / float(total)) * 100 self.set_scan_host_progress(scan_id, current_host, host_prog) def report_openvas_scan_status( self, scan_db: ScanDB, scan_id: str, current_host: str ): """ Get all status entries from redis kb. Arguments: scan_id: Scan ID to identify the current scan. current_host: Host to be updated. """ res = scan_db.get_scan_status() while res: self.update_progress(scan_id, current_host, res) res = scan_db.get_scan_status() def get_severity_score(self, vt_aux: dict) -> Optional[float]: """ Return the severity score for the given oid. Arguments: vt_aux: VT element from which to get the severity vector Returns: The calculated cvss base value. None if there is no severity vector or severity type is not cvss base version 2. """ if vt_aux: severity_type = vt_aux['severities'].get('severity_type') severity_vector = vt_aux['severities'].get('severity_base_vector') if severity_type == "cvss_base_v2" and severity_vector: return CVSS.cvss_base_v2_value(severity_vector) return None def report_openvas_results( self, db: BaseDB, scan_id: str, current_host: str ): """ Get all result entries from redis kb. """ res = db.get_result() while res: msg = res.split('|||') roid = msg[3].strip() rqod = '' rname = '' rhostname = msg[1].strip() if msg[1] else '' host_is_dead = "Host dead" in msg[4] vt_aux = None if roid and not host_is_dead: vt_aux = copy.deepcopy(self.vts.get(roid)) if not vt_aux and not host_is_dead: logger.warning('Invalid VT oid %s for a result', roid) if vt_aux: if vt_aux.get('qod_type'): qod_t = vt_aux.get('qod_type') rqod = self.nvti.QOD_TYPES[qod_t] elif vt_aux.get('qod'): rqod = vt_aux.get('qod') rname = vt_aux.get('name') if msg[0] == 'ERRMSG': self.add_scan_error( scan_id, host=current_host, hostname=rhostname, name=rname, value=msg[4], port=msg[2], test_id=roid, ) if msg[0] == 'LOG': self.add_scan_log( scan_id, host=current_host, hostname=rhostname, name=rname, value=msg[4], port=msg[2], qod=rqod, test_id=roid, ) if msg[0] == 'HOST_DETAIL': self.add_scan_host_detail( scan_id, host=current_host, hostname=rhostname, name=rname, value=msg[4], ) if msg[0] == 'ALARM': rseverity = self.get_severity_score(vt_aux) self.add_scan_alarm( scan_id, host=current_host, hostname=rhostname, name=rname, value=msg[4], port=msg[2], test_id=roid, severity=rseverity, qod=rqod, ) vt_aux = None del vt_aux res = db.get_result() def report_openvas_timestamp_scan_host( self, scan_db: ScanDB, scan_id: str, host: str ): """ Get start and end timestamp of a host scan from redis kb. """ timestamp = scan_db.get_host_scan_end_time() if timestamp: self.add_scan_log( scan_id, host=host, name='HOST_END', value=timestamp ) return timestamp = scan_db.get_host_scan_start_time() if timestamp: self.add_scan_log( scan_id, host=host, name='HOST_START', value=timestamp ) return def stop_scan_cleanup( # pylint: disable=arguments-differ self, global_scan_id: str ): """ Set a key in redis to indicate the wrapper is stopped. It is done through redis because it is a new multiprocess instance and it is not possible to reach the variables of the grandchild process. Send SIGUSR2 to openvas to stop each running scan.""" openvas_scan_id, kbdb = self.main_db.find_kb_database_by_scan_id( global_scan_id ) if kbdb: kbdb.stop_scan(openvas_scan_id) ovas_pid = kbdb.get_scan_process_id() parent = None try: parent = psutil.Process(int(ovas_pid)) except psutil.NoSuchProcess: logger.debug('Process with pid %s already stopped', ovas_pid) except TypeError: logger.debug( 'Scan with ID %s never started and stopped unexpectedly', openvas_scan_id, ) if parent: can_stop_scan = Openvas.stop_scan( openvas_scan_id, not self.is_running_as_root and self.sudo_available, ) if not can_stop_scan: logger.debug( 'Not possible to stop scan process: %s.', parent, ) return False logger.debug('Stopping process: %s', parent) while parent: try: parent = psutil.Process(int(ovas_pid)) except psutil.NoSuchProcess: parent = None for scan_db in kbdb.get_scan_databases(): self.main_db.release_database(scan_db) def exec_scan(self, scan_id: str): """ Starts the OpenVAS scanner for scan_id scan. """ if self.pending_feed: logger.info( '%s: There is a pending feed update. ' 'The scan can not be started.', scan_id, ) self.add_scan_error( scan_id, name='', host='', value=( 'It was not possible to start the scan,' 'because a pending feed update. Please try later' ), ) return 2 do_not_launch = False # Set plugins to run. # Make a deepcopy of the vts dictionary. Otherwise, consulting the # DictProxy object of multiprocessing directly is to expensinve # (interprocess communication). temp_vts = self.vts.copy() kbdb = self.main_db.get_new_kb_database() scan_prefs = PreferenceHandler( scan_id, kbdb, self.scan_collection, temp_vts ) openvas_scan_id = scan_prefs.prepare_openvas_scan_id_for_openvas() scan_prefs.prepare_target_for_openvas() if not scan_prefs.prepare_ports_for_openvas(): self.add_scan_error( scan_id, name='', host='', value='No port list defined.' ) do_not_launch = True # Set credentials if not scan_prefs.prepare_credentials_for_openvas(): self.add_scan_error( scan_id, name='', host='', value='Malformed credential.' ) do_not_launch = True if not scan_prefs.prepare_plugins_for_openvas(): self.add_scan_error( scan_id, name='', host='', value='No VTS to run.' ) do_not_launch = True temp_vts = None scan_prefs.prepare_main_kbindex_for_openvas() scan_prefs.prepare_host_options_for_openvas() scan_prefs.prepare_scan_params_for_openvas(OSPD_PARAMS) scan_prefs.prepare_reverse_lookup_opt_for_openvas() scan_prefs.prepare_alive_test_option_for_openvas() # Release memory used for scan preferences. del scan_prefs if do_not_launch: self.main_db.release_database(kbdb) return 2 result = Openvas.start_scan( openvas_scan_id, not self.is_running_as_root and self.sudo_available, self._niceness, ) if result is None: self.main_db.release_database(kbdb) return False ovas_pid = result.pid kbdb.add_scan_process_id(ovas_pid) logger.debug('pid = %s', ovas_pid) # Wait until the scanner starts and loads all the preferences. while kbdb.get_status(openvas_scan_id) == 'new': res = result.poll() if res and res < 0: self.stop_scan_cleanup(scan_id) logger.error( 'It was not possible run the task %s, since openvas ended ' 'unexpectedly with errors during launching.', scan_id, ) return 1 time.sleep(1) no_id_found = False while True: time.sleep(3) # Check if the client stopped the whole scan if kbdb.scan_is_stopped(openvas_scan_id): return 1 self.report_openvas_results(kbdb, scan_id, "") for scan_db in kbdb.get_scan_databases(): id_aux = scan_db.get_scan_id() if not id_aux: continue if id_aux == openvas_scan_id: no_id_found = False current_host = scan_db.get_host_ip() self.report_openvas_results(scan_db, scan_id, current_host) self.report_openvas_scan_status( scan_db, scan_id, current_host ) self.report_openvas_timestamp_scan_host( scan_db, scan_id, current_host ) if scan_db.host_is_finished(openvas_scan_id): self.set_scan_host_finished(scan_id, current_host) self.report_openvas_scan_status( scan_db, scan_id, current_host ) self.report_openvas_timestamp_scan_host( scan_db, scan_id, current_host ) kbdb.remove_scan_database(scan_db) self.main_db.release_database(scan_db) # Scan end. No kb in use for this scan id if no_id_found and kbdb.target_is_finished(scan_id): break no_id_found = True # Delete keys from KB related to this scan task. self.main_db.release_database(kbdb)
def setUp(self, MockMainDB): # pylint: disable=arguments-differ self.db = MockMainDB() self.nvti = NVTICache(self.db) self.nvti._ctx = 'foo'
class TestNVTICache(TestCase): @patch('ospd_openvas.db.MainDB') def setUp(self, MockMainDB): # pylint: disable=arguments-differ self.db = MockMainDB() self.nvti = NVTICache(self.db) self.nvti._ctx = 'foo' def test_set_index(self, MockOpenvasDB): self.nvti._ctx = None MockOpenvasDB.find_database_by_pattern.return_value = ('foo', 22) ctx = self.nvti.ctx self.assertIsNotNone(ctx) self.assertEqual(ctx, 'foo') self.assertEqual(self.nvti.index, 22) def test_get_feed_version(self, MockOpenvasDB): MockOpenvasDB.get_single_item.return_value = '1234' resp = self.nvti.get_feed_version() self.assertEqual(resp, '1234') MockOpenvasDB.get_single_item.assert_called_with( 'foo', NVTI_CACHE_NAME) def test_get_feed_version_not_available(self, MockOpenvasDB): pmock = PropertyMock(return_value=123) type(self.db).max_database_index = pmock self.nvti._ctx = None MockOpenvasDB.find_database_by_pattern.return_value = (None, None) resp = self.nvti.get_feed_version() self.assertIsNone(resp) MockOpenvasDB.find_database_by_pattern.assert_called_with( NVTI_CACHE_NAME, 123) def test_get_oids(self, MockOpenvasDB): MockOpenvasDB.get_filenames_and_oids.return_value = ['oids'] resp = self.nvti.get_oids() self.assertEqual(resp, ['oids']) def test_parse_metadata_tag_missing_value(self, MockOpenvasDB): logging.Logger.error = Mock() tags = 'tag1' ret = NVTICache._parse_metadata_tags( # pylint: disable=protected-access tags, '1.2.3') self.assertEqual(ret, {}) assert_called(logging.Logger.error) def test_parse_metadata_tag(self, MockOpenvasDB): tags = 'tag1=value1' ret = NVTICache._parse_metadata_tags( # pylint: disable=protected-access tags, '1.2.3') self.assertEqual(ret, {'tag1': 'value1'}) def test_parse_metadata_tags(self, MockOpenvasDB): tags = 'tag1=value1|foo=bar' ret = NVTICache._parse_metadata_tags( # pylint: disable=protected-access tags, '1.2.3') self.assertEqual(ret, {'tag1': 'value1', 'foo': 'bar'}) def test_get_nvt_params(self, MockOpenvasDB): prefs1 = ['1|||dns-fuzz.timelimit|||entry|||default'] prefs2 = ['1|||dns-fuzz.timelimit|||entry|||'] prefs3 = ['1|||dns-fuzz.timelimit|||entry'] out_dict1 = { '1': { 'id': '1', 'type': 'entry', 'default': 'default', 'name': 'dns-fuzz.timelimit', 'description': 'Description', }, } out_dict2 = { '1': { 'id': '1', 'type': 'entry', 'default': '', 'name': 'dns-fuzz.timelimit', 'description': 'Description', }, } MockOpenvasDB.get_list_item.return_value = prefs1 resp = self.nvti.get_nvt_params('1.2.3.4') self.assertEqual(resp, out_dict1) MockOpenvasDB.get_list_item.return_value = prefs2 resp = self.nvti.get_nvt_params('1.2.3.4') self.assertEqual(resp, out_dict2) MockOpenvasDB.get_list_item.return_value = prefs3 resp = self.nvti.get_nvt_params('1.2.3.4') self.assertEqual(resp, out_dict2) def test_get_nvt_metadata(self, MockOpenvasDB): metadata = [ 'mantis_detect.nasl', '', '', 'Settings/disable_cgi_scanning', '', 'Services/www, 80', 'find_service.nasl, http_version.nasl', 'cvss_base_vector=AV:N/AC:L/Au:N/C:N/I:N' '/A:N|last_modification=1533906565' '|creation_date=1237458156' '|summary=Detects the ins' 'talled version of\n Mantis a free popular web-based ' 'bugtracking system.\n\n This script sends HTTP GET r' 'equest and try to get the version from the\n respons' 'e, and sets the result in KB.|qod_type=remote_banner', '', '', 'URL:http://www.mantisbt.org/', '3', '10', 'Product detection', 'Mantis Detection', ] custom = { 'category': '3', 'creation_date': '1237458156', 'cvss_base_vector': 'AV:N/AC:L/Au:N/C:N/I:N/A:N', 'dependencies': 'find_service.nasl, http_version.nasl', 'excluded_keys': 'Settings/disable_cgi_scanning', 'family': 'Product detection', 'filename': 'mantis_detect.nasl', 'last_modification': ('1533906565'), 'name': 'Mantis Detection', 'qod_type': 'remote_banner', 'refs': { 'xref': ['URL:http://www.mantisbt.org/'] }, 'required_ports': 'Services/www, 80', 'summary': ('Detects the installed version of\n Mantis a ' 'free popular web-based bugtracking system.\n' '\n This script sends HTTP GET request and t' 'ry to get the version from the\n response, ' 'and sets the result in KB.'), 'vt_params': { '0': { 'id': '0', 'type': 'entry', 'name': 'timeout', 'description': 'Script Timeout', 'default': '10', }, '1': { 'id': '1', 'type': 'entry', 'name': 'dns-fuzz.timelimit', 'description': 'Description', 'default': 'default', }, }, } prefs1 = ['1|||dns-fuzz.timelimit|||entry|||default'] MockOpenvasDB.get_list_item.side_effect = [metadata, prefs1] resp = self.nvti.get_nvt_metadata('1.2.3.4') self.maxDiff = None self.assertEqual(resp, custom) def test_get_nvt_metadata_fail(self, MockOpenvasDB): MockOpenvasDB.get_list_item.return_value = [] resp = self.nvti.get_nvt_metadata('1.2.3.4') self.assertIsNone(resp) def test_get_nvt_refs(self, MockOpenvasDB): refs = ['', '', 'URL:http://www.mantisbt.org/'] out_dict = { 'cve': [''], 'bid': [''], 'xref': ['URL:http://www.mantisbt.org/'], } MockOpenvasDB.get_list_item.return_value = refs resp = self.nvti.get_nvt_refs('1.2.3.4') self.assertEqual(resp, out_dict) def test_get_nvt_refs_fail(self, MockOpenvasDB): MockOpenvasDB.get_list_item.return_value = [] resp = self.nvti.get_nvt_refs('1.2.3.4') self.assertIsNone(resp) def test_get_nvt_prefs(self, MockOpenvasDB): prefs = ['dns-fuzz.timelimit|||entry|||default'] MockOpenvasDB.get_list_item.return_value = prefs resp = self.nvti.get_nvt_prefs('1.2.3.4') self.assertEqual(resp, prefs) def test_get_nvt_timeout(self, MockOpenvasDB): MockOpenvasDB.get_single_item.return_value = '300' resp = self.nvti.get_nvt_timeout('1.2.3.4') self.assertEqual(resp, '300') def test_get_nvt_tags(self, MockOpenvasDB): tag = ('last_modification=1533906565' '|creation_date=1517443741|cvss_bas' 'e_vector=AV:N/AC:L/Au:N/C:P/I:P/A:P|solution_type=V' 'endorFix|qod_type=package|affected=rubygems on Debi' 'an Linux|solution_method=DebianAPTUpgrade') out_dict = { 'last_modification': '1533906565', 'creation_date': '1517443741', 'cvss_base_vector': 'AV:N/AC:L/Au:N/C:P/I:P/A:P', 'solution_type': 'VendorFix', 'qod_type': 'package', 'affected': 'rubygems on Debian Linux', 'solution_method': 'DebianAPTUpgrade', } MockOpenvasDB.get_single_item.return_value = tag resp = self.nvti.get_nvt_tags('1.2.3.4') self.assertEqual(out_dict, resp) def test_get_nvt_files_count(self, MockOpenvasDB): MockOpenvasDB.get_key_count.return_value = 20 self.assertEqual(self.nvti.get_nvt_files_count(), 20) MockOpenvasDB.get_key_count.assert_called_with('foo', 'filename:*') def test_get_nvt_count(self, MockOpenvasDB): MockOpenvasDB.get_key_count.return_value = 20 self.assertEqual(self.nvti.get_nvt_count(), 20) MockOpenvasDB.get_key_count.assert_called_with('foo', 'nvt:*') def test_force_reload(self, _MockOpenvasDB): self.nvti.force_reload() self.db.release_database.assert_called_with(self.nvti) def test_flush(self, _MockOpenvasDB): self.nvti._ctx = Mock() self.nvti.flush() self.nvti._ctx.flushdb.assert_called_with()
class TestNVTICache(TestCase): def setUp(self): self.db = OpenvasDB() self.nvti = NVTICache(self.db) def test_get_feed_version(self, mock_redis): with patch.object(OpenvasDB, 'db_find', return_value=mock_redis): with patch.object(OpenvasDB, 'get_single_item', return_value='1234'): resp = self.nvti.get_feed_version() self.assertEqual(resp, '1234') def test_get_oids(self, mock_redis): with patch.object(OpenvasDB, 'get_elem_pattern_by_index', return_value=['oids']): resp = self.nvti.get_oids() self.assertEqual(resp, ['oids']) def test_parse_metadata_tags(self, mock_redis): tags = 'tag1' with patch.object(logger, 'error', return_value=None) as log: ret = self.nvti._parse_metadata_tags(tags, '1.2.3') log.assert_called_with('Tag tag1 in 1.2.3 has no value.') self.assertEqual(ret, {}) def test_get_nvt_params(self, mock_redis): prefs = ['dns-fuzz.timelimit|||entry|||default'] prefs1 = ['dns-fuzz.timelimit|||entry|||'] timeout = '300' out_dict = { 'dns-fuzz.timelimit': { 'type': 'entry', 'default': 'default', 'name': 'dns-fuzz.timelimit', 'description': 'Description' }, 'timeout': { 'type': 'entry', 'default': '300', 'name': 'timeout', 'description': 'Script Timeout' } } out_dict1 = { 'dns-fuzz.timelimit': { 'type': 'entry', 'default': '', 'name': 'dns-fuzz.timelimit', 'description': 'Description' }, 'timeout': { 'type': 'entry', 'default': '300', 'name': 'timeout', 'description': 'Script Timeout' } } with patch.object(OpenvasDB, 'get_kb_context', return_value=mock_redis): with patch.object(NVTICache, 'get_nvt_timeout', return_value=timeout): with patch.object(NVTICache, 'get_nvt_prefs', return_value=prefs): resp = self.nvti.get_nvt_params('1.2.3.4') with patch.object(NVTICache, 'get_nvt_prefs', return_value=prefs1): resp1 = self.nvti.get_nvt_params('1.2.3.4') self.assertEqual(resp, out_dict) self.assertEqual(resp1, out_dict1) @patch('ospd_openvas.db.subprocess') def test_get_nvt_metadata(self, mock_subps, mock_redis): metadata = [ 'mantis_detect.nasl', '', '', 'Settings/disable_cgi_scanning', '', 'Services/www, 80', 'find_service.nasl, http_version.nasl', 'cvss_base=0.0|cvss_base_vector=AV:N/AC:L/Au:N/C:N/I:N' '/A:N|last_modification=$Date: 2018-08-10 15:09:25 +02' '00 (Fri, 10 Aug 2018) $|creation_date=2009-03-19 11:2' '2:36 +0100 (Thu, 19 Mar 2009)|summary=Detects the ins' 'talled version of\n Mantis a free popular web-based ' 'bugtracking system.\n\n This script sends HTTP GET r' 'equest and try to get the version from the\n respons' 'e, and sets the result in KB.|qod_type=remote_banner', '', '', 'URL:http://www.mantisbt.org/', '3', '0', 'Product detection', 'Mantis Detection', ] custom = { 'category': '3', 'creation_date': '2009-03-19 11:22:36 +0100 (Thu, 19 Mar 2009)', 'cvss_base_vector': 'AV:N/AC:L/Au:N/C:N/I:N/A:N', 'dependencies': 'find_service.nasl, http_version.nasl', 'excluded_keys': 'Settings/disable_cgi_scanning', 'family': 'Product detection', 'filename': 'mantis_detect.nasl', 'last_modification': ('$Date: 2018-08-10 15:09:25 +0200 ' '(Fri, 10 Aug 2018) $'), 'name': 'Mantis Detection', 'qod_type': 'remote_banner', 'required_ports': 'Services/www, 80', 'summary': ('Detects the installed version of\n Mantis a ' 'free popular web-based bugtracking system.\n' '\n This script sends HTTP GET request and t' 'ry to get the version from the\n response, ' 'and sets the result in KB.'), 'timeout': '0' } mock_subps.check_output.return_value = ( 'use_mac_addr = no\ndb_address = ' '/tmp/redis.sock\ndrop_privileges = no').encode() mock_redis.return_value = mock_redis mock_redis.config_get.return_value = {'databases': '513'} mock_redis.lrange.return_value = metadata mock_redis.keys.return_value = 1 self.db.db_init() resp = self.nvti.get_nvt_metadata('1.2.3.4') self.assertEqual(resp, custom) @patch('ospd_openvas.db.subprocess') def test_get_nvt_metadata_fail(self, mock_subps, mock_redis): mock_subps.check_output.return_value = \ 'use_mac_addr = no\ndb_address = ' \ '/tmp/redis.sock\ndrop_privileges = no'.encode() mock_redis.return_value = mock_redis mock_redis.config_get.return_value = {'databases': '513'} mock_redis.lrange.return_value = {} mock_redis.keys.return_value = 1 self.db.db_init() resp = self.nvti.get_nvt_metadata('1.2.3.4') self.assertEqual(resp, None) @patch('ospd_openvas.db.subprocess') def test_get_nvt_refs(self, mock_subps, mock_redis): refs = ['', '', 'URL:http://www.mantisbt.org/'] out_dict = { 'cve': [''], 'bid': [''], 'xref': ['URL:http://www.mantisbt.org/'], } mock_subps.check_output.return_value = ( 'use_mac_addr = no\ndb_address = ' '/tmp/redis.sock\ndrop_privileges = no').encode() mock_redis.return_value = mock_redis mock_redis.config_get.return_value = {'databases': '513'} mock_redis.lrange.return_value = refs mock_redis.keys.return_value = 1 self.db.db_init() resp = self.nvti.get_nvt_refs('1.2.3.4') self.assertEqual(resp, out_dict) @patch('ospd_openvas.db.subprocess') def test_get_nvt_refs_fail(self, mock_subps, mock_redis): mock_subps.check_output.return_value = \ 'use_mac_addr = no\ndb_address = ' \ '/tmp/redis.sock\ndrop_privileges = no'.encode() mock_redis.return_value = mock_redis mock_redis.config_get.return_value = {'databases': '513'} mock_redis.lrange.return_value = {} mock_redis.keys.return_value = 1 self.db.db_init() resp = self.nvti.get_nvt_refs('1.2.3.4') self.assertEqual(resp, None) def test_get_nvt_prefs(self, mock_redis): prefs = ['dns-fuzz.timelimit|||entry|||default'] mock_redis.lrange.return_value = prefs mock_redis.return_value = mock_redis resp = self.nvti.get_nvt_prefs(mock_redis(), '1.2.3.4') self.assertEqual(resp, prefs) def test_get_nvt_timeout(self, mock_redis): mock_redis.lindex.return_value = '300' mock_redis.return_value = mock_redis resp = self.nvti.get_nvt_timeout(mock_redis(), '1.2.3.4') self.assertEqual(resp, '300') def test_get_nvt_tag(self, mock_redis): tag = 'last_modification=$Date: 2018-07-10 10:12:26 +0200 ' \ '(Tue, 10 Jul 2018) $|creation_date=2018-04-02 00:00' \ ':00 +0200 (Mon, 02 Apr 2018)|cvss_base=7.5|cvss_bas' \ 'e_vector=AV:N/AC:L/Au:N/C:P/I:P/A:P|solution_type=V' \ 'endorFix|qod_type=package|affected=rubygems on Debi' \ 'an Linux' out_dict = { 'last_modification': \ '$Date: 2018-07-10 10:12:26 +0200 (Tue, 10 Jul 2018) $', 'creation_date': '2018-04-02 00:00:00 +0200 (Mon, 02 Apr 2018)', 'cvss_base_vector': 'AV:N/AC:L/Au:N/C:P/I:P/A:P', 'solution_type': 'VendorFix', 'qod_type': 'package', 'cvss_base': '7.5', 'affected': 'rubygems on Debian Linux'} mock_redis.lindex.return_value = tag mock_redis.return_value = mock_redis resp = self.nvti.get_nvt_tag(mock_redis(), '1.2.3.4') self.assertEqual(out_dict, resp)
class OSPDopenvas(OSPDaemon): """Class for ospd-openvas daemon.""" def __init__( self, *, niceness=None, lock_file_dir='/var/lib/openvas', mqtt_broker_address="localhost", mqtt_broker_port=1883, disable_notus_hashsum_verification=False, **kwargs, ): """Initializes the ospd-openvas daemon's internal data.""" self.main_db = MainDB() notus_dir = kwargs.get('notus_feed_dir') notus = None if notus_dir: ndir = Path(notus_dir) verifier = hashsum_verificator(ndir, disable_notus_hashsum_verification) notus = Notus(ndir, self.main_db.ctx, verifier) self.nvti = NVTICache( self.main_db, notus, ) super().__init__( customvtfilter=OpenVasVtsFilter(self.nvti), storage=dict, file_storage_dir=lock_file_dir, **kwargs, ) self.server_version = __version__ self._niceness = str(niceness) self.feed_lock = LockFile(Path(lock_file_dir) / 'feed-update.lock') self.daemon_info['name'] = 'OSPd OpenVAS' self.scanner_info['name'] = 'openvas' self.scanner_info['version'] = '' # achieved during self.init() self.scanner_info['description'] = OSPD_DESC for name, param in OSPD_PARAMS.items(): self.set_scanner_param(name, param) self._sudo_available = None self._is_running_as_root = None self.scan_only_params = dict() self._mqtt_broker_address = mqtt_broker_address self._mqtt_broker_port = mqtt_broker_port def init(self, server: BaseServer) -> None: notus_handler = NotusResultHandler(self.report_results) if self._mqtt_broker_address: try: client = MQTTClient(self._mqtt_broker_address, self._mqtt_broker_port, "ospd") daemon = MQTTDaemon(client) subscriber = MQTTSubscriber(client) subscriber.subscribe(ResultMessage, notus_handler.result_handler) daemon.run() except (ConnectionRefusedError, gaierror, ValueError) as e: logger.error( "Could not connect to MQTT broker at %s, error was: %s." " Unable to get results from Notus.", self._mqtt_broker_address, e, ) else: logger.info( "MQTT Broker Adress empty. MQTT disabled. Unable to get Notus" " results.") self.scan_collection.init() server.start(self.handle_client_stream) self.scanner_info['version'] = Openvas.get_version() self.set_params_from_openvas_settings() with self.feed_lock.wait_for_lock(): Openvas.load_vts_into_redis() self.set_feed_info() logger.debug("Calculating vts integrity check hash...") vthelper = VtHelper(self.nvti) self.vts.sha256_hash = vthelper.calculate_vts_collection_hash() self.initialized = True def set_params_from_openvas_settings(self): """Set OSPD_PARAMS with the params taken from the openvas executable.""" param_list = Openvas.get_settings() for elem in param_list: # pylint: disable=consider-using-dict-items if elem not in OSPD_PARAMS: self.scan_only_params[elem] = param_list[elem] else: OSPD_PARAMS[elem]['default'] = param_list[elem] def feed_is_outdated(self, current_feed: str) -> Optional[bool]: """Compare the current feed with the one in the disk. Return: False if there is no new feed. True if the feed version in disk is newer than the feed in redis cache. None if there is no feed on the disk. """ current_feed = safe_int(current_feed) if current_feed is None: logger.debug( "Wrong PLUGIN_SET format in plugins feed file " "'plugin_feed_info.inc'. Format has to" " be yyyymmddhhmm. For example 'PLUGIN_SET = \"201910251033\"'" ) feed_date = None feed_info = self.get_feed_info() if feed_info: feed_date = safe_int(feed_info.get("PLUGIN_SET")) logger.debug("Current feed version: %s", current_feed) logger.debug("Plugin feed version: %s", feed_date) return ((not feed_date) or (not current_feed) or (current_feed < feed_date)) def get_feed_info(self) -> Dict[str, Any]: """Parses the current plugin_feed_info.inc file""" plugins_folder = self.scan_only_params.get('plugins_folder') if not plugins_folder: raise OspdOpenvasError("Error: Path to plugins folder not found.") feed_info_file = Path(plugins_folder) / 'plugin_feed_info.inc' if not feed_info_file.exists(): self.set_params_from_openvas_settings() logger.debug('Plugins feed file %s not found.', feed_info_file) return {} feed_info = {} with feed_info_file.open(encoding='utf-8') as fcontent: for line in fcontent: try: key, value = line.split('=', 1) except ValueError: continue key = key.strip() value = value.strip() value = value.replace(';', '') value = value.replace('"', '') if value: feed_info[key] = value return feed_info def set_feed_info(self): """Set feed current information to be included in the response of <get_version/> command """ current_feed = self.nvti.get_feed_version() self.set_vts_version(vts_version=current_feed) feed_info = self.get_feed_info() self.set_feed_vendor(feed_info.get("FEED_VENDOR", "unknown")) self.set_feed_home(feed_info.get("FEED_HOME", "unknown")) self.set_feed_name(feed_info.get("PLUGIN_FEED", "unknown")) def check_feed_self_test(self) -> Dict: """Perform a feed sync self tests and check if the feed lock file is locked. """ feed_status = dict() # It is locked by the current process if self.feed_lock.has_lock(): feed_status["lockfile_in_use"] = '1' # Check if we can get the lock else: with self.feed_lock as fl: # It is available if fl.has_lock(): feed_status["lockfile_in_use"] = '0' # Locked by another process else: feed_status["lockfile_in_use"] = '1' feed = Feed() _exit_error, _error_msg = feed.perform_feed_sync_self_test_success() feed_status["self_test_exit_error"] = str(_exit_error) feed_status["self_test_error_msg"] = _error_msg return feed_status def check_feed(self): """Check if there is a feed update. Wait until all the running scans finished. Set a flag to announce there is a pending feed update, which avoids to start a new scan. """ if not self.vts.is_cache_available: return current_feed = self.nvti.get_feed_version() is_outdated = self.feed_is_outdated(current_feed) # Check if the nvticache in redis is outdated if not current_feed or is_outdated: with self.feed_lock as fl: if fl.has_lock(): self.initialized = False Openvas.load_vts_into_redis() self.set_feed_info() vthelper = VtHelper(self.nvti) self.vts.sha256_hash = ( vthelper.calculate_vts_collection_hash()) self.initialized = True else: logger.debug("The feed was not upload or it is outdated, " "but other process is locking the update. " "Trying again later...") return def scheduler(self): """This method is called periodically to run tasks.""" self.check_feed() def get_vt_iterator(self, vt_selection: List[str] = None, details: bool = True) -> Iterator[Tuple[str, Dict]]: vthelper = VtHelper(self.nvti) return vthelper.get_vt_iterator(vt_selection, details) @property def is_running_as_root(self) -> bool: """Check if it is running as root user.""" if self._is_running_as_root is not None: return self._is_running_as_root self._is_running_as_root = False if geteuid() == 0: self._is_running_as_root = True return self._is_running_as_root @property def sudo_available(self) -> bool: """Checks that sudo is available""" if self._sudo_available is not None: return self._sudo_available if self.is_running_as_root: self._sudo_available = False return self._sudo_available self._sudo_available = Openvas.check_sudo() return self._sudo_available def check(self) -> bool: """Checks that openvas command line tool is found and is executable.""" has_openvas = Openvas.check() if not has_openvas: logger.error( 'openvas executable not available. Please install openvas' ' into your PATH.') return has_openvas def report_openvas_scan_status(self, kbdb: BaseDB, scan_id: str): """Get all status entries from redis kb. Arguments: kbdb: KB context where to get the status from. scan_id: Scan ID to identify the current scan. """ all_status = kbdb.get_scan_status() all_hosts = dict() finished_hosts = list() for res in all_status: try: current_host, launched, total = res.split('/') except ValueError: continue try: if float(total) == 0: continue elif float(total) == ScanProgress.DEAD_HOST: host_prog = ScanProgress.DEAD_HOST else: host_prog = int((float(launched) / float(total)) * 100) except TypeError: continue all_hosts[current_host] = host_prog if (host_prog == ScanProgress.DEAD_HOST or host_prog == ScanProgress.FINISHED): finished_hosts.append(current_host) logger.debug('%s: Host %s has progress: %d', scan_id, current_host, host_prog) self.set_scan_progress_batch(scan_id, host_progress=all_hosts) self.sort_host_finished(scan_id, finished_hosts) def report_openvas_results(self, db: BaseDB, scan_id: str) -> bool: """Get all result entries from redis kb. Arguments: db: KB context where to get the results from. scan_id: Scan ID to identify the current scan. """ # result_type|||host ip|||hostname|||port|||OID|||value[|||uri] all_results = db.get_result() results = [] for res in all_results: if not res: continue msg = res.split('|||') result = { "result_type": msg[0], "host_ip": msg[1], "host_name": msg[2], "port": msg[3], "oid": msg[4], "value": msg[5], } if len(msg) > 6: result["uri"] = msg[6] results.append(result) return self.report_results(results, scan_id) def report_results(self, results: list, scan_id: str) -> bool: """Reports all results given in a list. Arguments: results: list of results each list item must contain a dictionary with following fields: result_type, host_ip, host_name, port, oid, value, uri (optional) """ vthelper = VtHelper(self.nvti) res_list = ResultList() total_dead = 0 for res in results: if not res: continue roid = res["oid"].strip() rqod = '' rname = '' current_host = res["host_ip"].strip() if res["host_ip"] else '' rhostname = res["host_name"].strip() if res["host_name"] else '' host_is_dead = ("Host dead" in res["value"] or res["result_type"] == "DEADHOST") host_deny = "Host access denied" in res["value"] start_end_msg = (res["result_type"] == "HOST_START" or res["result_type"] == "HOST_END") host_count = res["result_type"] == "HOSTS_COUNT" vt_aux = None # URI is optional and containing must be checked ruri = res["uri"] if "uri" in res else "" if (not host_is_dead and not host_deny and not start_end_msg and not host_count): if not roid and res["result_type"] != 'ERRMSG': logger.warning('Missing VT oid for a result') vt_aux = vthelper.get_single_vt(roid) if not vt_aux: logger.warning('Invalid VT oid %s for a result', roid) else: if vt_aux.get('qod_type'): qod_t = vt_aux.get('qod_type') rqod = self.nvti.QOD_TYPES[qod_t] elif vt_aux.get('qod'): rqod = vt_aux.get('qod') rname = vt_aux.get('name') if res["result_type"] == 'ERRMSG': res_list.add_scan_error_to_list( host=current_host, hostname=rhostname, name=rname, value=res["value"], port=res["port"], test_id=roid, uri=ruri, ) elif (res["result_type"] == 'HOST_START' or res["result_type"] == 'HOST_END'): res_list.add_scan_log_to_list( host=current_host, name=res["result_type"], value=res["value"], ) elif res["result_type"] == 'LOG': res_list.add_scan_log_to_list( host=current_host, hostname=rhostname, name=rname, value=res["value"], port=res["port"], qod=rqod, test_id=roid, uri=ruri, ) elif res["result_type"] == 'HOST_DETAIL': res_list.add_scan_host_detail_to_list( host=current_host, hostname=rhostname, name=rname, value=res["value"], uri=ruri, ) elif res["result_type"] == 'ALARM': rseverity = vthelper.get_severity_score(vt_aux) res_list.add_scan_alarm_to_list( host=current_host, hostname=rhostname, name=rname, value=res["value"], port=res["port"], test_id=roid, severity=rseverity, qod=rqod, uri=ruri, ) # To process non-scanned dead hosts when # test_alive_host_only in openvas is enable elif res["result_type"] == 'DEADHOST': try: total_dead = total_dead + int(res["value"]) except TypeError: logger.debug('Error processing dead host count') # To update total host count if res["result_type"] == 'HOSTS_COUNT': try: count_total = int(res["value"]) logger.debug( '%s: Set total hosts counted by OpenVAS: %d', scan_id, count_total, ) self.set_scan_total_hosts(scan_id, count_total) except TypeError: logger.debug('Error processing total host count') # Insert result batch into the scan collection table. if len(res_list): self.scan_collection.add_result_list(scan_id, res_list) logger.debug( '%s: Inserting %d results into scan collection table', scan_id, len(res_list), ) if total_dead: logger.debug( '%s: Set dead hosts counted by OpenVAS: %d', scan_id, total_dead, ) self.scan_collection.set_amount_dead_hosts(scan_id, total_dead=total_dead) return len(res_list) > 0 @staticmethod def is_openvas_process_alive(openvas_process: psutil.Popen) -> bool: try: if openvas_process.status() == psutil.STATUS_ZOMBIE: logger.debug("Process is a Zombie, waiting for it to clean up") openvas_process.wait() except psutil.NoSuchProcess: return False return openvas_process.is_running() def stop_scan_cleanup( self, kbdb: BaseDB, scan_id: str, ovas_process: psutil.Popen, # pylint: disable=arguments-differ ): """Set a key in redis to indicate the wrapper is stopped. It is done through redis because it is a new multiprocess instance and it is not possible to reach the variables of the grandchild process. Indirectly sends SIGUSR1 to the running openvas scan process via an invocation of openvas with the --scan-stop option to stop it.""" if kbdb: # Set stop flag in redis kbdb.stop_scan(scan_id) # Check if openvas is running if ovas_process.is_running(): # Cleaning in case of Zombie Process if ovas_process.status() == psutil.STATUS_ZOMBIE: logger.debug( '%s: Process with PID %s is a Zombie process.' ' Cleaning up...', scan_id, ovas_process.pid, ) ovas_process.wait() # Stop openvas process and wait until it stopped else: can_stop_scan = Openvas.stop_scan( scan_id, not self.is_running_as_root and self.sudo_available, ) if not can_stop_scan: logger.debug( 'Not possible to stop scan process: %s.', ovas_process, ) return logger.debug('Stopping process: %s', ovas_process) while ovas_process.is_running(): if ovas_process.status() == psutil.STATUS_ZOMBIE: ovas_process.wait() else: time.sleep(0.1) else: logger.debug( "%s: Process with PID %s already stopped", scan_id, ovas_process.pid, ) # Clean redis db for scan_db in kbdb.get_scan_databases(): self.main_db.release_database(scan_db) def exec_scan(self, scan_id: str): """Starts the OpenVAS scanner for scan_id scan.""" params = self.scan_collection.get_options(scan_id) if params.get("dry_run"): dryrun = DryRun(self) dryrun.exec_dry_run_scan(scan_id, self.nvti, OSPD_PARAMS) return do_not_launch = False kbdb = self.main_db.get_new_kb_database() scan_prefs = PreferenceHandler(scan_id, kbdb, self.scan_collection, self.nvti) kbdb.add_scan_id(scan_id) scan_prefs.prepare_target_for_openvas() if not scan_prefs.prepare_ports_for_openvas(): self.add_scan_error(scan_id, name='', host='', value='Invalid port list.') do_not_launch = True # Set credentials if not scan_prefs.prepare_credentials_for_openvas(): error = ('All authentifications contain errors.' + 'Starting unauthenticated scan instead.') self.add_scan_error( scan_id, name='', host='', value=error, ) logger.error(error) errors = scan_prefs.get_error_messages() for e in errors: error = 'Malformed credential. ' + e self.add_scan_error( scan_id, name='', host='', value=error, ) logger.error(error) if not scan_prefs.prepare_plugins_for_openvas(): self.add_scan_error(scan_id, name='', host='', value='No VTS to run.') do_not_launch = True scan_prefs.prepare_main_kbindex_for_openvas() scan_prefs.prepare_host_options_for_openvas() scan_prefs.prepare_scan_params_for_openvas(OSPD_PARAMS) scan_prefs.prepare_reverse_lookup_opt_for_openvas() scan_prefs.prepare_alive_test_option_for_openvas() # VT preferences are stored after all preferences have been processed, # since alive tests preferences have to be able to overwrite default # preferences of ping_host.nasl for the classic method. scan_prefs.prepare_nvt_preferences() scan_prefs.prepare_boreas_alive_test() # Release memory used for scan preferences. del scan_prefs scan_stopped = self.get_scan_status(scan_id) == ScanStatus.STOPPED if do_not_launch or kbdb.scan_is_stopped(scan_id) or scan_stopped: self.main_db.release_database(kbdb) return openvas_process = Openvas.start_scan( scan_id, not self.is_running_as_root and self.sudo_available, self._niceness, ) if openvas_process is None: self.main_db.release_database(kbdb) return kbdb.add_scan_process_id(openvas_process.pid) logger.debug('pid = %s', openvas_process.pid) # Wait until the scanner starts and loads all the preferences. while kbdb.get_status(scan_id) == 'new': res = openvas_process.poll() if res and res < 0: self.stop_scan_cleanup(kbdb, scan_id, openvas_process) logger.error( 'It was not possible run the task %s, since openvas ended ' 'unexpectedly with errors during launching.', scan_id, ) return time.sleep(1) got_results = False while True: openvas_process_is_alive = self.is_openvas_process_alive( openvas_process) target_is_finished = kbdb.target_is_finished(scan_id) scan_stopped = self.get_scan_status(scan_id) == ScanStatus.STOPPED # Report new Results and update status got_results = self.report_openvas_results(kbdb, scan_id) self.report_openvas_scan_status(kbdb, scan_id) # Check if the client stopped the whole scan if scan_stopped: logger.debug('%s: Scan stopped by the client', scan_id) self.stop_scan_cleanup(kbdb, scan_id, openvas_process) # clean main_db, but wait for scanner to finish. while not kbdb.target_is_finished(scan_id): if not self.is_openvas_process_alive(openvas_process): break logger.debug('%s: Waiting for openvas to finish', scan_id) time.sleep(1) self.main_db.release_database(kbdb) return # Scan end. No kb in use for this scan id if target_is_finished: logger.debug('%s: Target is finished', scan_id) break if not openvas_process_is_alive: logger.error( 'Task %s was unexpectedly stopped or killed.', scan_id, ) self.add_scan_error( scan_id, name='', host='', value='Task was unexpectedly stopped or killed.', ) # check for scanner error messages before leaving. self.report_openvas_results(kbdb, scan_id) kbdb.stop_scan(scan_id) for scan_db in kbdb.get_scan_databases(): self.main_db.release_database(scan_db) self.main_db.release_database(kbdb) return # Wait a second before trying to get result from redis if there # was no results before. # Otherwise, wait 50 msec to give access other process to redis. if not got_results: time.sleep(1) else: time.sleep(0.05) got_results = False # Sleep a second to be sure to get all notus results time.sleep(1) # Delete keys from KB related to this scan task. logger.debug('%s: End Target. Release main database', scan_id) self.main_db.release_database(kbdb)
def setUp(self): self.db = OpenvasDB() self.nvti = NVTICache(self.db)
class TestNVTICache(TestCase): def setUp(self): self.db = OpenvasDB() self.nvti = NVTICache(self.db) def test_get_feed_version(self, mock_redis): self.nvti._nvti_cache_name = '20.4' with patch.object(OpenvasDB, 'db_find', return_value=mock_redis): with patch.object(OpenvasDB, 'get_single_item', return_value='1234'): resp = self.nvti.get_feed_version() self.assertEqual(resp, '1234') def test_get_oids(self, mock_redis): with patch.object(OpenvasDB, 'get_elem_pattern_by_index', return_value=['oids']): resp = self.nvti.get_oids() self.assertEqual(resp, ['oids']) def test_parse_metadata_tags(self, mock_redis): tags = 'tag1' ret = self.nvti._parse_metadata_tags( # pylint: disable=protected-access tags, '1.2.3') self.assertEqual(ret, {}) def test_get_nvt_params(self, mock_redis): prefs = ['1|||dns-fuzz.timelimit|||entry|||default'] prefs1 = ['1|||dns-fuzz.timelimit|||entry|||'] timeout = '300' out_dict = { '1': { 'id': '1', 'type': 'entry', 'default': 'default', 'name': 'dns-fuzz.timelimit', 'description': 'Description', }, '0': { 'type': 'entry', 'id': '0', 'default': '300', 'name': 'timeout', 'description': 'Script Timeout', }, } out_dict1 = { '1': { 'id': '1', 'type': 'entry', 'default': '', 'name': 'dns-fuzz.timelimit', 'description': 'Description', }, '0': { 'type': 'entry', 'id': '0', 'default': '300', 'name': 'timeout', 'description': 'Script Timeout', }, } with patch.object(OpenvasDB, 'get_kb_context', return_value=mock_redis): with patch.object(NVTICache, 'get_nvt_timeout', return_value=timeout): with patch.object(NVTICache, 'get_nvt_prefs', return_value=prefs): resp = self.nvti.get_nvt_params('1.2.3.4') with patch.object(NVTICache, 'get_nvt_prefs', return_value=prefs1): resp1 = self.nvti.get_nvt_params('1.2.3.4') self.assertEqual(resp, out_dict) self.assertEqual(resp1, out_dict1) @patch('ospd_openvas.db.subprocess') def test_get_nvt_metadata(self, mock_subps, mock_redis): metadata = [ 'mantis_detect.nasl', '', '', 'Settings/disable_cgi_scanning', '', 'Services/www, 80', 'find_service.nasl, http_version.nasl', 'cvss_base_vector=AV:N/AC:L/Au:N/C:N/I:N' '/A:N|last_modification=1533906565' '|creation_date=1237458156' '|summary=Detects the ins' 'talled version of\n Mantis a free popular web-based ' 'bugtracking system.\n\n This script sends HTTP GET r' 'equest and try to get the version from the\n respons' 'e, and sets the result in KB.|qod_type=remote_banner', '', '', 'URL:http://www.mantisbt.org/', '3', '0', 'Product detection', 'Mantis Detection', ] custom = { 'category': '3', 'creation_date': '1237458156', 'cvss_base_vector': 'AV:N/AC:L/Au:N/C:N/I:N/A:N', 'dependencies': 'find_service.nasl, http_version.nasl', 'excluded_keys': 'Settings/disable_cgi_scanning', 'family': 'Product detection', 'filename': 'mantis_detect.nasl', 'last_modification': ('1533906565'), 'name': 'Mantis Detection', 'qod_type': 'remote_banner', 'required_ports': 'Services/www, 80', 'summary': ('Detects the installed version of\n Mantis a ' 'free popular web-based bugtracking system.\n' '\n This script sends HTTP GET request and t' 'ry to get the version from the\n response, ' 'and sets the result in KB.'), 'timeout': '0', } mock_subps.check_output.return_value = ( 'use_mac_addr = no\ndb_address = ' '/tmp/redis.sock\ndrop_privileges = no').encode() mock_redis.return_value = mock_redis mock_redis.config_get.return_value = {'databases': '513'} mock_redis.lrange.return_value = metadata mock_redis.keys.return_value = 1 self.db.db_init() resp = self.nvti.get_nvt_metadata('1.2.3.4') self.assertEqual(resp, custom) @patch('ospd_openvas.db.subprocess') def test_get_nvt_metadata_fail(self, mock_subps, mock_redis): mock_subps.check_output.return_value = ( 'use_mac_addr = no\ndb_address = ' '/tmp/redis.sock\ndrop_privileges = no'.encode()) mock_redis.return_value = mock_redis mock_redis.config_get.return_value = {'databases': '513'} mock_redis.lrange.return_value = {} mock_redis.keys.return_value = 1 self.db.db_init() resp = self.nvti.get_nvt_metadata('1.2.3.4') self.assertEqual(resp, None) @patch('ospd_openvas.db.subprocess') def test_get_nvt_refs(self, mock_subps, mock_redis): refs = ['', '', 'URL:http://www.mantisbt.org/'] out_dict = { 'cve': [''], 'bid': [''], 'xref': ['URL:http://www.mantisbt.org/'], } mock_subps.check_output.return_value = ( 'use_mac_addr = no\ndb_address = ' '/tmp/redis.sock\ndrop_privileges = no').encode() mock_redis.return_value = mock_redis mock_redis.config_get.return_value = {'databases': '513'} mock_redis.lrange.return_value = refs mock_redis.keys.return_value = 1 self.db.db_init() resp = self.nvti.get_nvt_refs('1.2.3.4') self.assertEqual(resp, out_dict) @patch('ospd_openvas.db.subprocess') def test_get_nvt_refs_fail(self, mock_subps, mock_redis): mock_subps.check_output.return_value = ( 'use_mac_addr = no\ndb_address = ' '/tmp/redis.sock\ndrop_privileges = no'.encode()) mock_redis.return_value = mock_redis mock_redis.config_get.return_value = {'databases': '513'} mock_redis.lrange.return_value = {} mock_redis.keys.return_value = 1 self.db.db_init() resp = self.nvti.get_nvt_refs('1.2.3.4') self.assertEqual(resp, None) def test_get_nvt_prefs(self, mock_redis): prefs = ['dns-fuzz.timelimit|||entry|||default'] mock_redis.lrange.return_value = prefs mock_redis.return_value = mock_redis resp = self.nvti.get_nvt_prefs(mock_redis(), '1.2.3.4') self.assertEqual(resp, prefs) def test_get_nvt_timeout(self, mock_redis): mock_redis.lindex.return_value = '300' mock_redis.return_value = mock_redis resp = self.nvti.get_nvt_timeout(mock_redis(), '1.2.3.4') self.assertEqual(resp, '300') def test_get_nvt_tag(self, mock_redis): tag = ('last_modification=1533906565' '|creation_date=1517443741|cvss_bas' 'e_vector=AV:N/AC:L/Au:N/C:P/I:P/A:P|solution_type=V' 'endorFix|qod_type=package|affected=rubygems on Debi' 'an Linux|solution_method=DebianAPTUpgrade') out_dict = { 'last_modification': '1533906565', 'creation_date': '1517443741', 'cvss_base_vector': 'AV:N/AC:L/Au:N/C:P/I:P/A:P', 'solution_type': 'VendorFix', 'qod_type': 'package', 'affected': 'rubygems on Debian Linux', 'solution_method': 'DebianAPTUpgrade', } mock_redis.lindex.return_value = tag mock_redis.return_value = mock_redis resp = self.nvti.get_nvt_tag(mock_redis(), '1.2.3.4') self.assertEqual(out_dict, resp) @patch('ospd_openvas.nvticache.NVTICache._get_gvm_libs_version_string') def test_set_nvti_cache_name(self, mock_version, mock_redis): self.assertIsNone(self.nvti._nvti_cache_name) mock_version.return_value = '20.10' self.nvti._set_nvti_cache_name() self.assertTrue(mock_version.called) self.assertEqual(self.nvti._nvti_cache_name, 'nvticache20.10') mock_version.reset_mock() mock_version.return_value = '10.0.1' with self.assertRaises(OspdOpenvasError): self.nvti._set_nvti_cache_name() self.assertTrue(mock_version.called) @patch('ospd_openvas.nvticache.NVTICache._get_gvm_libs_version_string') def test_get_nvti_cache_name(self, mock_version, mock_redis): self.assertIsNone(self.nvti._nvti_cache_name) mock_version.return_value = '20.4' self.assertEqual(self.nvti._get_nvti_cache_name(), 'nvticache20.4') self.assertTrue(mock_version.called) mock_version.reset_mock() mock_version.return_value = '20.10' self.assertEqual(self.nvti._get_nvti_cache_name(), 'nvticache20.4') self.assertFalse(mock_version.called) @patch('ospd_openvas.nvticache.subprocess') def test_get_gvm_libs_version_string(self, mock_subps: Mock, mock_redis: Mock): mock_subps.check_output.return_value = ( 'openvas 20.4.0\ngvm-libs 20.10\n'.encode()) self.assertEqual(self.nvti._get_gvm_libs_version_string(), '20.10') @patch('ospd_openvas.nvticache.subprocess') def test_get_gvm_libs_version_string_subprocess_error( self, mock_subps: Mock, mock_redis: Mock): mock_subps.check_output.side_effect = CalledProcessError(returncode=1, cmd='foo bar') with self.assertRaises(OspdOpenvasError): self.nvti._get_gvm_libs_version_string() @patch('ospd_openvas.nvticache.subprocess') def test_get_gvm_libs_version_string_old_openvas(self, mock_subps: Mock, mock_redis: Mock): mock_subps.check_output.side_effect = CalledProcessError(returncode=1, cmd='foo bar') mock_subps.check_output.return_value = ( 'openvas 7.0.0\nfoo bar\n'.encode()) with self.assertRaises(OspdOpenvasError): self.nvti._get_gvm_libs_version_string() def test_is_compatible_version(self, mock_redis): self.assertFalse(self.nvti._is_compatible_version("1.0.0")) self.assertFalse(self.nvti._is_compatible_version("10.0.0")) self.assertTrue(self.nvti._is_compatible_version("11.0.1")) self.assertTrue(self.nvti._is_compatible_version("20.4")) self.assertTrue(self.nvti._is_compatible_version("20.4.2")) self.assertTrue(self.nvti._is_compatible_version("20.04")) self.assertTrue(self.nvti._is_compatible_version("20.10"))
class OSPDopenvas(OSPDaemon): """ Class for ospd-openvas daemon. """ def __init__(self, certfile, keyfile, cafile): """ Initializes the ospd-openvas daemon's internal data. """ super(OSPDopenvas, self).__init__(certfile=certfile, keyfile=keyfile, cafile=cafile) self.server_version = __version__ self.scanner_info['name'] = 'openvassd' self.scanner_info['version'] = '' # achieved during self.check() self.scanner_info['description'] = OSPD_DESC for name, param in OSPD_PARAMS.items(): self.add_scanner_param(name, param) self.main_kbindex = None self.openvas_db = OpenvasDB() self.nvti = NVTICache(self.openvas_db) self.openvas_db.db_init() self.pending_feed = None ctx = self.openvas_db.db_find(self.nvti.NVTICACHE_STR) if not ctx: self.redis_nvticache_init() ctx = self.openvas_db.db_find(self.nvti.NVTICACHE_STR) self.openvas_db.set_redisctx(ctx) self.load_vts() def parse_param(self): """ Set OSPD_PARAMS with the params taken from the openvas_scanner. """ global OSPD_PARAMS bool_dict = {'no': 0, 'yes': 1} result = subprocess.check_output(['openvassd', '-s'], stderr=subprocess.STDOUT) result = result.decode('ascii') param_list = dict() for conf in result.split('\n'): elem = conf.split('=') if len(elem) == 2: value = str.strip(elem[1]) if str.strip(elem[1]) in bool_dict: value = bool_dict[value] param_list[str.strip(elem[0])] = value for elem in OSPD_PARAMS: if elem in param_list: OSPD_PARAMS[elem]['default'] = param_list[elem] def redis_nvticache_init(self): """ Loads NVT's metadata into Redis DB. """ try: logger.debug('Loading NVTs in Redis DB') subprocess.check_call(['openvassd', '-C']) except subprocess.CalledProcessError as err: logger.error('OpenVAS Scanner failed to load NVTs.') raise err def check_feed(self): """ Check if there is a feed update. Wait until all the running scans finished. Set a flag to anounce there is a pending feed update, which avoid to start a new scan. """ _running_scan = False for scan_id in self.scan_processes: if self.scan_processes[scan_id].is_alive(): _running_scan = True if self.pending_feed: _pending_feed = True else: _pending_feed = self.get_vts_version( ) != self.nvti.get_feed_version() if _running_scan and _pending_feed: if not self.pending_feed: self.pending_feed = True logger.debug('There is a running scan. Therefore the feed ' 'update will be performed later.') elif not _running_scan and _pending_feed: self.vts = dict() self.load_vts() def scheduler(self): """This method is called periodically to run tasks.""" self.check_feed() def load_vts(self): """ Load the NVT's metadata into the vts global dictionary. """ logger.debug('Loading vts in memory.') oids = dict(self.nvti.get_oids()) for filename, vt_id in oids.items(): _vt_params = self.nvti.get_nvt_params(vt_id) _vt_refs = self.nvti.get_nvt_refs(vt_id) _custom = self.nvti.get_nvt_metadata(vt_id) _name = _custom.pop('name') _vt_creation_time = _custom.pop('creation_date') _vt_modification_time = _custom.pop('last_modification') _summary = None _impact = None _affected = None _insight = None _solution = None _solution_t = None _vuldetect = None _qod_t = None _qod_v = None if 'summary' in _custom: _summary = _custom.pop('summary') if 'impact' in _custom: _impact = _custom.pop('impact') if 'affected' in _custom: _affected = _custom.pop('affected') if 'insight' in _custom: _insight = _custom.pop('insight') if 'solution' in _custom: _solution = _custom.pop('solution') if 'solution_type' in _custom: _solution_t = _custom.pop('solution_type') if 'vuldetect' in _custom: _vuldetect = _custom.pop('vuldetect') if 'qod_type' in _custom: _qod_t = _custom.pop('qod_type') elif 'qod' in _custom: _qod_v = _custom.pop('qod') _severity = dict() if 'severity_base_vector' in _custom: _severity_vector = _custom.pop('severity_base_vector') else: _severity_vector = _custom.pop('cvss_base_vector') _severity['severity_base_vector'] = _severity_vector if 'severity_type' in _custom: _severity_type = custom.pop('severity_type') else: _severity_type = 'cvss_base_v2' _severity['severity_type'] = _severity_type if 'severity_origin' in _custom: _severity['severity_origin'] = _custom.pop('severity_origin') _vt_dependencies = list() if 'dependencies' in _custom: _deps = _custom.pop('dependencies') _deps_list = _deps.split(', ') for dep in _deps_list: _vt_dependencies.append(oids.get('filename:' + dep)) ret = self.add_vt(vt_id, name=_name, vt_params=_vt_params, vt_refs=_vt_refs, custom=_custom, vt_creation_time=_vt_creation_time, vt_modification_time=_vt_modification_time, vt_dependencies=_vt_dependencies, summary=_summary, impact=_impact, affected=_affected, insight=_insight, solution=_solution, solution_t=_solution_t, detection=_vuldetect, qod_t=_qod_t, qod_v=_qod_v, severities=_severity) if ret == -1: logger.info("Dupplicated VT with OID: {0}".format(vt_id)) if ret == -2: logger.info("{0}: Invalid OID.".format(vt_id)) _feed_version = self.nvti.get_feed_version() self.set_vts_version(vts_version=_feed_version) self.pending_feed = False logger.debug('Finish loading up vts.') @staticmethod def get_custom_vt_as_xml_str(vt_id, custom): """ Return an xml element with custom metadata formatted as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. custom (dict): Dictionary with the custom metadata. Return: string: xml element as string. """ _custom = Element('custom') for key, val in custom.items(): xml_key = SubElement(_custom, key) xml_key.text = val return tostring(_custom).decode('utf-8') @staticmethod def get_severities_vt_as_xml_str(vt_id, severities): """ Return an xml element with severities as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. severities (dict): Dictionary with the severities. Return: string: xml element as string. """ _severities = Element('severities') _severity = SubElement(_severities, 'severity') if 'severity_base_vector' in severities: _severity.text = severities.pop('severity_base_vector') if 'severity_origin' in severities: _severity.set('origin', severities.pop('severity_origin')) if 'severity_type' in severities: _severity.set('type', severities.pop('severity_type')) return tostring(_severities).decode('utf-8') @staticmethod def get_params_vt_as_xml_str(vt_id, vt_params): """ Return an xml element with params formatted as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. vt_params (dict): Dictionary with the VT parameters. Return: string: xml element as string. """ vt_params_xml = Element('vt_params') for pref_name, prefs in vt_params.items(): vt_param = Element('vt_param') vt_param.set('type', prefs['type']) vt_param.set('id', pref_name) xml_name = SubElement(vt_param, 'name') xml_name.text = prefs['name'] if prefs['default']: xml_def = SubElement(vt_param, 'default') xml_def.text = prefs['default'] vt_params_xml.append(vt_param) return tostring(vt_params_xml).decode('utf-8') @staticmethod def get_refs_vt_as_xml_str(vt_id, vt_refs): """ Return an xml element with references formatted as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. vt_refs (dict): Dictionary with the VT references. Return: string: xml element as string. """ vt_refs_xml = Element('vt_refs') for ref_type, ref_values in vt_refs.items(): for value in ref_values: vt_ref = Element('ref') if ref_type == "xref" and value: for xref in value.split(', '): try: _type, _id = xref.split(':', 1) except ValueError: logger.error( 'Not possible to parse xref %s for vt %s' % (xref, vt_id)) continue vt_ref.set('type', _type.lower()) vt_ref.set('id', _id) elif value: vt_ref.set('type', ref_type.lower()) vt_ref.set('id', value) else: continue vt_refs_xml.append(vt_ref) return tostring(vt_refs_xml).decode('utf-8') @staticmethod def get_dependencies_vt_as_xml_str(vt_id, dep_list): """ Return an xml element with dependencies as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. dep_list (List): List with the VT dependencies. Return: string: xml element as string. """ vt_deps_xml = Element('dependencies') for dep in dep_list: _vt_dep = Element('dependency') try: _vt_dep.set('vt_id', dep) except TypeError: logger.error('Not possible to add dependency %s for vt %s' % (dep, vt_id)) continue vt_deps_xml.append(_vt_dep) return tostring(vt_deps_xml).decode('utf-8') @staticmethod def get_creation_time_vt_as_xml_str(vt_id, creation_time): """ Return creation time as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. creation_time (str): String with the VT creation time. Return: string: xml element as string. """ _time = Element('creation_time') _time.text = creation_time return tostring(_time).decode('utf-8') @staticmethod def get_modification_time_vt_as_xml_str(vt_id, modification_time): """ Return modification time as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. modification_time (str): String with the VT modification time. Return: string: xml element as string. """ _time = Element('modification_time') _time.text = modification_time return tostring(_time).decode('utf-8') @staticmethod def get_summary_vt_as_xml_str(vt_id, summary): """ Return summary as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. summary (str): String with a VT summary. Return: string: xml element as string. """ _summary = Element('summary') _summary.text = summary return tostring(_summary).decode('utf-8') @staticmethod def get_impact_vt_as_xml_str(vt_id, impact): """ Return impact as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. impact (str): String which explain the vulneravility impact. Return: string: xml element as string. """ _impact = Element('impact') _impact.text = impact return tostring(_impact).decode('utf-8') @staticmethod def get_affected_vt_as_xml_str(vt_id, affected): """ Return affected as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. affected (str): String which explain what is affected. Return: string: xml element as string. """ _affected = Element('affected') _affected.text = affected return tostring(_affected).decode('utf-8') @staticmethod def get_insight_vt_as_xml_str(vt_id, insight): """ Return insight as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. insight (str): String giving an insight of the vulnerability. Return: string: xml element as string. """ _insight = Element('insight') _insight.text = insight return tostring(_insight).decode('utf-8') @staticmethod def get_solution_vt_as_xml_str(vt_id, solution, solution_type=None): """ Return solution as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. solution (str): String giving a possible solution. solution_type (str): A solution type Return: string: xml element as string. """ _solution = Element('solution') _solution.text = solution if solution_type: _solution.set('type', solution_type) return tostring(_solution).decode('utf-8') @staticmethod def get_detection_vt_as_xml_str(vt_id, vuldetect=None, qod_type=None, qod=None): """ Return detection as string. Arguments: vt_id (str): VT OID. Only used for logging in error case. vuldetect (str, opt): String which explain how the vulnerability was detected. qod_type (str, opt): qod type. qod (str, opt): qod value. Return: string: xml element as string. """ _detection = Element('detection') if vuldetect: _detection.text = vuldetect if qod_type: _detection.set('qod_type', qod_type) elif qod: _detection.set('qod', qod) return tostring(_detection).decode('utf-8') def check(self): """ Checks that openvassd command line tool is found and is executable. """ try: result = subprocess.check_output(['openvassd', '-V'], stderr=subprocess.STDOUT) result = result.decode('ascii') except OSError: # The command is not available return False if result is None: return False version = result.split('\n') if version[0].find('OpenVAS') < 0: return False self.parse_param() self.scanner_info['version'] = version[0] return True def update_progress(self, scan_id, target, msg): """ Calculate percentage and update the scan status of a target for the progress bar. Arguments: scan_id (uuid): Scan ID to identify the current scan process. target (str): Target to be updated with the calculated scan progress. msg (str): String with launched and total plugins. """ host_progress_dict = dict() try: launched, total = msg.split('/') except ValueError: return if float(total) == 0: return host_prog = (float(launched) / float(total)) * 100 host_progress_dict[target] = host_prog total_host = len(target_str_to_list(target)) target_progress = sum(host_progress_dict.values()) / total_host self.set_scan_target_progress(scan_id, target, target_progress) def get_openvas_status(self, scan_id, target): """ Get all status entries from redis kb. Arguments: scan_id (uuid): Scan ID to identify the current scan. target (str): Target progress to be updated. """ res = self.openvas_db.get_status() while res: self.update_progress(scan_id, target, res) res = self.openvas_db.get_status() def get_severity_score(self, oid): """ Return the severity score for the given oid. Arguments: oid (str): VT OID from which to get the severity vector Returns: The calculated cvss base value. None if there is no severity vector or severity type is not cvss base version 2. """ severity_type = (self.vts[oid]['severities'].get('severity_type')) severity_vector = ( self.vts[oid]['severities'].get('severity_base_vector')) if severity_type == "cvss_base_v2" and severity_vector: return CVSS.cvss_base_v2_value(severity_vector) return None def get_openvas_result(self, scan_id): """ Get all result entries from redis kb. """ res = self.openvas_db.get_result() while res: msg = res.split('|||') host_aux = self.openvas_db.get_single_item('internal/ip') roid = msg[3] rqod = '' if self.vts[roid].get('qod_type'): qod_t = self.vts[roid].get('qod_type') rqod = self.nvti.QoD_TYPES[qod_t] elif self.vts[roid].get('qod'): rqod = self.vts[roid].get('qod') rname = self.vts[roid].get('name') if msg[0] == 'ERRMSG': self.add_scan_error( scan_id, host=host_aux, name=rname, value=msg[4], port=msg[2], ) if msg[0] == 'LOG': self.add_scan_log( scan_id, host=host_aux, name=rname, value=msg[4], port=msg[2], qod=rqod, test_id=roid, ) if msg[0] == 'HOST_DETAIL': self.add_scan_log( scan_id, host=host_aux, name=rname, value=msg[4], ) if msg[0] == 'ALARM': rseverity = self.get_severity_score(roid) self.add_scan_alarm( scan_id, host=host_aux, name=rname, value=msg[4], port=msg[2], test_id=roid, severity=rseverity, qod=rqod, ) res = self.openvas_db.get_result() def get_openvas_timestamp_scan_host(self, scan_id, target): """ Get start and end timestamp of a host scan from redis kb. """ timestamp = self.openvas_db.get_host_scan_scan_end_time() if timestamp: self.add_scan_log(scan_id, host=target, name='HOST_END', value=timestamp) return timestamp = self.openvas_db.get_host_scan_scan_start_time() if timestamp: self.add_scan_log(scan_id, host=target, name='HOST_START', value=timestamp) return def scan_is_finished(self, scan_id): """ Check if the scan has finished. """ status = self.openvas_db.get_single_item('internal/%s' % scan_id) return status == 'finished' def scan_is_stopped(self, scan_id): """ Check if the parent process has received the stop_scan order. @in scan_id: ID to identify the scan to be stopped. @return 1 if yes, None in other case. """ ctx = self.openvas_db.kb_connect(dbnum=self.main_kbindex) self.openvas_db.set_redisctx(ctx) status = self.openvas_db.get_single_item('internal/%s' % scan_id) return status == 'stop_all' def stop_scan(self, global_scan_id): """ Set a key in redis to indicate the wrapper is stopped. It is done through redis because it is a new multiprocess instance and it is not possible to reach the variables of the grandchild process. Send SIGUSR2 to openvas to stop each running scan.""" ctx = self.openvas_db.kb_connect() for current_kbi in range(0, self.openvas_db.max_dbindex): self.openvas_db.select_kb(ctx, str(current_kbi), set_global=True) scan_id = self.openvas_db.get_single_item( 'internal/%s/globalscanid' % global_scan_id) if scan_id: self.openvas_db.set_single_item('internal/%s' % scan_id, [ 'stop_all', ]) ovas_pid = self.openvas_db.get_single_item('internal/ovas_pid') parent = psutil.Process(int(ovas_pid)) self.openvas_db.release_db(current_kbi) parent.send_signal(signal.SIGUSR2) logger.debug('Stopping process: {0}'.format(parent)) def get_vts_in_groups(self, filters): """ Return a list of vts which match with the given filter. @input filters A list of filters. Each filter has key, operator and a value. They are separated by a space. Supported keys: family @return Return a list of vts which match with the given filter. """ vts_list = list() families = dict() for oid in self.vts: family = self.vts[oid]['custom'].get('family') if family not in families: families[family] = list() families[family].append(oid) for elem in filters: key, value = elem.split('=') if key == 'family' and value in families: vts_list.extend(families[value]) return vts_list def get_vt_param_type(self, vtid, vt_param_id): """ Return the type of the vt parameter from the vts dictionary. """ vt_params_list = self.vts[vtid].get("vt_params") return vt_params_list[vt_param_id]["type"] @staticmethod def check_param_type(vt_param_value, param_type): """ Check if the value of a vt parameter matches with the type founded. """ if (param_type in [ 'entry', 'file', 'password', 'radio', 'sshlogin', ] and isinstance(vt_param_value, str)): return None elif (param_type == 'checkbox' and (vt_param_value == 'yes' or vt_param_value == 'no')): return None elif param_type == 'integer': try: int(vt_param_value) except ValueError: return 1 return None return 1 def process_vts(self, vts): """ Add single VTs and their parameters. """ vts_list = [] vts_params = [] vtgroups = vts.pop('vt_groups') if vtgroups: vts_list = self.get_vts_in_groups(vtgroups) for vtid, vt_params in vts.items(): vts_list.append(vtid) nvt_name = self.vts[vtid].get('name') for vt_param_id, vt_param_value in vt_params.items(): param_type = self.get_vt_param_type(vtid, vt_param_id) if vt_param_id == 'timeout': type_aux = 'integer' else: type_aux = param_type if self.check_param_type(vt_param_value, type_aux): logger.debug( 'Expected {} type for parameter value {}'.format( type_aux, str(vt_param_value))) param = [ "{0}[{1}]:{2}".format(nvt_name, param_type, vt_param_id), str(vt_param_value) ] vts_params.append(param) return vts_list, vts_params @staticmethod def build_credentials_as_prefs(credentials): """ Parse the credential dictionary. @param credentials: Dictionary with the credentials. @return A list with the credentials in string format to be added to the redis KB. """ cred_prefs_list = [] for credential in credentials.items(): service = credential[0] cred_params = credentials.get(service) cred_type = cred_params.get('type', '') username = cred_params.get('username', '') password = cred_params.get('password', '') if service == 'ssh': port = cred_params.get('port', '') cred_prefs_list.append('auth_port_ssh|||' + '{0}'.format(port)) cred_prefs_list.append('SSH Authorization[entry]:SSH login ' + 'name:|||{0}'.format(username)) if cred_type == 'up': cred_prefs_list.append('SSH Authorization[password]:' + 'SSH password (unsafe!):|||' + '{0}'.format(password)) else: private = cred_params.get('private', '') cred_prefs_list.append('SSH Authorization[password]:' + 'SSH key passphrase:|||' + '{0}'.format(password)) cred_prefs_list.append('SSH Authorization[file]:' + 'SSH private key:|||' + '{0}'.format(private)) if service == 'smb': cred_prefs_list.append('SMB Authorization[entry]:SMB login:'******'|||{0}'.format(username)) cred_prefs_list.append('SMB Authorization[password]:' + 'SMB password :|||' + '{0}'.format(password)) if service == 'esxi': cred_prefs_list.append( 'ESXi Authorization[entry]:ESXi login ' + 'name:|||{0}'.format(username)) cred_prefs_list.append('ESXi Authorization[password]:' + 'ESXi login password:|||' + '{0}'.format(password)) if service == 'snmp': community = cred_params.get('community', '') auth_algorithm = cred_params.get('auth_algorithm', '') privacy_password = cred_params.get('privacy_password', '') privacy_algorithm = cred_params.get('privacy_algorithm', '') cred_prefs_list.append('SNMP Authorization[password]:' + 'SNMP Community:' + '{0}'.format(community)) cred_prefs_list.append('SNMP Authorization[entry]:' + 'SNMPv3 Username:'******'{0}'.format(username)) cred_prefs_list.append('SNMP Authorization[password]:' + 'SNMPv3 Password:'******'{0}'.format(password)) cred_prefs_list.append('SNMP Authorization[radio]:' + 'SNMPv3 Authentication Algorithm:' + '{0}'.format(auth_algorithm)) cred_prefs_list.append('SNMP Authorization[password]:' + 'SNMPv3 Privacy Password:'******'{0}'.format(privacy_password)) cred_prefs_list.append('SNMP Authorization[radio]:' + 'SNMPv3 Privacy Algorithm:' + '{0}'.format(privacy_algorithm)) return cred_prefs_list def exec_scan(self, scan_id, target): """ Starts the OpenVAS scanner for scan_id scan. """ if self.pending_feed: logger.info('%s: There is a pending feed update. ' 'The scan can not be started.' % scan_id) self.add_scan_error( scan_id, name='', host=target, value=('It was not possible to start the scan,' 'because a pending feed update. Please try later')) return 2 ports = self.get_scan_ports(scan_id, target) if not ports: self.add_scan_error(scan_id, name='', host=target, value='No port list defined.') return 2 # Get scan options options = self.get_scan_options(scan_id) prefs_val = [] ctx = self.openvas_db.kb_new() self.openvas_db.set_redisctx(ctx) self.main_kbindex = self.openvas_db.db_index # To avoid interference between scan process during a parallel scanning # new uuid is used internally for each scan. openvas_scan_id = str(uuid.uuid4()) self.openvas_db.add_single_item('internal/%s' % openvas_scan_id, ['new']) self.openvas_db.add_single_item('internal/%s/globalscanid' % scan_id, [openvas_scan_id]) # Set scan preferences for key, value in options.items(): item_type = '' if key in OSPD_PARAMS: item_type = OSPD_PARAMS[key].get('type') if item_type == 'boolean': val = _from_bool_to_str(value) else: val = str(value) prefs_val.append(key + "|||" + val) self.openvas_db.add_single_item( 'internal/%s/scanprefs' % openvas_scan_id, prefs_val) # Store main_kbindex as global preference ov_maindbid = ('ov_maindbid|||%d' % self.main_kbindex) self.openvas_db.add_single_item( 'internal/%s/scanprefs' % openvas_scan_id, [ov_maindbid]) # Set target target_aux = ('TARGET|||%s' % target) self.openvas_db.add_single_item( 'internal/%s/scanprefs' % openvas_scan_id, [target_aux]) # Set port range port_range = ('port_range|||%s' % ports) self.openvas_db.add_single_item( 'internal/%s/scanprefs' % openvas_scan_id, [port_range]) # Set credentials credentials = self.get_scan_credentials(scan_id, target) if credentials: cred_prefs = self.build_credentials_as_prefs(credentials) self.openvas_db.add_single_item( 'internal/%s/scanprefs' % openvas_scan_id, cred_prefs) # Set plugins to run nvts = self.get_scan_vts(scan_id) if nvts != '': nvts_list, nvts_params = self.process_vts(nvts) # Add nvts list separ = ';' plugin_list = 'plugin_set|||%s' % separ.join(nvts_list) self.openvas_db.add_single_item( 'internal/%s/scanprefs' % openvas_scan_id, [plugin_list]) # Add nvts parameters for elem in nvts_params: item = '%s|||%s' % (elem[0], elem[1]) self.openvas_db.add_single_item( 'internal/%s/scanprefs' % openvas_scan_id, [item]) else: self.openvas_db.release_db(self.main_kbindex) self.add_scan_error(scan_id, name='', host=target, value='No VTS to run.') return 2 # Create a general log entry about executing OpenVAS # It is important to send at least one result, otherwise # the host details won't be stored. self.add_scan_log(scan_id, host=target, name='OpenVAS summary', value='An OpenVAS Scanner was started for %s.' % target) self.add_scan_log(scan_id, host=target, name='KB location Found', value='KB location path was found: %s.' % self.openvas_db.db_address) self.add_scan_log(scan_id, host=target, name='Feed Update', value='Feed version: %s.' % self.nvti.get_feed_version()) cmd = ['openvassd', '--scan-start', openvas_scan_id] try: result = subprocess.Popen(cmd, shell=False) except OSError: # the command is not available return False ovas_pid = result.pid logger.debug('pid = {0}'.format(ovas_pid)) self.openvas_db.add_single_item('internal/ovas_pid', [ovas_pid]) # Wait until the scanner starts and loads all the preferences. while self.openvas_db.get_single_item('internal/' + openvas_scan_id) == 'new': time.sleep(1) no_id_found = False while True: time.sleep(3) # Check if the client stopped the whole scan if self.scan_is_stopped(openvas_scan_id): return 1 ctx = self.openvas_db.kb_connect(self.main_kbindex) self.openvas_db.set_redisctx(ctx) dbs = self.openvas_db.get_list_item('internal/dbindex') for i in list(dbs): if i == self.main_kbindex: continue self.openvas_db.select_kb(ctx, str(i), set_global=True) id_aux = self.openvas_db.get_single_item('internal/scan_id') if not id_aux: continue if id_aux == openvas_scan_id: no_id_found = False self.get_openvas_timestamp_scan_host(scan_id, target) self.get_openvas_result(scan_id) self.get_openvas_status(scan_id, target) if self.scan_is_finished(openvas_scan_id): self.openvas_db.select_kb(ctx, str(self.main_kbindex), set_global=False) self.openvas_db.remove_list_item('internal/dbindex', i) self.openvas_db.release_db(i) # Scan end. No kb in use for this scan id if no_id_found: break no_id_found = True # Delete keys from KB related to this scan task. self.openvas_db.release_db(self.main_kbindex) return 1