Passed
Pull Request — master (#48)
by Juan José
01:23
created

OSPDopenvas.get_refs_vt_as_xml_str()   B

Complexity

Conditions 8

Size

Total Lines 32
Code Lines 23

Duplication

Lines 32
Ratio 100 %

Importance

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