Passed
Push — master ( 6c1e13...78bc3c )
by Juan José
01:40 queued 13s
created

OSPDopenvas.stop_scan_cleanup()   A

Complexity

Conditions 3

Size

Total Lines 19
Code Lines 14

Duplication

Lines 19
Ratio 100 %

Importance

Changes 0
Metric Value
cc 3
eloc 14
nop 2
dl 19
loc 19
rs 9.7
c 0
b 0
f 0
1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2018 Greenbone Networks GmbH
3
#
4
# SPDX-License-Identifier: GPL-2.0-or-later
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
20
""" Setup for the OSP OpenVAS Server. """
21
22
import logging
23
import subprocess
24
import time
25
import signal
26
import uuid
27
from lxml.etree import tostring, SubElement, Element
28
from datetime import datetime
29
import psutil
30
from os import path
31
32
from ospd.ospd import OSPDaemon, logger
33
from ospd.misc import main as daemon_main
34
from ospd.misc import target_str_to_list
35
from ospd.cvss import CVSS
36
from ospd.vtfilter import VtsFilter
37
from ospd_openvas import __version__
38
from ospd_openvas.errors import OSPDOpenvasError
39
40
from ospd_openvas.nvticache import NVTICache
41
from ospd_openvas.db import OpenvasDB
42
43
logger = logging.getLogger(__name__)
44
45
OSPD_DESC = """
46
This scanner runs 'OpenVAS Scanner' to scan the target hosts.
47
48
OpenVAS (Open Vulnerability Assessment System) is a powerful scanner
49
for vulnerabilities in IT infrastrucutres. The capabilities include
50
unauthzenticated scanning as well as authneticated scanning for
51
various types of systems and services.
52
53
For more details about OpenVAS see the OpenVAS homepage:
54
http://www.openvas.org/
55
56
The current version of ospd-openvas is a simple frame, which sends
57
the server parameters to the Greenbone Vulnerability Manager (GVM) and checks
58
the existence of OpenVAS Scanner binary. But it can not run scans yet.
59
"""
60
61
OSPD_PARAMS = {
62
    'auto_enable_dependencies': {
63
        'type': 'boolean',
64
        'name': 'auto_enable_dependencies',
65
        'default': 1,
66
        'mandatory': 1,
67
        'description': 'Automatically enable the plugins that are depended on',
68
    },
69
    'cgi_path': {
70
        'type': 'string',
71
        'name': 'cgi_path',
72
        'default': '/cgi-bin:/scripts',
73
        'mandatory': 1,
74
        'description': 'Look for default CGIs in /cgi-bin and /scripts',
75
    },
76
    'checks_read_timeout': {
77
        'type': 'integer',
78
        'name': 'checks_read_timeout',
79
        'default': 5,
80
        'mandatory': 1,
81
        'description': ('Number  of seconds that the security checks will ' +
82
                        'wait for when doing a recv()'),
83
    },
84
    'drop_privileges': {
85
        'type': 'boolean',
86
        'name': 'drop_privileges',
87
        'default': 0,
88
        'mandatory': 1,
89
        'description': '',
90
    },
91
    'network_scan': {
92
        'type': 'boolean',
93
        'name': 'network_scan',
94
        'default': 0,
95
        'mandatory': 1,
96
        'description': '',
97
    },
98
    'non_simult_ports': {
99
        'type': 'string',
100
        'name': 'non_simult_ports',
101
        'default': '139, 445, 3389, Services/irc',
102
        'mandatory': 1,
103
        'description': ('Prevent to make two connections on the same given ' +
104
                        'ports at the same time.'),
105
    },
106
    'open_sock_max_attempts': {
107
        'type': 'integer',
108
        'name': 'open_sock_max_attempts',
109
        'default': 5,
110
        'mandatory': 0,
111
        'description': ('Number of unsuccessful retries to open the socket ' +
112
                        'before to set the port as closed.'),
113
    },
114
    'timeout_retry': {
115
        'type': 'integer',
116
        'name': 'timeout_retry',
117
        'default': 5,
118
        'mandatory': 0,
119
        'description': ('Number of retries when a socket connection attempt ' +
120
                        'timesout.'),
121
    },
122
    'optimize_test': {
123
        'type': 'integer',
124
        'name': 'optimize_test',
125
        'default': 5,
126
        'mandatory': 0,
127
        'description': ('By default, openvassd does not trust the remote ' +
128
                        'host banners.'),
129
    },
130
    'plugins_timeout': {
131
        'type': 'integer',
132
        'name': 'plugins_timeout',
133
        'default': 5,
134
        'mandatory': 0,
135
        'description': 'This is the maximum lifetime, in seconds of a plugin.',
136
    },
137
    'report_host_details': {
138
        'type': 'boolean',
139
        'name': 'report_host_details',
140
        'default': 1,
141
        'mandatory': 1,
142
        'description': '',
143
    },
144
    'safe_checks': {
145
        'type': 'boolean',
146
        'name': 'safe_checks',
147
        'default': 1,
148
        'mandatory': 1,
149
        'description': ('Disable the plugins with potential to crash ' +
150
                        'the remote services'),
151
    },
152
    'scanner_plugins_timeout': {
153
        'type': 'integer',
154
        'name': 'scanner_plugins_timeout',
155
        'default': 36000,
156
        'mandatory': 1,
157
        'description': 'Like plugins_timeout, but for ACT_SCANNER plugins.',
158
    },
159
    'time_between_request': {
160
        'type': 'integer',
161
        'name': 'time_between_request',
162
        'default': 0,
163
        'mandatory': 0,
164
        'description': ('Allow to set a wait time between two actions ' +
165
                        '(open, send, close).'),
166
    },
167
    'unscanned_closed': {
168
        'type': 'boolean',
169
        'name': 'unscanned_closed',
170
        'default': 1,
171
        'mandatory': 1,
172
        'description': '',
173
    },
174
    'unscanned_closed_udp': {
175
        'type': 'boolean',
176
        'name': 'unscanned_closed_udp',
177
        'default': 1,
178
        'mandatory': 1,
179
        'description': '',
180
    },
181
    'use_mac_addr': {
182
        'type': 'boolean',
183
        'name': 'use_mac_addr',
184
        'default': 0,
185
        'mandatory': 0,
186
        'description': 'To test the local network. ' +
187
                       'Hosts will be referred to by their MAC address.',
188
    },
189
    'vhosts': {
190
        'type': 'string',
191
        'name': 'vhosts',
192
        'default': '',
193
        'mandatory': 0,
194
        'description': '',
195
    },
196
    'vhosts_ip': {
197
        'type': 'string',
198
        'name': 'vhosts_ip',
199
        'default': '',
200
        'mandatory': 0,
201
        'description': '',
202
    },
203
}
204
205
206
def _from_bool_to_str(value):
207
    """ The OpenVAS scanner use yes and no as boolean values, whereas ospd
208
    uses 1 and 0."""
209
    return 'yes' if value == 1 else 'no'
210
211 View Code Duplication
class OpenVasVtsFilter(VtsFilter):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
212
    """ Methods to overwrite the ones in the original class.
213
    Each method formats the value to be compatible with the filter
214
    """
215
    def format_vt_modification_time(self, value):
216
        """ Convert the datetime value in an 19 character string
217
        representing YearMonthDateHourMinuteSecond.
218
        e.g. 20190319122532
219
        """
220
221
        date = value[7:26].replace(" ", "")
222
        date = date.replace("-", "")
223
        date = date.replace(":","")
224
        return date
225
226 View Code Duplication
class OSPDopenvas(OSPDaemon):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
227
228
    """ Class for ospd-openvas daemon. """
229
230
    def __init__(self, certfile, keyfile, cafile):
231
        """ Initializes the ospd-openvas daemon's internal data. """
232
233
        super().__init__(certfile=certfile, keyfile=keyfile, cafile=cafile,
234
                         customvtfilter=OpenVasVtsFilter(),
235
                         wrapper_logger=logger)
236
237
        self.server_version = __version__
238
        self.scanner_info['name'] = 'openvassd'
239
        self.scanner_info['version'] = ''  # achieved during self.check()
240
        self.scanner_info['description'] = OSPD_DESC
241
        for name, param in OSPD_PARAMS.items():
242
            self.add_scanner_param(name, param)
243
244
        self.scan_only_params = dict()
245
        self.main_kbindex = None
246
        self.openvas_db = OpenvasDB(logger)
247
        self.nvti = NVTICache(self.openvas_db)
248
249
        self.openvas_db.db_init()
250
251
        self.pending_feed = None
252
        ctx = self.openvas_db.db_find(self.nvti.NVTICACHE_STR)
253
        if not ctx:
254
            self.redis_nvticache_init()
255
            ctx = self.openvas_db.db_find(self.nvti.NVTICACHE_STR)
256
        self.openvas_db.set_redisctx(ctx)
257
        self.load_vts()
258
259
    def parse_param(self):
260
        """ Set OSPD_PARAMS with the params taken from the openvas_scanner. """
261
        global OSPD_PARAMS
262
        bool_dict = {'no': 0, 'yes': 1}
263
264
        result = subprocess.check_output(['openvassd', '-s'],
265
                                         stderr=subprocess.STDOUT)
266
        result = result.decode('ascii')
267
        param_list = dict()
268
        for conf in result.split('\n'):
269
            elem = conf.split('=')
270
            if len(elem) == 2:
271
                value = str.strip(elem[1])
272
                if str.strip(elem[1]) in bool_dict:
273
                    value = bool_dict[value]
274
                param_list[str.strip(elem[0])] = value
275
        for elem in OSPD_PARAMS:
276
            if elem in param_list:
277
                OSPD_PARAMS[elem]['default'] = param_list[elem]
278
        for elem in param_list:
279
            if elem not in OSPD_PARAMS:
280
                self.scan_only_params[elem] = param_list[elem]
281
282
    def redis_nvticache_init(self):
283
        """ Loads NVT's metadata into Redis DB. """
284
        try:
285
            logger.debug('Loading NVTs in Redis DB')
286
            subprocess.check_call(['openvassd', '-C'])
287
        except subprocess.CalledProcessError as err:
288
            logger.error('OpenVAS Scanner failed to load NVTs.')
289
            raise err
290
291
    def feed_is_outdated(self, current_feed):
292
        """ Compare the current feed with the one in the disk.
293
294
        Return:
295
            False if there is no new feed. True if the feed version in disk
296
            is newer than the feed in redis cache.
297
        """
298
        plugins_folder = self.scan_only_params.get('plugins_folder')
299
        if not plugins_folder:
300
            raise OSPDOpenvasError("Error: Path to plugins folder not found.")
301
        feed_info_file = path.join(plugins_folder, 'plugin_feed_info.inc')
302
        fcontent = open(feed_info_file)
303
        for line in fcontent:
304
            if "PLUGIN_SET" in line:
305
                date = line.split(' = ')[1]
306
                date = date.replace(';', '')
307
                date = date.replace('"', '')
308
        if int(current_feed) < int(date):
0 ignored issues
show
introduced by
The variable date does not seem to be defined for all execution paths.
Loading history...
309
            return True
310
        return False
311
312
    def check_feed(self):
313
        """ Check if there is a feed update. Wait until all the running
314
        scans finished. Set a flag to anounce there is a pending feed update,
315
        which avoid to start a new scan.
316
        """
317
318
        # Check if the nvticache in redis is outdated
319
        current_feed =  self.nvti.get_feed_version()
320
        if not current_feed or self.feed_is_outdated(current_feed):
321
            self.redis_nvticache_init()
322
            ctx = self.openvas_db.db_find(self.nvti.NVTICACHE_STR)
323
            self.openvas_db.set_redisctx(ctx)
324
            self.pending_feed = True
325
326
        _running_scan = False
327
        for scan_id in self.scan_processes:
328
            if self.scan_processes[scan_id].is_alive():
329
                _running_scan = True
330
331
        # Check if the NVT dict is outdated
332
        if self.pending_feed:
333
            _pending_feed = True
334
        else:
335
            _pending_feed = self.get_vts_version() != self.nvti.get_feed_version()
336
337
        if _running_scan and _pending_feed:
338
            if not self.pending_feed:
339
                self.pending_feed = True
340
                logger.debug(
341
                    'There is a running scan. Therefore the feed '
342
                    'update will be performed later.')
343
        elif not _running_scan and _pending_feed:
344
            self.vts = dict()
345
            self.load_vts()
346
347
    def scheduler(self):
348
        """This method is called periodically to run tasks."""
349
        self.check_feed()
350
351
    def load_vts(self):
352
        """ Load the NVT's metadata into the vts
353
        global  dictionary. """
354
        logger.debug('Loading vts in memory.')
355
        oids = dict(self.nvti.get_oids())
356
        for filename, vt_id in oids.items():
357
            _vt_params = self.nvti.get_nvt_params(vt_id)
358
            _vt_refs = self.nvti.get_nvt_refs(vt_id)
359
            _custom = self.nvti.get_nvt_metadata(vt_id)
360
            _name = _custom.pop('name')
361
            _vt_creation_time = _custom.pop('creation_date')
362
            _vt_modification_time = _custom.pop('last_modification')
363
364
            _summary = None
365
            _impact = None
366
            _affected = None
367
            _insight = None
368
            _solution = None
369
            _solution_t = None
370
            _vuldetect = None
371
            _qod_t = None
372
            _qod_v = None
373
374
            if 'summary' in _custom:
375
                _summary = _custom.pop('summary')
376
            if 'impact' in _custom:
377
                _impact = _custom.pop('impact')
378
            if 'affected' in _custom:
379
                _affected = _custom.pop('affected')
380
            if 'insight' in _custom :
381
                _insight = _custom.pop('insight')
382
            if 'solution' in _custom:
383
                _solution = _custom.pop('solution')
384
                if 'solution_type' in _custom:
385
                    _solution_t = _custom.pop('solution_type')
386
387
            if 'vuldetect' in _custom:
388
                _vuldetect  = _custom.pop('vuldetect')
389
            if 'qod_type' in _custom:
390
                _qod_t  = _custom.pop('qod_type')
391
            elif 'qod' in _custom:
392
                _qod_v  = _custom.pop('qod')
393
394
            _severity = dict()
395
            if 'severity_base_vector' in _custom:
396
                _severity_vector = _custom.pop('severity_base_vector')
397
            else:
398
                _severity_vector = _custom.pop('cvss_base_vector')
399
            _severity['severity_base_vector'] = _severity_vector
400
            if 'severity_type' in _custom:
401
                _severity_type = custom.pop('severity_type')
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable custom does not seem to be defined.
Loading history...
402
            else:
403
                _severity_type = 'cvss_base_v2'
404
            _severity['severity_type'] = _severity_type
405
            if 'severity_origin' in _custom:
406
                _severity['severity_origin'] = _custom.pop('severity_origin')
407
408
            _vt_dependencies = list()
409
            if 'dependencies' in _custom:
410
                _deps = _custom.pop('dependencies')
411
                _deps_list = _deps.split(', ')
412
                for dep in _deps_list:
413
                    _vt_dependencies.append(oids.get('filename:' + dep))
414
415
            ret = self.add_vt(
416
                vt_id,
417
                name=_name,
418
                vt_params=_vt_params,
419
                vt_refs=_vt_refs,
420
                custom=_custom,
421
                vt_creation_time=_vt_creation_time,
422
                vt_modification_time=_vt_modification_time,
423
                vt_dependencies=_vt_dependencies,
424
                summary=_summary,
425
                impact=_impact,
426
                affected=_affected,
427
                insight=_insight,
428
                solution=_solution,
429
                solution_t=_solution_t,
430
                detection=_vuldetect,
431
                qod_t=_qod_t,
432
                qod_v=_qod_v,
433
                severities=_severity
434
            )
435
            if ret == -1:
436
                logger.info("Dupplicated VT with OID: {0}".format(vt_id))
437
            if ret == -2:
438
                logger.info("{0}: Invalid OID.".format(vt_id))
439
440
        _feed_version = self.nvti.get_feed_version()
441
        self.set_vts_version(vts_version=_feed_version)
442
        self.pending_feed = False
443
        logger.debug('Finish loading up vts.')
444
445
    @staticmethod
446
    def get_custom_vt_as_xml_str(vt_id, custom):
447
        """ Return an xml element with custom metadata formatted as string.
448
        Arguments:
449
            vt_id (str): VT OID. Only used for logging in error case.
450
            custom (dict): Dictionary with the custom metadata.
451
        Return:
452
            string: xml element as string.
453
        """
454
455
        _custom = Element('custom')
456
        for key, val in custom.items():
457
            xml_key = SubElement(_custom, key)
458
            xml_key.text = val
459
460
        return tostring(_custom).decode('utf-8')
461
462
    @staticmethod
463
    def get_severities_vt_as_xml_str(vt_id, severities):
464
        """ Return an xml element with severities as string.
465
        Arguments:
466
            vt_id (str): VT OID. Only used for logging in error case.
467
            severities (dict): Dictionary with the severities.
468
        Return:
469
            string: xml element as string.
470
        """
471
        _severities = Element('severities')
472
        _severity = SubElement(_severities, 'severity')
473
        if 'severity_base_vector' in severities:
474
            _severity.text = severities.get('severity_base_vector')
475
        if 'severity_origin' in severities:
476
            _severity.set('origin', severities.get('severity_origin'))
477
        if 'severity_type' in severities:
478
            _severity.set('type', severities.get('severity_type'))
479
480
        return tostring(_severities).decode('utf-8')
481
482
    @staticmethod
483
    def get_params_vt_as_xml_str(vt_id, vt_params):
484
        """ Return an xml element with params formatted as string.
485
        Arguments:
486
            vt_id (str): VT OID. Only used for logging in error case.
487
            vt_params (dict): Dictionary with the VT parameters.
488
        Return:
489
            string: xml element as string.
490
        """
491
        vt_params_xml = Element('vt_params')
492
        for pref_name, prefs in vt_params.items():
493
            vt_param = Element('vt_param')
494
            vt_param.set('type', prefs['type'])
495
            vt_param.set('id', prefs['id'])
496
            xml_name = SubElement(vt_param, 'name')
497
            xml_name.text = prefs['name']
498
            if prefs['default']:
499
                xml_def = SubElement(vt_param, 'default')
500
                xml_def.text = prefs['default']
501
            vt_params_xml.append(vt_param)
502
503
        return tostring(vt_params_xml).decode('utf-8')
504
505
    @staticmethod
506
    def get_refs_vt_as_xml_str(vt_id, vt_refs):
507
        """ Return an xml element with references formatted as string.
508
        Arguments:
509
            vt_id (str): VT OID. Only used for logging in error case.
510
            vt_refs (dict): Dictionary with the VT references.
511
        Return:
512
            string: xml element as string.
513
        """
514
        vt_refs_xml = Element('refs')
515
        for ref_type, ref_values in vt_refs.items():
516
            for value in ref_values:
517
                vt_ref = Element('ref')
518
                if ref_type == "xref" and value:
519
                    for xref in value.split(', '):
520
                        try:
521
                            _type, _id = xref.split(':', 1)
522
                        except ValueError:
523
                            logger.error(
524
                                'Not possible to parse xref %s for vt %s' % (
525
                                    xref, vt_id))
526
                            continue
527
                        vt_ref.set('type', _type.lower())
528
                        vt_ref.set('id', _id)
529
                elif value:
530
                    vt_ref.set('type', ref_type.lower())
531
                    vt_ref.set('id', value)
532
                else:
533
                    continue
534
                vt_refs_xml.append(vt_ref)
535
536
        return tostring(vt_refs_xml).decode('utf-8')
537
538
    @staticmethod
539
    def get_dependencies_vt_as_xml_str(vt_id, dep_list):
540
        """ Return  an xml element with dependencies as string.
541
        Arguments:
542
            vt_id (str): VT OID. Only used for logging in error case.
543
            dep_list (List): List with the VT dependencies.
544
        Return:
545
            string: xml element as string.
546
        """
547
        vt_deps_xml = Element('dependencies')
548
        for dep in dep_list:
549
            _vt_dep = Element('dependency')
550
            try:
551
                _vt_dep.set('vt_id', dep)
552
            except TypeError:
553
                logger.error('Not possible to add dependency %s for vt %s' % (
554
                    dep, vt_id))
555
                continue
556
            vt_deps_xml.append(_vt_dep)
557
558
        return tostring(vt_deps_xml).decode('utf-8')
559
560
    @staticmethod
561
    def get_creation_time_vt_as_xml_str(vt_id, creation_time):
562
        """ Return creation time as string.
563
        Arguments:
564
            vt_id (str): VT OID. Only used for logging in error case.
565
            creation_time (str): String with the VT creation time.
566
        Return:
567
            string: xml element as string.
568
        """
569
        _time = Element('creation_time')
570
        _time.text = creation_time
571
        return tostring(_time).decode('utf-8')
572
573
    @staticmethod
574
    def get_modification_time_vt_as_xml_str(vt_id, modification_time):
575
        """ Return modification time as string.
576
        Arguments:
577
            vt_id (str): VT OID. Only used for logging in error case.
578
            modification_time (str): String with the VT modification time.
579
        Return:
580
            string: xml element as string.
581
        """
582
        _time = Element('modification_time')
583
        _time.text = modification_time
584
        return tostring(_time).decode('utf-8')
585
586
    @staticmethod
587
    def get_summary_vt_as_xml_str(vt_id, summary):
588
        """ Return summary as string.
589
        Arguments:
590
            vt_id (str): VT OID. Only used for logging in error case.
591
            summary (str): String with a VT summary.
592
        Return:
593
            string: xml element as string.
594
        """
595
        _summary = Element('summary')
596
        _summary.text = summary
597
        return tostring(_summary).decode('utf-8')
598
599
    @staticmethod
600
    def get_impact_vt_as_xml_str(vt_id, impact):
601
        """ Return impact as string.
602
603
        Arguments:
604
            vt_id (str): VT OID. Only used for logging in error case.
605
            impact (str): String which explain the vulneravility impact.
606
        Return:
607
            string: xml element as string.
608
        """
609
        _impact = Element('impact')
610
        _impact.text = impact
611
        return tostring(_impact).decode('utf-8')
612
613
    @staticmethod
614
    def get_affected_vt_as_xml_str(vt_id, affected):
615
        """ Return affected as string.
616
        Arguments:
617
            vt_id (str): VT OID. Only used for logging in error case.
618
            affected (str): String which explain what is affected.
619
        Return:
620
            string: xml element as string.
621
        """
622
        _affected = Element('affected')
623
        _affected.text = affected
624
        return tostring(_affected).decode('utf-8')
625
626
    @staticmethod
627
    def get_insight_vt_as_xml_str(vt_id, insight):
628
        """ Return insight as string.
629
        Arguments:
630
            vt_id (str): VT OID. Only used for logging in error case.
631
            insight (str): String giving an insight of the vulnerability.
632
        Return:
633
            string: xml element as string.
634
        """
635
        _insight = Element('insight')
636
        _insight.text = insight
637
        return tostring(_insight).decode('utf-8')
638
639
    @staticmethod
640
    def get_solution_vt_as_xml_str(vt_id, solution, solution_type=None):
641
        """ Return solution as string.
642
        Arguments:
643
            vt_id (str): VT OID. Only used for logging in error case.
644
            solution (str): String giving a possible solution.
645
            solution_type (str): A solution type
646
        Return:
647
            string: xml element as string.
648
        """
649
        _solution = Element('solution')
650
        _solution.text = solution
651
        if solution_type:
652
            _solution.set('type', solution_type)
653
        return tostring(_solution).decode('utf-8')
654
655
    @staticmethod
656
    def get_detection_vt_as_xml_str(vt_id, vuldetect=None,
657
                                    qod_type=None, qod=None):
658
        """ Return detection as string.
659
        Arguments:
660
            vt_id (str): VT OID. Only used for logging in error case.
661
            vuldetect (str, opt): String which explain how the vulnerability
662
                was detected.
663
            qod_type (str, opt): qod type.
664
            qod (str, opt): qod value.
665
        Return:
666
            string: xml element as string.
667
        """
668
        _detection = Element('detection')
669
        if vuldetect:
670
            _detection.text = vuldetect
671
        if qod_type:
672
            _detection.set('qod_type', qod_type)
673
        elif qod:
674
            _detection.set('qod', qod)
675
676
        return tostring(_detection).decode('utf-8')
677
678
    def check(self):
679
        """ Checks that openvassd command line tool is found and
680
        is executable. """
681
        try:
682
            result = subprocess.check_output(['openvassd', '-V'],
683
                                             stderr=subprocess.STDOUT)
684
            result = result.decode('ascii')
685
        except OSError:
686
            # The command is not available
687
            return False
688
689
        if result is None:
690
            return False
691
692
        version = result.split('\n')
693
        if version[0].find('OpenVAS') < 0:
694
            return False
695
696
        self.parse_param()
697
        self.scanner_info['version'] = version[0]
698
699
        return True
700
701
    def update_progress(self, scan_id, target, msg):
702
        """ Calculate percentage and update the scan status of a target
703
        for the progress bar.
704
        Arguments:
705
            scan_id (uuid): Scan ID to identify the current scan process.
706
            target (str): Target to be updated with the calculated
707
                          scan progress.
708
            msg (str): String with launched and total plugins.
709
        """
710
        host_progress_dict = dict()
711
        try:
712
            launched, total = msg.split('/')
713
        except ValueError:
714
            return
715
        if float(total) == 0:
716
            return
717
        host_prog = (float(launched) / float(total)) * 100
718
        host_progress_dict[target] = host_prog
719
        total_host = len(target_str_to_list(target))
720
        target_progress = sum(host_progress_dict.values()) / total_host
721
        self.set_scan_target_progress(scan_id, target, target_progress)
722
723
    def get_openvas_status(self, scan_id, target):
724
        """ Get all status entries from redis kb.
725
        Arguments:
726
            scan_id (uuid): Scan ID to identify the current scan.
727
            target (str): Target progress to be updated.
728
        """
729
        res = self.openvas_db.get_status()
730
        while res:
731
            self.update_progress(scan_id, target, res)
732
            res = self.openvas_db.get_status()
733
734
    def get_severity_score(self, oid):
735
        """ Return the severity score for the given oid.
736
        Arguments:
737
            oid (str): VT OID from which to get the severity vector
738
        Returns:
739
            The calculated cvss base value. None if there is no severity
740
            vector or severity type is not cvss base version 2.
741
        """
742
        severity_type = (
743
            self.vts[oid]['severities'].get('severity_type'))
744
        severity_vector = (
745
            self.vts[oid]['severities'].get('severity_base_vector'))
746
747
        if severity_type == "cvss_base_v2" and severity_vector:
748
            return CVSS.cvss_base_v2_value(severity_vector)
749
750
        return None
751
752
    def get_openvas_result(self, scan_id):
753
        """ Get all result entries from redis kb. """
754
        res = self.openvas_db.get_result()
755
        while res:
756
            msg = res.split('|||')
757
            host_aux = self.openvas_db.get_single_item('internal/ip')
758
            roid = msg[3]
759
760
            rqod = ''
761
            if self.vts[roid].get('qod_type'):
762
                qod_t = self.vts[roid].get('qod_type')
763
                rqod = self.nvti.QoD_TYPES[qod_t]
764
            elif self.vts[roid].get('qod'):
765
                rqod = self.vts[roid].get('qod')
766
767
            rname = self.vts[roid].get('name')
768
769
            if msg[0] == 'ERRMSG':
770
                self.add_scan_error(
771
                    scan_id,
772
                    host=host_aux,
773
                    name=rname,
774
                    value=msg[4],
775
                    port=msg[2],
776
                )
777
778
            if msg[0] == 'LOG':
779
                self.add_scan_log(
780
                    scan_id,
781
                    host=host_aux,
782
                    name=rname,
783
                    value=msg[4],
784
                    port=msg[2],
785
                    qod=rqod,
786
                    test_id=roid,
787
                )
788
789
            if msg[0] == 'HOST_DETAIL':
790
                self.add_scan_host_detail(
791
                    scan_id,
792
                    host=host_aux,
793
                    name=rname,
794
                    value=msg[4],
795
                )
796
797
            if msg[0] == 'ALARM':
798
                rseverity = self.get_severity_score(roid)
799
                self.add_scan_alarm(
800
                    scan_id,
801
                    host=host_aux,
802
                    name=rname,
803
                    value=msg[4],
804
                    port=msg[2],
805
                    test_id=roid,
806
                    severity=rseverity,
807
                    qod=rqod,
808
                )
809
810
            res = self.openvas_db.get_result()
811
812
    def get_openvas_timestamp_scan_host(self, scan_id, target):
813
        """ Get start and end timestamp of a host scan from redis kb. """
814
        timestamp = self.openvas_db.get_host_scan_scan_end_time()
815
        if timestamp:
816
            self.add_scan_log(scan_id, host=target, name='HOST_END',
817
                              value=timestamp)
818
            return
819
        timestamp = self.openvas_db.get_host_scan_scan_start_time()
820
        if timestamp:
821
            self.add_scan_log(scan_id, host=target, name='HOST_START',
822
                              value=timestamp)
823
            return
824
825
    def scan_is_finished(self, scan_id):
826
        """ Check if the scan has finished. """
827
        status = self.openvas_db.get_single_item('internal/%s' % scan_id)
828
        return status == 'finished'
829
830
    def scan_is_stopped(self, scan_id):
831
        """ Check if the parent process has received the stop_scan order.
832
        @in scan_id: ID to identify the scan to be stopped.
833
        @return 1 if yes, None in other case.
834
        """
835
        ctx = self.openvas_db.kb_connect(dbnum=self.main_kbindex)
836
        self.openvas_db.set_redisctx(ctx)
837
        status = self.openvas_db.get_single_item('internal/%s' % scan_id)
838
        return status == 'stop_all'
839
840
    def stop_scan_cleanup(self, global_scan_id):
841
        """ Set a key in redis to indicate the wrapper is stopped.
842
        It is done through redis because it is a new multiprocess
843
        instance and it is not possible to reach the variables
844
        of the grandchild process. Send SIGUSR2 to openvas to stop
845
        each running scan."""
846
        ctx = self.openvas_db.kb_connect()
847
        for current_kbi in range(0, self.openvas_db.max_dbindex):
848
            self.openvas_db.select_kb(ctx, str(current_kbi), set_global=True)
849
            scan_id = self.openvas_db.get_single_item(
850
                'internal/%s/globalscanid' % global_scan_id)
851
            if scan_id:
852
                self.openvas_db.set_single_item('internal/%s' % scan_id,
853
                                           ['stop_all', ])
854
                ovas_pid = self.openvas_db.get_single_item('internal/ovas_pid')
855
                parent = psutil.Process(int(ovas_pid))
856
                self.openvas_db.release_db(current_kbi)
857
                parent.send_signal(signal.SIGUSR2)
858
                logger.debug('Stopping process: {0}'.format(parent))
859
860
    def get_vts_in_groups(self, filters):
861
        """ Return a list of vts which match with the given filter.
862
863
        @input filters A list of filters. Each filter has key, operator and
864
                       a value. They are separated by a space.
865
                       Supported keys: family
866
        @return Return a list of vts which match with the given filter.
867
        """
868
        vts_list = list()
869
        families = dict()
870
        for oid in self.vts:
871
            family = self.vts[oid]['custom'].get('family')
872
            if family not in families:
873
                families[family] = list()
874
            families[family].append(oid)
875
876
        for elem in filters:
877
            key, value = elem.split('=')
878
            if key == 'family' and value in families:
879
                vts_list.extend(families[value])
880
        return vts_list
881
882
    def get_vt_param_type(self, vtid, vt_param_id):
883
        """ Return the type of the vt parameter from the vts dictionary. """
884
        vt_params_list = self.vts[vtid].get("vt_params")
885
        if vt_params_list.get(vt_param_id):
886
            return vt_params_list[vt_param_id]["type"]
887
        return False
888
889
    @staticmethod
890
    def check_param_type(vt_param_value, param_type):
891
        """ Check if the value of a vt parameter matches with
892
        the type founded.
893
        """
894
        if (param_type in ['entry',
895
                           'file',
896
                           'password',
897
                           'radio',
898
                           'sshlogin', ] and isinstance(vt_param_value, str)):
899
            return None
900
        elif (param_type == 'checkbox' and
901
              (vt_param_value == 'yes' or vt_param_value == 'no')):
902
            return None
903
        elif param_type == 'integer':
904
            try:
905
                int(vt_param_value)
906
            except ValueError:
907
                return 1
908
            return None
909
910
        return 1
911
912
    def process_vts(self, vts):
913
        """ Add single VTs and their parameters. """
914
        vts_list = []
915
        vts_params = []
916
        vtgroups = vts.pop('vt_groups')
917
918
        if vtgroups:
919
            vts_list = self.get_vts_in_groups(vtgroups)
920
921
        for vtid, vt_params in vts.items():
922
            vts_list.append(vtid)
923
            for vt_param_id, vt_param_value in vt_params.items():
924
                param_type = self.get_vt_param_type(vtid, vt_param_id)
925
                if not param_type:
926
                    logger.debug('The vt parameter %s for %s could not be loaded.',
927
                                 vt_param_id, vtid)
928
                    continue
929
                if vt_param_id == 'timeout':
930
                    type_aux = 'integer'
931
                else:
932
                    type_aux = param_type
933
                if self.check_param_type(vt_param_value, type_aux):
934
                    logger.debug('Expected {} type for parameter value {}'
935
                                 .format(type_aux, str(vt_param_value)))
936
                param = ["{0}:{1}:{2}".format(vtid, param_type,
937
                                               vt_param_id),
938
                         str(vt_param_value)]
939
                vts_params.append(param)
940
        return vts_list, vts_params
941
942
    @staticmethod
943
    def build_credentials_as_prefs(credentials):
944
        """ Parse the credential dictionary.
945
        @param credentials: Dictionary with the credentials.
946
947
        @return A list with the credentials in string format to be
948
                added to the redis KB.
949
        """
950
        cred_prefs_list = []
951
        for credential in credentials.items():
952
            service = credential[0]
953
            cred_params = credentials.get(service)
954
            cred_type = cred_params.get('type', '')
955
            username = cred_params.get('username', '')
956
            password = cred_params.get('password', '')
957
958
            if service == 'ssh':
959
                port = cred_params.get('port', '')
960
                cred_prefs_list.append('auth_port_ssh|||' +
961
                                       '{0}'.format(port))
962
                cred_prefs_list.append('SSH Authorization[entry]:SSH login ' +
963
                                       'name:|||{0}'.format(username))
964
                if cred_type == 'up':
965
                    cred_prefs_list.append('SSH Authorization[password]:' +
966
                                           'SSH password (unsafe!):|||' +
967
                                           '{0}'.format(password))
968
                else:
969
                    private = cred_params.get('private', '')
970
                    cred_prefs_list.append('SSH Authorization[password]:' +
971
                                           'SSH key passphrase:|||' +
972
                                           '{0}'.format(password))
973
                    cred_prefs_list.append('SSH Authorization[file]:' +
974
                                           'SSH private key:|||' +
975
                                           '{0}'.format(private))
976
            if service == 'smb':
977
                cred_prefs_list.append('SMB Authorization[entry]:SMB login:' +
978
                                       '|||{0}'.format(username))
979
                cred_prefs_list.append('SMB Authorization[password]:' +
980
                                       'SMB password :|||' +
981
                                       '{0}'.format(password))
982
            if service == 'esxi':
983
                cred_prefs_list.append('ESXi Authorization[entry]:ESXi login ' +
984
                                       'name:|||{0}'.format(username))
985
                cred_prefs_list.append('ESXi Authorization[password]:' +
986
                                       'ESXi login password:|||' +
987
                                       '{0}'.format(password))
988
989
            if service == 'snmp':
990
                community = cred_params.get('community', '')
991
                auth_algorithm = cred_params.get('auth_algorithm', '')
992
                privacy_password = cred_params.get('privacy_password', '')
993
                privacy_algorithm = cred_params.get('privacy_algorithm', '')
994
995
                cred_prefs_list.append('SNMP Authorization[password]:' +
996
                                       'SNMP Community:' +
997
                                       '{0}'.format(community))
998
                cred_prefs_list.append('SNMP Authorization[entry]:' +
999
                                       'SNMPv3 Username:' +
1000
                                       '{0}'.format(username))
1001
                cred_prefs_list.append('SNMP Authorization[password]:' +
1002
                                       'SNMPv3 Password:' +
1003
                                       '{0}'.format(password))
1004
                cred_prefs_list.append('SNMP Authorization[radio]:' +
1005
                                       'SNMPv3 Authentication Algorithm:' +
1006
                                       '{0}'.format(auth_algorithm))
1007
                cred_prefs_list.append('SNMP Authorization[password]:' +
1008
                                       'SNMPv3 Privacy Password:' +
1009
                                       '{0}'.format(privacy_password))
1010
                cred_prefs_list.append('SNMP Authorization[radio]:' +
1011
                                       'SNMPv3 Privacy Algorithm:' +
1012
                                       '{0}'.format(privacy_algorithm))
1013
1014
        return cred_prefs_list
1015
1016
    def exec_scan(self, scan_id, target):
1017
        """ Starts the OpenVAS scanner for scan_id scan. """
1018
        if self.pending_feed:
1019
            logger.info(
1020
                '%s: There is a pending feed update. '
1021
                'The scan can not be started.' % scan_id)
1022
            self.add_scan_error(
1023
                scan_id, name='', host=target,
1024
                value=('It was not possible to start the scan,'
1025
                'because a pending feed update. Please try later'))
1026
            return 2
1027
1028
        ports = self.get_scan_ports(scan_id, target)
1029
        if not ports:
1030
            self.add_scan_error(scan_id, name='', host=target,
1031
                                value='No port list defined.')
1032
            return 2
1033
1034
        # Get scan options
1035
        options = self.get_scan_options(scan_id)
1036
        prefs_val = []
1037
        ctx = self.openvas_db.kb_new()
1038
        self.openvas_db.set_redisctx(ctx)
1039
        self.main_kbindex = self.openvas_db.db_index
1040
1041
        # To avoid interference between scan process during a parallel scanning
1042
        # new uuid is used internally for each scan.
1043
        openvas_scan_id = str(uuid.uuid4())
1044
        self.openvas_db.add_single_item(
1045
            'internal/%s' % openvas_scan_id, ['new'])
1046
        self.openvas_db.add_single_item(
1047
            'internal/%s/globalscanid' % scan_id, [openvas_scan_id])
1048
1049
        # Set scan preferences
1050
        for key, value in options.items():
1051
            item_type = ''
1052
            if key in OSPD_PARAMS:
1053
                item_type = OSPD_PARAMS[key].get('type')
1054
            if item_type == 'boolean':
1055
                val =  _from_bool_to_str(value)
1056
            else:
1057
                val = str(value)
1058
            prefs_val.append(key + "|||" + val)
1059
        self.openvas_db.add_single_item(
1060
            'internal/%s/scanprefs' % openvas_scan_id, prefs_val)
1061
1062
        # Store main_kbindex as global preference
1063
        ov_maindbid = ('ov_maindbid|||%d' % self.main_kbindex)
1064
        self.openvas_db.add_single_item(
1065
            'internal/%s/scanprefs' % openvas_scan_id, [ov_maindbid])
1066
1067
        # Set target
1068
        target_aux = ('TARGET|||%s' % target)
1069
        self.openvas_db.add_single_item(
1070
            'internal/%s/scanprefs' % openvas_scan_id, [target_aux])
1071
        # Set port range
1072
        port_range = ('port_range|||%s' % ports)
1073
        self.openvas_db.add_single_item(
1074
            'internal/%s/scanprefs' % openvas_scan_id, [port_range])
1075
1076
        # Set credentials
1077
        credentials = self.get_scan_credentials(scan_id, target)
1078
        if credentials:
1079
            cred_prefs = self.build_credentials_as_prefs(credentials)
1080
            self.openvas_db.add_single_item(
1081
                'internal/%s/scanprefs' % openvas_scan_id, cred_prefs)
1082
1083
        # Set plugins to run
1084
        nvts = self.get_scan_vts(scan_id)
1085
        if nvts != '':
1086
            nvts_list, nvts_params = self.process_vts(nvts)
1087
            # Add nvts list
1088
            separ = ';'
1089
            plugin_list = 'plugin_set|||%s' % separ.join(nvts_list)
1090
            self.openvas_db.add_single_item(
1091
                'internal/%s/scanprefs' % openvas_scan_id, [plugin_list])
1092
            # Add nvts parameters
1093
            for elem in nvts_params:
1094
                item = '%s|||%s' % (elem[0], elem[1])
1095
                self.openvas_db.add_single_item(
1096
                    'internal/%s/scanprefs' % openvas_scan_id, [item])
1097
        else:
1098
            self.openvas_db.release_db(self.main_kbindex)
1099
            self.add_scan_error(scan_id, name='', host=target,
1100
                                value='No VTS to run.')
1101
            return 2
1102
1103
        # Create a general log entry about executing OpenVAS
1104
        # It is important to send at least one result, otherwise
1105
        # the host details won't be stored.
1106
        self.add_scan_log(scan_id, host=target, name='OpenVAS summary',
1107
                          value='An OpenVAS Scanner was started for %s.'
1108
                          % target)
1109
1110
        self.add_scan_log(scan_id, host=target, name='KB location Found',
1111
                          value='KB location path was found: %s.'
1112
                          % self.openvas_db.db_address)
1113
1114
        self.add_scan_log(scan_id, host=target, name='Feed Update',
1115
                          value='Feed version: %s.'
1116
                          % self.nvti.get_feed_version())
1117
1118
        cmd = ['openvassd', '--scan-start', openvas_scan_id]
1119
        try:
1120
            result = subprocess.Popen(cmd, shell=False)
1121
        except OSError:
1122
            # the command is not available
1123
            return False
1124
1125
        ovas_pid = result.pid
1126
        logger.debug('pid = {0}'.format(ovas_pid))
1127
        self.openvas_db.add_single_item('internal/ovas_pid', [ovas_pid])
1128
1129
        # Wait until the scanner starts and loads all the preferences.
1130
        while self.openvas_db.get_single_item('internal/'+ openvas_scan_id) == 'new':
1131
            time.sleep(1)
1132
1133
        no_id_found = False
1134
        while True:
1135
            time.sleep(3)
1136
1137
            # Check if the client stopped the whole scan
1138
            if self.scan_is_stopped(openvas_scan_id):
1139
                return 1
1140
1141
            ctx = self.openvas_db.kb_connect(self.main_kbindex)
1142
            self.openvas_db.set_redisctx(ctx)
1143
            dbs = self.openvas_db.get_list_item('internal/dbindex')
1144
            for i in list(dbs):
1145
                if i == self.main_kbindex:
1146
                    continue
1147
                self.openvas_db.select_kb(ctx, str(i), set_global=True)
1148
                id_aux = self.openvas_db.get_single_item('internal/scan_id')
1149
                if not id_aux:
1150
                    continue
1151
                if id_aux == openvas_scan_id:
1152
                    no_id_found = False
1153
                    self.get_openvas_timestamp_scan_host(scan_id, target)
1154
                    self.get_openvas_result(scan_id)
1155
                    self.get_openvas_status(scan_id, target)
1156
                    if self.scan_is_finished(openvas_scan_id):
1157
                        current_host=self.openvas_db.get_host_ip()
1158
                        self.set_scan_host_finished(
1159
                            scan_id, target, current_host)
1160
                        self.openvas_db.select_kb(
1161
                            ctx, str(self.main_kbindex), set_global=False)
1162
                        self.openvas_db.remove_list_item('internal/dbindex', i)
1163
                        self.openvas_db.release_db(i)
1164
1165
            # Scan end. No kb in use for this scan id
1166
            if no_id_found:
1167
                break
1168
            no_id_found = True
1169
1170
        # Delete keys from KB related to this scan task.
1171
        self.openvas_db.release_db(self.main_kbindex)
1172
        return 1
1173
1174
1175
def main():
1176
    """ OSP openvas main function. """
1177
    daemon_main('OSPD - openvas wrapper', OSPDopenvas)
1178