Completed
Push — master ( 7df86a...7fe335 )
by
unknown
13s queued 11s
created

ospd_openvas.daemon.OSPDopenvas.parse_param()   B

Complexity

Conditions 8

Size

Total Lines 22
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 19
nop 1
dl 0
loc 22
rs 7.3333
c 0
b 0
f 0
1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2019 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
# pylint: disable=too-many-lines
21
22
""" Setup for the OSP OpenVAS Server. """
23
24
import logging
25
import subprocess
26
import time
27
import uuid
28
import binascii
29
30
from typing import Optional, Dict, List, Tuple
31
from datetime import datetime
32
from base64 import b64decode
33
34
from pathlib import Path
35
from os import geteuid
36
from lxml.etree import tostring, SubElement, Element
37
38
import psutil
39
40
from ospd.errors import OspdError
41
from ospd.ospd import OSPDaemon
42
from ospd.main import main as daemon_main
43
from ospd.cvss import CVSS
44
from ospd.vtfilter import VtsFilter
45
46
from ospd_openvas import __version__
47
from ospd_openvas.errors import OspdOpenvasError
48
49
from ospd_openvas.nvticache import NVTICache
50
from ospd_openvas.db import OpenvasDB
51
52
logger = logging.getLogger(__name__)
53
54
55
OSPD_DESC = """
56
This scanner runs OpenVAS to scan the target hosts.
57
58
OpenVAS (Open Vulnerability Assessment Scanner) is a powerful scanner
59
for vulnerabilities in IT infrastrucutres. The capabilities include
60
unauthenticated scanning as well as authenticated scanning for
61
various types of systems and services.
62
63
For more details about OpenVAS see:
64
http://www.openvas.org/
65
66
The current version of ospd-openvas is a simple frame, which sends
67
the server parameters to the Greenbone Vulnerability Manager daemon (GVMd) and
68
checks the existence of OpenVAS binary. But it can not run scans yet.
69
"""
70
71
OSPD_PARAMS = {
72
    'auto_enable_dependencies': {
73
        'type': 'boolean',
74
        'name': 'auto_enable_dependencies',
75
        'default': 1,
76
        'mandatory': 1,
77
        'description': 'Automatically enable the plugins that are depended on',
78
    },
79
    'cgi_path': {
80
        'type': 'string',
81
        'name': 'cgi_path',
82
        'default': '/cgi-bin:/scripts',
83
        'mandatory': 1,
84
        'description': 'Look for default CGIs in /cgi-bin and /scripts',
85
    },
86
    'checks_read_timeout': {
87
        'type': 'integer',
88
        'name': 'checks_read_timeout',
89
        'default': 5,
90
        'mandatory': 1,
91
        'description': (
92
            'Number  of seconds that the security checks will '
93
            + 'wait for when doing a recv()'
94
        ),
95
    },
96
    'drop_privileges': {
97
        'type': 'boolean',
98
        'name': 'drop_privileges',
99
        'default': 0,
100
        'mandatory': 1,
101
        'description': '',
102
    },
103
    'network_scan': {
104
        'type': 'boolean',
105
        'name': 'network_scan',
106
        'default': 0,
107
        'mandatory': 1,
108
        'description': '',
109
    },
110
    'non_simult_ports': {
111
        'type': 'string',
112
        'name': 'non_simult_ports',
113
        'default': '139, 445, 3389, Services/irc',
114
        'mandatory': 1,
115
        'description': (
116
            'Prevent to make two connections on the same given '
117
            + 'ports at the same time.'
118
        ),
119
    },
120
    'open_sock_max_attempts': {
121
        'type': 'integer',
122
        'name': 'open_sock_max_attempts',
123
        'default': 5,
124
        'mandatory': 0,
125
        'description': (
126
            'Number of unsuccessful retries to open the socket '
127
            + 'before to set the port as closed.'
128
        ),
129
    },
130
    'timeout_retry': {
131
        'type': 'integer',
132
        'name': 'timeout_retry',
133
        'default': 5,
134
        'mandatory': 0,
135
        'description': (
136
            'Number of retries when a socket connection attempt ' + 'timesout.'
137
        ),
138
    },
139
    'optimize_test': {
140
        'type': 'integer',
141
        'name': 'optimize_test',
142
        'default': 5,
143
        'mandatory': 0,
144
        'description': (
145
            'By default, openvas does not trust the remote ' + 'host banners.'
146
        ),
147
    },
148
    'plugins_timeout': {
149
        'type': 'integer',
150
        'name': 'plugins_timeout',
151
        'default': 5,
152
        'mandatory': 0,
153
        'description': 'This is the maximum lifetime, in seconds of a plugin.',
154
    },
155
    'report_host_details': {
156
        'type': 'boolean',
157
        'name': 'report_host_details',
158
        'default': 1,
159
        'mandatory': 1,
160
        'description': '',
161
    },
162
    'safe_checks': {
163
        'type': 'boolean',
164
        'name': 'safe_checks',
165
        'default': 1,
166
        'mandatory': 1,
167
        'description': (
168
            'Disable the plugins with potential to crash '
169
            + 'the remote services'
170
        ),
171
    },
172
    'scanner_plugins_timeout': {
173
        'type': 'integer',
174
        'name': 'scanner_plugins_timeout',
175
        'default': 36000,
176
        'mandatory': 1,
177
        'description': 'Like plugins_timeout, but for ACT_SCANNER plugins.',
178
    },
179
    'time_between_request': {
180
        'type': 'integer',
181
        'name': 'time_between_request',
182
        'default': 0,
183
        'mandatory': 0,
184
        'description': (
185
            'Allow to set a wait time between two actions '
186
            + '(open, send, close).'
187
        ),
188
    },
189
    'unscanned_closed': {
190
        'type': 'boolean',
191
        'name': 'unscanned_closed',
192
        'default': 1,
193
        'mandatory': 1,
194
        'description': '',
195
    },
196
    'unscanned_closed_udp': {
197
        'type': 'boolean',
198
        'name': 'unscanned_closed_udp',
199
        'default': 1,
200
        'mandatory': 1,
201
        'description': '',
202
    },
203
    'use_mac_addr': {
204
        'type': 'boolean',
205
        'name': 'use_mac_addr',
206
        'default': 0,
207
        'mandatory': 0,
208
        'description': 'To test the local network. '
209
        + 'Hosts will be referred to by their MAC address.',
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 15 spaces).
Loading history...
210
    },
211
    'vhosts': {
212
        'type': 'string',
213
        'name': 'vhosts',
214
        'default': '',
215
        'mandatory': 0,
216
        'description': '',
217
    },
218
    'vhosts_ip': {
219
        'type': 'string',
220
        'name': 'vhosts_ip',
221
        'default': '',
222
        'mandatory': 0,
223
        'description': '',
224
    },
225
}
226
227
OID_SSH_AUTH = "1.3.6.1.4.1.25623.1.0.103591"
228
OID_SMB_AUTH = "1.3.6.1.4.1.25623.1.0.90023"
229
OID_ESXI_AUTH = "1.3.6.1.4.1.25623.1.0.105058"
230
OID_SNMP_AUTH = "1.3.6.1.4.1.25623.1.0.105076"
231
232
233
def _from_bool_to_str(value: int) -> str:
234
    """ The OpenVAS scanner use yes and no as boolean values, whereas ospd
235
    uses 1 and 0."""
236
    return 'yes' if value == 1 else 'no'
237
238
239
class OpenVasVtsFilter(VtsFilter):
240
    """ Methods to overwrite the ones in the original class.
241
    Each method formats the value to be compatible with the filter
242
    """
243
244
    def format_vt_modification_time(self, value: str) -> str:
245
        """ Convert the string seconds since epoch into a 19 character
246
        string representing YearMonthDayHourMinuteSecond,
247
        e.g. 20190319122532. This always refers to UTC.
248
        """
249
250
        return datetime.utcfromtimestamp(int(value)).strftime("%Y%m%d%H%M%S")
251
252
253
class OSPDopenvas(OSPDaemon):
254
255
    """ Class for ospd-openvas daemon. """
256
257
    def __init__(self, *, niceness=None, **kwargs):
258
        """ Initializes the ospd-openvas daemon's internal data. """
259
260
        super().__init__(customvtfilter=OpenVasVtsFilter(), **kwargs)
261
262
        self.server_version = __version__
263
264
        self._niceness = str(niceness)
265
266
        self.scanner_info['name'] = 'openvas'
267
        self.scanner_info['version'] = ''  # achieved during self.check()
268
        self.scanner_info['description'] = OSPD_DESC
269
270
        for name, param in OSPD_PARAMS.items():
271
            self.add_scanner_param(name, param)
272
273
        self._sudo_available = None
274
        self._is_running_as_root = None
275
276
        self.scan_only_params = dict()
277
278
        self.main_kbindex = None
279
280
        self.openvas_db = OpenvasDB()
281
282
        self.nvti = NVTICache(self.openvas_db)
283
        self.nvti.set_nvticache_str()
284
285
        self.pending_feed = None
286
287
    def init(self):
288
        self.openvas_db.db_init()
289
290
        ctx = self.openvas_db.db_find(self.nvti.NVTICACHE_STR)
291
292
        if not ctx:
293
            self.redis_nvticache_init()
294
            ctx = self.openvas_db.db_find(self.nvti.NVTICACHE_STR)
295
296
        self.openvas_db.set_redisctx(ctx)
297
298
        self.load_vts()
299
300
    def parse_param(self):
301
        """ Set OSPD_PARAMS with the params taken from the openvas_scanner. """
302
        bool_dict = {'no': 0, 'yes': 1}
303
304
        result = subprocess.check_output(
305
            ['openvas', '-s'], stderr=subprocess.STDOUT
306
        )
307
        result = result.decode('ascii')
308
        param_list = dict()
309
        for conf in result.split('\n'):
310
            elem = conf.split('=')
311
            if len(elem) == 2:
312
                value = str.strip(elem[1])
313
                if str.strip(elem[1]) in bool_dict:
314
                    value = bool_dict[value]
315
                param_list[str.strip(elem[0])] = value
316
        for elem in OSPD_PARAMS:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable OSPD_PARAMS does not seem to be defined.
Loading history...
317
            if elem in param_list:
318
                OSPD_PARAMS[elem]['default'] = param_list[elem]
319
        for elem in param_list:
320
            if elem not in OSPD_PARAMS:
321
                self.scan_only_params[elem] = param_list[elem]
322
323
    def redis_nvticache_init(self):
324
        """ Loads NVT's metadata into Redis DB. """
325
        try:
326
            logger.debug('Loading NVTs in Redis DB')
327
            subprocess.check_call(['openvas', '--update-vt-info'])
328
        except subprocess.CalledProcessError as err:
329
            logger.error('OpenVAS Scanner failed to load NVTs. %s', err)
330
331
    def feed_is_outdated(self, current_feed: str) -> Optional[bool]:
332
        """ Compare the current feed with the one in the disk.
333
334
        Return:
335
            False if there is no new feed.
336
            True if the feed version in disk is newer than the feed in
337
            redis cache.
338
            None if there is no feed
339
            the disk.
340
        """
341
        plugins_folder = self.scan_only_params.get('plugins_folder')
342
        if not plugins_folder:
343
            raise OspdOpenvasError("Error: Path to plugins folder not found.")
344
345
        feed_info_file = Path(plugins_folder) / 'plugin_feed_info.inc'
346
        if not feed_info_file.exists():
347
            self.parse_param()
348
            msg = 'Plugins feed file %s not found.' % feed_info_file
349
            logger.debug(msg)
350
            return None
351
352
        date = 0
353
        with open(str(feed_info_file)) as fcontent:
354
            for line in fcontent:
355
                if "PLUGIN_SET" in line:
356
                    date = line.split(' = ')[1]
357
                    date = date.replace(';', '')
358
                    date = date.replace('"', '')
359
        if int(current_feed) < int(date) or int(date) == 0:
360
            return True
361
        return False
362
363
    def check_feed(self):
364
        """ Check if there is a feed update. Wait until all the running
365
        scans finished. Set a flag to anounce there is a pending feed update,
366
        which avoid to start a new scan.
367
        """
368
        current_feed = self.nvti.get_feed_version()
369
        # Check if the feed is already accessible in the disk.
370
        if current_feed and self.feed_is_outdated(current_feed) is None:
371
            self.pending_feed = True
372
            return
373
374
        # Check if the nvticache in redis is outdated
375
        if not current_feed or self.feed_is_outdated(current_feed):
376
            self.redis_nvticache_init()
377
            ctx = self.openvas_db.db_find(self.nvti.NVTICACHE_STR)
378
            self.openvas_db.set_redisctx(ctx)
379
            self.pending_feed = True
380
381
        _running_scan = False
382
        for scan_id in self.scan_processes:
383
            if self.scan_processes[scan_id].is_alive():
384
                _running_scan = True
385
386
        # Check if the NVT dict is outdated
387
        if self.pending_feed:
388
            _pending_feed = True
389
        else:
390
            _pending_feed = (
391
                self.get_vts_version() != self.nvti.get_feed_version()
392
            )
393
394
        if _running_scan and _pending_feed:
395
            if not self.pending_feed:
396
                self.pending_feed = True
397
                logger.debug(
398
                    'There is a running scan. Therefore the feed '
399
                    'update will be performed later.'
400
                )
401
        elif not _running_scan and _pending_feed:
402
            self.vts = dict()
403
            self.load_vts()
404
405
    def scheduler(self):
406
        """This method is called periodically to run tasks."""
407
        self.check_feed()
408
409
    def load_vts(self):
410
        """ Load the NVT's metadata into the vts
411
        global  dictionary. """
412
        logger.debug('Loading vts in memory.')
413
        oids = dict(self.nvti.get_oids())
414
        for _filename, vt_id in oids.items():
415
            _vt_params = self.nvti.get_nvt_params(vt_id)
416
            _vt_refs = self.nvti.get_nvt_refs(vt_id)
417
            _custom = self.nvti.get_nvt_metadata(vt_id)
418
419
            if _vt_params is None or _custom is None:
420
                logger.warning('Error loading VTs in memory. Trying again...')
421
                return
422
423
            _name = _custom.pop('name')
424
            _vt_creation_time = _custom.pop('creation_date')
425
            _vt_modification_time = _custom.pop('last_modification')
426
427
            _summary = None
428
            _impact = None
429
            _affected = None
430
            _insight = None
431
            _solution = None
432
            _solution_t = None
433
            _solution_m = None
434
            _vuldetect = None
435
            _qod_t = None
436
            _qod_v = None
437
438
            if 'summary' in _custom:
439
                _summary = _custom.pop('summary')
440
            if 'impact' in _custom:
441
                _impact = _custom.pop('impact')
442
            if 'affected' in _custom:
443
                _affected = _custom.pop('affected')
444
            if 'insight' in _custom:
445
                _insight = _custom.pop('insight')
446
            if 'solution' in _custom:
447
                _solution = _custom.pop('solution')
448
                if 'solution_type' in _custom:
449
                    _solution_t = _custom.pop('solution_type')
450
                if 'solution_method' in _custom:
451
                    _solution_m = _custom.pop('solution_method')
452
453
            if 'vuldetect' in _custom:
454
                _vuldetect = _custom.pop('vuldetect')
455
            if 'qod_type' in _custom:
456
                _qod_t = _custom.pop('qod_type')
457
            elif 'qod' in _custom:
458
                _qod_v = _custom.pop('qod')
459
460
            _severity = dict()
461
            if 'severity_base_vector' in _custom:
462
                _severity_vector = _custom.pop('severity_base_vector')
463
            else:
464
                _severity_vector = _custom.pop('cvss_base_vector')
465
            _severity['severity_base_vector'] = _severity_vector
466
            if 'severity_type' in _custom:
467
                _severity_type = _custom.pop('severity_type')
468
            else:
469
                _severity_type = 'cvss_base_v2'
470
            _severity['severity_type'] = _severity_type
471
            if 'severity_origin' in _custom:
472
                _severity['severity_origin'] = _custom.pop('severity_origin')
473
474
            _vt_dependencies = list()
475
            if 'dependencies' in _custom:
476
                _deps = _custom.pop('dependencies')
477
                _deps_list = _deps.split(', ')
478
                for dep in _deps_list:
479
                    _vt_dependencies.append(oids.get('filename:' + dep))
480
481
            try:
482
                self.add_vt(
483
                    vt_id,
484
                    name=_name,
485
                    vt_params=_vt_params,
486
                    vt_refs=_vt_refs,
487
                    custom=_custom,
488
                    vt_creation_time=_vt_creation_time,
489
                    vt_modification_time=_vt_modification_time,
490
                    vt_dependencies=_vt_dependencies,
491
                    summary=_summary,
492
                    impact=_impact,
493
                    affected=_affected,
494
                    insight=_insight,
495
                    solution=_solution,
496
                    solution_t=_solution_t,
497
                    solution_m=_solution_m,
498
                    detection=_vuldetect,
499
                    qod_t=_qod_t,
500
                    qod_v=_qod_v,
501
                    severities=_severity,
502
                )
503
            except OspdError as e:
504
                logger.info("Error while adding vt. %s", e)
505
506
        _feed_version = self.nvti.get_feed_version()
507
        self.set_vts_version(vts_version=_feed_version)
508
        self.pending_feed = False
509
        logger.debug('Finish loading up vts.')
510
511
    @staticmethod
512
    def get_custom_vt_as_xml_str(vt_id: str, custom: Dict) -> str:
513
        """ Return an xml element with custom metadata formatted as string.
514
        Arguments:
515
            vt_id: VT OID. Only used for logging in error case.
516
            custom: Dictionary with the custom metadata.
517
        Return:
518
            Xml element as string.
519
        """
520
521
        _custom = Element('custom')
522
        for key, val in custom.items():
523
            xml_key = SubElement(_custom, key)
524
            try:
525
                xml_key.text = val
526
            except ValueError as e:
527
                logger.warning(
528
                    "Not possible to parse custom tag for vt %s: %s", vt_id, e
529
                )
530
        return tostring(_custom).decode('utf-8')
531
532
    @staticmethod
533
    def get_severities_vt_as_xml_str(vt_id: str, severities: Dict) -> str:
534
        """ Return an xml element with severities as string.
535
        Arguments:
536
            vt_id: VT OID. Only used for logging in error case.
537
            severities: Dictionary with the severities.
538
        Return:
539
            Xml element as string.
540
        """
541
        _severities = Element('severities')
542
        _severity = SubElement(_severities, 'severity')
543
        if 'severity_base_vector' in severities:
544
            try:
545
                _severity.text = severities.get('severity_base_vector')
546
            except ValueError as e:
547
                logger.warning(
548
                    "Not possible to parse severity tag for vt %s: %s", vt_id, e
549
                )
550
        if 'severity_origin' in severities:
551
            _severity.set('origin', severities.get('severity_origin'))
552
        if 'severity_type' in severities:
553
            _severity.set('type', severities.get('severity_type'))
554
555
        return tostring(_severities).decode('utf-8')
556
557
    @staticmethod
558
    def get_params_vt_as_xml_str(vt_id: str, vt_params: Dict) -> str:
559
        """ Return an xml element with params formatted as string.
560
        Arguments:
561
            vt_id: VT OID. Only used for logging in error case.
562
            vt_params: Dictionary with the VT parameters.
563
        Return:
564
            Xml element as string.
565
        """
566
        vt_params_xml = Element('params')
567
        for _pref_id, prefs in vt_params.items():
568
            vt_param = Element('param')
569
            vt_param.set('type', prefs['type'])
570
            vt_param.set('id', _pref_id)
571
            xml_name = SubElement(vt_param, 'name')
572
            try:
573
                xml_name.text = prefs['name']
574
            except ValueError as e:
575
                logger.warning(
576
                    "Not possible to parse parameter for vt %s: %s", vt_id, e
577
                )
578
            if prefs['default']:
579
                xml_def = SubElement(vt_param, 'default')
580
                try:
581
                    xml_def.text = prefs['default']
582
                except ValueError as e:
583
                    logger.warning(
584
                        "Not possible to parse default parameter for vt %s: %s",
585
                        vt_id,
586
                        e,
587
                    )
588
            vt_params_xml.append(vt_param)
589
590
        return tostring(vt_params_xml).decode('utf-8')
591
592
    @staticmethod
593
    def get_refs_vt_as_xml_str(vt_id: str, vt_refs: Dict) -> str:
594
        """ Return an xml element with references formatted as string.
595
        Arguments:
596
            vt_id: VT OID. Only used for logging in error case.
597
            vt_refs: Dictionary with the VT references.
598
        Return:
599
            Xml element as string.
600
        """
601
        vt_refs_xml = Element('refs')
602
        for ref_type, ref_values in vt_refs.items():
603
            for value in ref_values:
604
                vt_ref = Element('ref')
605
                if ref_type == "xref" and value:
606
                    for xref in value.split(', '):
607
                        try:
608
                            _type, _id = xref.split(':', 1)
609
                        except ValueError:
610
                            logger.error(
611
                                'Not possible to parse xref %s for vt %s',
612
                                xref,
613
                                vt_id,
614
                            )
615
                            continue
616
                        vt_ref.set('type', _type.lower())
617
                        vt_ref.set('id', _id)
618
                elif value:
619
                    vt_ref.set('type', ref_type.lower())
620
                    vt_ref.set('id', value)
621
                else:
622
                    continue
623
                vt_refs_xml.append(vt_ref)
624
625
        return tostring(vt_refs_xml).decode('utf-8')
626
627
    @staticmethod
628
    def get_dependencies_vt_as_xml_str(
629
        vt_id: str, vt_dependencies: List
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
630
    ) -> str:
631
        """ Return  an xml element with dependencies as string.
632
        Arguments:
633
            vt_id: VT OID. Only used for logging in error case.
634
            vt_dependencies: List with the VT dependencies.
635
        Return:
636
            Xml element as string.
637
        """
638
        vt_deps_xml = Element('dependencies')
639
        for dep in vt_dependencies:
640
            _vt_dep = Element('dependency')
641
            try:
642
                _vt_dep.set('vt_id', dep)
643
            except (ValueError, TypeError):
644
                logger.error(
645
                    'Not possible to add dependency %s for vt %s', dep, vt_id
646
                )
647
                continue
648
            vt_deps_xml.append(_vt_dep)
649
650
        return tostring(vt_deps_xml).decode('utf-8')
651
652
    @staticmethod
653
    def get_creation_time_vt_as_xml_str(
654
        vt_id: str, vt_creation_time: str
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
655
    ) -> str:
656
        """ Return creation time as string.
657
        Arguments:
658
            vt_id: VT OID. Only used for logging in error case.
659
            vt_creation_time: String with the VT creation time.
660
        Return:
661
           Xml element as string.
662
        """
663
        _time = Element('creation_time')
664
        try:
665
            _time.text = vt_creation_time
666
        except ValueError as e:
667
            logger.warning(
668
                "Not possible to parse creation time for vt %s: %s", vt_id, e
669
            )
670
        return tostring(_time).decode('utf-8')
671
672
    @staticmethod
673
    def get_modification_time_vt_as_xml_str(
674
        vt_id: str, vt_modification_time: str
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
675
    ) -> str:
676
        """ Return modification time as string.
677
        Arguments:
678
            vt_id: VT OID. Only used for logging in error case.
679
            vt_modification_time: String with the VT modification time.
680
        Return:
681
            Xml element as string.
682
        """
683
        _time = Element('modification_time')
684
        try:
685
            _time.text = vt_modification_time
686
        except ValueError as e:
687
            logger.warning(
688
                "Not possible to parse modification time for vt %s: %s",
689
                vt_id,
690
                e,
691
            )
692
        return tostring(_time).decode('utf-8')
693
694
    @staticmethod
695
    def get_summary_vt_as_xml_str(vt_id: str, summary: str) -> str:
696
        """ Return summary as string.
697
        Arguments:
698
            vt_id: VT OID. Only used for logging in error case.
699
            summary: String with a VT summary.
700
        Return:
701
            Xml element as string.
702
        """
703
        _summary = Element('summary')
704
        try:
705
            _summary.text = summary
706
        except ValueError as e:
707
            logger.warning(
708
                "Not possible to parse summary tag for vt %s: %s", vt_id, e
709
            )
710
        return tostring(_summary).decode('utf-8')
711
712
    @staticmethod
713
    def get_impact_vt_as_xml_str(vt_id: str, impact) -> str:
714
        """ Return impact as string.
715
716
        Arguments:
717
            vt_id (str): VT OID. Only used for logging in error case.
718
            impact (str): String which explain the vulneravility impact.
719
        Return:
720
            string: xml element as string.
721
        """
722
        _impact = Element('impact')
723
        try:
724
            _impact.text = impact
725
        except ValueError as e:
726
            logger.warning(
727
                "Not possible to parse impact tag for vt %s: %s", vt_id, e
728
            )
729
        return tostring(_impact).decode('utf-8')
730
731
    @staticmethod
732
    def get_affected_vt_as_xml_str(vt_id: str, affected: str) -> str:
733
        """ Return affected as string.
734
        Arguments:
735
            vt_id: VT OID. Only used for logging in error case.
736
            affected: String which explain what is affected.
737
        Return:
738
            Xml element as string.
739
        """
740
        _affected = Element('affected')
741
        try:
742
            _affected.text = affected
743
        except ValueError as e:
744
            logger.warning(
745
                "Not possible to parse affected tag for vt %s: %s", vt_id, e
746
            )
747
        return tostring(_affected).decode('utf-8')
748
749
    @staticmethod
750
    def get_insight_vt_as_xml_str(vt_id: str, insight: str) -> str:
751
        """ Return insight as string.
752
        Arguments:
753
            vt_id: VT OID. Only used for logging in error case.
754
            insight: String giving an insight of the vulnerability.
755
        Return:
756
            Xml element as string.
757
        """
758
        _insight = Element('insight')
759
        try:
760
            _insight.text = insight
761
        except ValueError as e:
762
            logger.warning(
763
                "Not possible to parse insight tag for vt %s: %s", vt_id, e
764
            )
765
        return tostring(_insight).decode('utf-8')
766
767
    @staticmethod
768
    def get_solution_vt_as_xml_str(
769
        vt_id: str,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
770
        solution: str,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
771
        solution_type: Optional[str] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
772
        solution_method: Optional[str] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
773
    ) -> str:
774
        """ Return solution as string.
775
        Arguments:
776
            vt_id: VT OID. Only used for logging in error case.
777
            solution: String giving a possible solution.
778
            solution_type: A solution type
779
            solution_method: A solution method
780
        Return:
781
            Xml element as string.
782
        """
783
        _solution = Element('solution')
784
        try:
785
            _solution.text = solution
786
        except ValueError as e:
787
            logger.warning(
788
                "Not possible to parse solution tag for vt %s: %s", vt_id, e
789
            )
790
        if solution_type:
791
            _solution.set('type', solution_type)
792
        if solution_method:
793
            _solution.set('method', solution_method)
794
        return tostring(_solution).decode('utf-8')
795
796
    @staticmethod
797
    def get_detection_vt_as_xml_str(
798
        vt_id: str,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
799
        detection: Optional[str] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
800
        qod_type: Optional[str] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
801
        qod: Optional[str] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
802
    ) -> str:
803
        """ Return detection as string.
804
        Arguments:
805
            vt_id: VT OID. Only used for logging in error case.
806
            detection: String which explain how the vulnerability
807
              was detected.
808
            qod_type: qod type.
809
            qod: qod value.
810
        Return:
811
            Xml element as string.
812
        """
813
        _detection = Element('detection')
814
        if detection:
815
            try:
816
                _detection.text = detection
817
            except ValueError as e:
818
                logger.warning(
819
                    "Not possible to parse detection tag for vt %s: %s",
820
                    vt_id,
821
                    e,
822
                )
823
        if qod_type:
824
            _detection.set('qod_type', qod_type)
825
        elif qod:
826
            _detection.set('qod', qod)
827
828
        return tostring(_detection).decode('utf-8')
829
830
    @property
831
    def is_running_as_root(self) -> bool:
832
        """ Check if it is running as root user."""
833
        if self._is_running_as_root is not None:
834
            return self._is_running_as_root
835
836
        self._is_running_as_root = False
837
        if geteuid() == 0:
838
            self._is_running_as_root = True
839
840
        return self._is_running_as_root
841
842
    @property
843
    def sudo_available(self) -> bool:
844
        """ Checks that sudo is available """
845
        if self._sudo_available is not None:
846
            return self._sudo_available
847
848
        if self.is_running_as_root:
849
            self._sudo_available = False
850
            return self._sudo_available
851
852
        try:
853
            subprocess.check_call(
854
                ['sudo', '-n', 'openvas', '-s'], stdout=subprocess.PIPE
855
            )
856
            self._sudo_available = True
857
        except (subprocess.SubprocessError, OSError) as e:
858
            logger.debug(
859
                'It was not possible to call openvas with sudo. '
860
                'The scanner will run as non-root user. Reason %s',
861
                e,
862
            )
863
            self._sudo_available = False
864
865
        return self._sudo_available
866
867
    def check(self) -> bool:
868
        """ Checks that openvas command line tool is found and
869
        is executable. """
870
        try:
871
            result = subprocess.check_output(
872
                ['openvas', '-V'], stderr=subprocess.STDOUT
873
            )
874
            result = result.decode('ascii')
875
        except OSError:
876
            # The command is not available
877
            return False
878
879
        if result is None:
880
            return False
881
882
        version = result.split('\n')
883
        if version[0].find('OpenVAS') < 0:
884
            return False
885
886
        self.parse_param()
887
        self.scanner_info['version'] = version[0]
888
889
        return True
890
891
    def update_progress(
892
        self, scan_id: str, target: str, current_host: str, msg: str
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
893
    ):
894
        """ Calculate percentage and update the scan status of a host
895
        for the progress bar.
896
        Arguments:
897
            scan_id: Scan ID to identify the current scan process.
898
            target: Target to be updated with the calculated
899
                          scan progress.
900
            current_host: Host in the target to be updated.
901
            msg: String with launched and total plugins.
902
        """
903
        try:
904
            launched, total = msg.split('/')
905
        except ValueError:
906
            return
907
        if float(total) == 0:
908
            return
909
        elif float(total) == -1:
910
            host_prog = 100
911
        else:
912
            host_prog = (float(launched) / float(total)) * 100
913
        self.set_scan_host_progress(scan_id, target, current_host, host_prog)
914
915
    def get_openvas_status(self, scan_id: str, target: str, current_host: str):
916
        """ Get all status entries from redis kb.
917
        Arguments:
918
            scan_id: Scan ID to identify the current scan.
919
            target: Target progress to be updated.
920
            current_host: Host to be updated.
921
        """
922
        res = self.openvas_db.get_status()
923
        while res:
924
            self.update_progress(scan_id, target, current_host, res)
925
            res = self.openvas_db.get_status()
926
927
    def get_severity_score(self, oid: str) -> Optional[float]:
928
        """ Return the severity score for the given oid.
929
        Arguments:
930
            oid: VT OID from which to get the severity vector
931
        Returns:
932
            The calculated cvss base value. None if there is no severity
933
            vector or severity type is not cvss base version 2.
934
        """
935
        severity_type = self.vts[oid]['severities'].get('severity_type')
936
        severity_vector = self.vts[oid]['severities'].get(
937
            'severity_base_vector'
938
        )
939
940
        if severity_type == "cvss_base_v2" and severity_vector:
941
            return CVSS.cvss_base_v2_value(severity_vector)
942
943
        return None
944
945
    def get_openvas_result(self, scan_id: str, current_host: str):
946
        """ Get all result entries from redis kb. """
947
        res = self.openvas_db.get_result()
948
        while res:
949
            msg = res.split('|||')
950
            roid = msg[3]
951
            rqod = ''
952
            rname = ''
953
            rhostname = msg[1] if msg[1] else ''
954
            host_is_dead = "Host dead" in msg[4]
955
956
            if not host_is_dead:
957
                if self.vts[roid].get('qod_type'):
958
                    qod_t = self.vts[roid].get('qod_type')
959
                    rqod = self.nvti.QOD_TYPES[qod_t]
960
                elif self.vts[roid].get('qod'):
961
                    rqod = self.vts[roid].get('qod')
962
963
                rname = self.vts[roid].get('name')
964
965
            if msg[0] == 'ERRMSG':
966
                self.add_scan_error(
967
                    scan_id,
968
                    host=current_host,
969
                    hostname=rhostname,
970
                    name=rname,
971
                    value=msg[4],
972
                    port=msg[2],
973
                )
974
975
            if msg[0] == 'LOG':
976
                self.add_scan_log(
977
                    scan_id,
978
                    host=current_host,
979
                    hostname=rhostname,
980
                    name=rname,
981
                    value=msg[4],
982
                    port=msg[2],
983
                    qod=rqod,
984
                    test_id=roid,
985
                )
986
987
            if msg[0] == 'HOST_DETAIL':
988
                self.add_scan_host_detail(
989
                    scan_id,
990
                    host=current_host,
991
                    hostname=rhostname,
992
                    name=rname,
993
                    value=msg[4],
994
                )
995
996
            if msg[0] == 'ALARM':
997
                rseverity = self.get_severity_score(roid)
998
                self.add_scan_alarm(
999
                    scan_id,
1000
                    host=current_host,
1001
                    hostname=rhostname,
1002
                    name=rname,
1003
                    value=msg[4],
1004
                    port=msg[2],
1005
                    test_id=roid,
1006
                    severity=rseverity,
1007
                    qod=rqod,
1008
                )
1009
1010
            res = self.openvas_db.get_result()
1011
1012
    def get_openvas_timestamp_scan_host(self, scan_id: str, target: str):
1013
        """ Get start and end timestamp of a host scan from redis kb. """
1014
        timestamp = self.openvas_db.get_host_scan_scan_end_time()
1015
        if timestamp:
1016
            self.add_scan_log(
1017
                scan_id, host=target, name='HOST_END', value=timestamp
1018
            )
1019
            return
1020
        timestamp = self.openvas_db.get_host_scan_scan_start_time()
1021
        if timestamp:
1022
            self.add_scan_log(
1023
                scan_id, host=target, name='HOST_START', value=timestamp
1024
            )
1025
            return
1026
1027
    def host_is_finished(self, scan_id: str) -> bool:
1028
        """ Check if the host has finished. """
1029
        status = self.openvas_db.get_single_item('internal/%s' % scan_id)
1030
        return status == 'finished'
1031
1032
    def target_is_finished(self, scan_id: str) -> bool:
1033
        """ Check if a target has finished. The scan id to be used is
1034
        the scan id passed to the openvas, is not the global scan id."""
1035
        ctx = self.openvas_db.kb_connect(dbnum=self.main_kbindex)
1036
        scan_id = self.openvas_db.get_single_item(
1037
            'internal/%s/globalscanid' % scan_id, ctx=ctx
1038
        )
1039
        status = self.openvas_db.get_single_item(
1040
            'internal/%s' % scan_id, ctx=ctx
1041
        )
1042
1043
        return status == 'finished' or status is None
1044
1045
    def scan_is_stopped(self, scan_id: str) -> bool:
1046
        """ Check if the parent process has received the stop_scan order.
1047
        @in scan_id: ID to identify the scan to be stopped.
1048
        @return 1 if yes, None in other case.
1049
        """
1050
        ctx = self.openvas_db.kb_connect(dbnum=self.main_kbindex)
1051
        self.openvas_db.set_redisctx(ctx)
1052
        status = self.openvas_db.get_single_item('internal/%s' % scan_id)
1053
        return status == 'stop_all'
1054
1055
    def stop_scan_cleanup(
1056
        self, global_scan_id: str
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1057
    ):  # pylint: disable=arguments-differ
1058
        """ Set a key in redis to indicate the wrapper is stopped.
1059
        It is done through redis because it is a new multiprocess
1060
        instance and it is not possible to reach the variables
1061
        of the grandchild process. Send SIGUSR2 to openvas to stop
1062
        each running scan."""
1063
        ctx = self.openvas_db.kb_connect()
1064
        for current_kbi in range(0, self.openvas_db.max_dbindex):
1065
            self.openvas_db.select_kb(ctx, str(current_kbi), set_global=True)
1066
            scan_id = self.openvas_db.get_single_item(
1067
                'internal/%s/globalscanid' % global_scan_id
1068
            )
1069
            if scan_id:
1070
                self.openvas_db.set_single_item(
1071
                    'internal/%s' % scan_id, ['stop_all']
1072
                )
1073
                ovas_pid = self.openvas_db.get_single_item('internal/ovas_pid')
1074
                parent = None
1075
                try:
1076
                    parent = psutil.Process(int(ovas_pid))
1077
                except psutil.NoSuchProcess:
1078
                    logger.debug(
1079
                        'Process with pid %s already stopped', ovas_pid
1080
                    )
1081
                except TypeError:
1082
                    logger.debug(
1083
                        'Scan with ID %s never started and stopped '
1084
                        'unexpectedly',
1085
                        scan_id,
1086
                    )
1087
1088
                if parent:
1089
                    cmd = ['openvas', '--scan-stop', scan_id]
1090
                    if not self.is_running_as_root and self.sudo_available:
1091
                        cmd = ['sudo', '-n'] + cmd
1092
1093
                    try:
1094
                        subprocess.Popen(cmd, shell=False)
1095
                    except OSError as e:
1096
                        # the command is not available
1097
                        logger.debug(
1098
                            'Not possible to Stopping process: %s.' 'Reason %s',
1099
                            parent,
1100
                            e,
1101
                        )
1102
                        return False
1103
1104
                    logger.debug('Stopping process: %s', parent)
1105
                    while parent:
1106
                        try:
1107
                            parent = psutil.Process(int(ovas_pid))
1108
                        except psutil.NoSuchProcess:
1109
                            parent = None
1110
1111
                self.openvas_db.release_db(current_kbi)
1112
                for host_kb in range(0, self.openvas_db.max_dbindex):
1113
                    self.openvas_db.select_kb(
1114
                        ctx, str(host_kb), set_global=True
1115
                    )
1116
                    if self.openvas_db.get_single_item('internal/%s' % scan_id):
1117
                        self.openvas_db.release_db(host_kb)
1118
1119
    def get_vts_in_groups(self, filters: List) -> List:
1120
        """ Return a list of vts which match with the given filter.
1121
1122
        @input filters A list of filters. Each filter has key, operator and
1123
                       a value. They are separated by a space.
1124
                       Supported keys: family
1125
        @return Return a list of vts which match with the given filter.
1126
        """
1127
        vts_list = list()
1128
        families = dict()
1129
        for oid in self.vts:
1130
            family = self.vts[oid]['custom'].get('family')
1131
            if family not in families:
1132
                families[family] = list()
1133
            families[family].append(oid)
1134
1135
        for elem in filters:
1136
            key, value = elem.split('=')
1137
            if key == 'family' and value in families:
1138
                vts_list.extend(families[value])
1139
        return vts_list
1140
1141
    def get_vt_param_type(self, vtid: str, vt_param_id: str) -> Optional[str]:
1142
        """ Return the type of the vt parameter from the vts dictionary. """
1143
1144
        vt_params_list = self.vts[vtid].get("vt_params")
1145
        if vt_params_list.get(vt_param_id):
1146
            return vt_params_list[vt_param_id]["type"]
1147
        return None
1148
1149
    def get_vt_param_name(self, vtid: str, vt_param_id: str) -> Optional[str]:
1150
        """ Return the type of the vt parameter from the vts dictionary. """
1151
1152
        vt_params_list = self.vts[vtid].get("vt_params")
1153
        if vt_params_list.get(vt_param_id):
1154
            return vt_params_list[vt_param_id]["name"]
1155
        return None
1156
1157
    @staticmethod
1158
    def check_param_type(vt_param_value: str, param_type: str) -> Optional[int]:
1159
        """ Check if the value of a vt parameter matches with
1160
        the type founded.
1161
        """
1162
        if param_type in [
1163
            'entry',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1164
            'password',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1165
            'radio',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1166
            'sshlogin',
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1167
        ] and isinstance(vt_param_value, str):
1168
            return None
1169
        elif param_type == 'checkbox' and (
1170
            vt_param_value == '0' or vt_param_value == '1'
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1171
        ):
1172
            return None
1173
        elif param_type == 'file':
1174
            try:
1175
                b64decode(vt_param_value.encode())
1176
            except (binascii.Error, AttributeError, TypeError):
1177
                return 1
1178
            return None
1179
        elif param_type == 'integer':
1180
            try:
1181
                int(vt_param_value)
1182
            except ValueError:
1183
                return 1
1184
            return None
1185
1186
        return 1
1187
1188
    def process_vts(self, vts: List) -> Tuple[list, list]:
1189
        """ Add single VTs and their parameters. """
1190
        vts_list = []
1191
        vts_params = []
1192
        vtgroups = vts.pop('vt_groups')
1193
1194
        if vtgroups:
1195
            vts_list = self.get_vts_in_groups(vtgroups)
1196
1197
        for vtid, vt_params in vts.items():
1198
            if vtid not in self.vts.keys():
1199
                logger.warning(
1200
                    'The vt %s was not found and it will not be loaded.', vtid
1201
                )
1202
                continue
1203
            vts_list.append(vtid)
1204
            for vt_param_id, vt_param_value in vt_params.items():
1205
                param_type = self.get_vt_param_type(vtid, vt_param_id)
1206
                param_name = self.get_vt_param_name(vtid, vt_param_id)
1207
                if not param_type or not param_name:
1208
                    logger.debug(
1209
                        'Missing type or name for vt parameter %s of %s. '
1210
                        'It could not be loaded.',
1211
                        vt_param_id,
1212
                        vtid,
1213
                    )
1214
                    continue
1215
                if vt_param_id == '0':
1216
                    type_aux = 'integer'
1217
                else:
1218
                    type_aux = param_type
1219
                if self.check_param_type(vt_param_value, type_aux):
1220
                    logger.debug(
1221
                        'The vt parameter %s for %s could not be loaded. '
1222
                        'Expected %s type for parameter value %s',
1223
                        vt_param_id,
1224
                        vtid,
1225
                        type_aux,
1226
                        str(vt_param_value),
1227
                    )
1228
                    continue
1229
                if type_aux == 'checkbox':
1230
                    vt_param_value = _from_bool_to_str(int(vt_param_value))
1231
                param = [
1232
                    "{0}:{1}:{2}:{3}".format(
1233
                        vtid, vt_param_id, param_type, param_name
1234
                    ),
1235
                    str(vt_param_value),
1236
                ]
1237
                vts_params.append(param)
1238
        return vts_list, vts_params
1239
1240
    @staticmethod
1241
    def build_credentials_as_prefs(credentials: Dict) -> List[str]:
1242
        """ Parse the credential dictionary.
1243
        @param credentials: Dictionary with the credentials.
1244
1245
        @return A list with the credentials in string format to be
1246
                added to the redis KB.
1247
        """
1248
        cred_prefs_list = []
1249
        for credential in credentials.items():
1250
            service = credential[0]
1251
            cred_params = credentials.get(service)
1252
            cred_type = cred_params.get('type', '')
1253
            username = cred_params.get('username', '')
1254
            password = cred_params.get('password', '')
1255
1256
            if service == 'ssh':
1257
                port = cred_params.get('port', '')
1258
                cred_prefs_list.append('auth_port_ssh|||' + '{0}'.format(port))
1259
                cred_prefs_list.append(
1260
                    OID_SSH_AUTH
1261
                    + ':1:'
1262
                    + 'entry:SSH login '
1263
                    + 'name:|||{0}'.format(username)
1264
                )
1265
                if cred_type == 'up':
1266
                    cred_prefs_list.append(
1267
                        OID_SSH_AUTH
1268
                        + ':3:'
1269
                        + 'password:SSH password '
1270
                        + '(unsafe!):|||{0}'.format(password)
1271
                    )
1272
                else:
1273
                    private = cred_params.get('private', '')
1274
                    cred_prefs_list.append(
1275
                        OID_SSH_AUTH
1276
                        + ':2:'
1277
                        + 'password:SSH key passphrase:|||'
1278
                        + '{0}'.format(password)
1279
                    )
1280
                    cred_prefs_list.append(
1281
                        OID_SSH_AUTH
1282
                        + ':4:'
1283
                        + 'file:SSH private key:|||'
1284
                        + '{0}'.format(private)
1285
                    )
1286
            if service == 'smb':
1287
                cred_prefs_list.append(
1288
                    OID_SMB_AUTH
1289
                    + ':1:entry'
1290
                    + ':SMB login:|||{0}'.format(username)
1291
                )
1292
                cred_prefs_list.append(
1293
                    OID_SMB_AUTH
1294
                    + ':2:'
1295
                    + 'password:SMB password:|||'
1296
                    + '{0}'.format(password)
1297
                )
1298
            if service == 'esxi':
1299
                cred_prefs_list.append(
1300
                    OID_ESXI_AUTH
1301
                    + ':1:entry:'
1302
                    + 'ESXi login name:|||'
1303
                    + '{0}'.format(username)
1304
                )
1305
                cred_prefs_list.append(
1306
                    OID_ESXI_AUTH
1307
                    + ':2:'
1308
                    + 'password:ESXi login password:|||'
1309
                    + '{0}'.format(password)
1310
                )
1311
1312
            if service == 'snmp':
1313
                community = cred_params.get('community', '')
1314
                auth_algorithm = cred_params.get('auth_algorithm', '')
1315
                privacy_password = cred_params.get('privacy_password', '')
1316
                privacy_algorithm = cred_params.get('privacy_algorithm', '')
1317
1318
                cred_prefs_list.append(
1319
                    OID_SNMP_AUTH
1320
                    + ':1:'
1321
                    + 'password:SNMP Community:'
1322
                    + '{0}'.format(community)
1323
                )
1324
                cred_prefs_list.append(
1325
                    OID_SNMP_AUTH
1326
                    + ':2:'
1327
                    + 'entry:SNMPv3 Username:'
1328
                    + '{0}'.format(username)
1329
                )
1330
                cred_prefs_list.append(
1331
                    OID_SNMP_AUTH + ':3:'
1332
                    'password:SNMPv3 Password:' + '{0}'.format(password)
1333
                )
1334
                cred_prefs_list.append(
1335
                    OID_SNMP_AUTH
1336
                    + ':4:'
1337
                    + 'radio:SNMPv3 Authentication '
1338
                    + 'Algorithm:{0}'.format(auth_algorithm)
1339
                )
1340
                cred_prefs_list.append(
1341
                    OID_SNMP_AUTH
1342
                    + ':5:'
1343
                    + 'password:SNMPv3 Privacy Password:'
1344
                    + '{0}'.format(privacy_password)
1345
                )
1346
                cred_prefs_list.append(
1347
                    OID_SNMP_AUTH
1348
                    + ':6:'
1349
                    + 'radio:SNMPv3 Privacy Algorithm:'
1350
                    + '{0}'.format(privacy_algorithm)
1351
                )
1352
1353
        return cred_prefs_list
1354
1355
    def exec_scan(self, scan_id: str, target: str):
1356
        """ Starts the OpenVAS scanner for scan_id scan. """
1357
        if self.pending_feed:
1358
            logger.info(
1359
                '%s: There is a pending feed update. '
1360
                'The scan can not be started.',
1361
                scan_id,
1362
            )
1363
            self.add_scan_error(
1364
                scan_id,
1365
                name='',
1366
                host=target,
1367
                value=(
1368
                    'It was not possible to start the scan,'
1369
                    'because a pending feed update. Please try later'
1370
                ),
1371
            )
1372
            return 2
1373
1374
        ports = self.get_scan_ports(scan_id, target)
1375
        if not ports:
1376
            self.add_scan_error(
1377
                scan_id, name='', host=target, value='No port list defined.'
1378
            )
1379
            return 2
1380
1381
        # Get scan options
1382
        options = self.get_scan_options(scan_id)
1383
        prefs_val = []
1384
        ctx = self.openvas_db.kb_new()
1385
        self.openvas_db.set_redisctx(ctx)
1386
        self.main_kbindex = self.openvas_db.db_index
1387
1388
        # To avoid interference between scan process during a parallel scanning
1389
        # new uuid is used internally for each scan.
1390
        openvas_scan_id = str(uuid.uuid4())
1391
        self.openvas_db.add_single_item(
1392
            'internal/%s' % openvas_scan_id, ['new']
1393
        )
1394
        self.openvas_db.add_single_item(
1395
            'internal/%s/globalscanid' % scan_id, [openvas_scan_id]
1396
        )
1397
        self.openvas_db.add_single_item('internal/scanid', [openvas_scan_id])
1398
1399
        exclude_hosts = self.get_scan_exclude_hosts(scan_id, target)
1400
        if exclude_hosts:
1401
            options['exclude_hosts'] = exclude_hosts
1402
1403
        # Get unfinished hosts, in case it is a resumed scan. And added
1404
        # into exclude_hosts scan preference. Set progress for the finished ones
1405
        # to 100%.
1406
        finished_hosts = self.get_scan_finished_hosts(scan_id)
1407
        if finished_hosts:
1408
            if exclude_hosts:
1409
                finished_hosts_str = ','.join(finished_hosts)
1410
                exclude_hosts = exclude_hosts + ',' + finished_hosts_str
1411
                options['exclude_hosts'] = exclude_hosts
1412
            else:
1413
                options['exclude_hosts'] = ','.join(finished_hosts)
1414
1415
        # Set scan preferences
1416
        for key, value in options.items():
1417
            item_type = ''
1418
            if key in OSPD_PARAMS:
1419
                item_type = OSPD_PARAMS[key].get('type')
1420
            if item_type == 'boolean':
1421
                val = _from_bool_to_str(value)
1422
            else:
1423
                val = str(value)
1424
            prefs_val.append(key + "|||" + val)
1425
        self.openvas_db.add_single_item(
1426
            'internal/%s/scanprefs' % openvas_scan_id, prefs_val
1427
        )
1428
1429
        # Store main_kbindex as global preference
1430
        ov_maindbid = 'ov_maindbid|||%d' % self.main_kbindex
1431
        self.openvas_db.add_single_item(
1432
            'internal/%s/scanprefs' % openvas_scan_id, [ov_maindbid]
1433
        )
1434
1435
        # Set target
1436
        target_aux = 'TARGET|||%s' % target
1437
        self.openvas_db.add_single_item(
1438
            'internal/%s/scanprefs' % openvas_scan_id, [target_aux]
1439
        )
1440
        # Set port range
1441
        port_range = 'port_range|||%s' % ports
1442
        self.openvas_db.add_single_item(
1443
            'internal/%s/scanprefs' % openvas_scan_id, [port_range]
1444
        )
1445
1446
        # If credentials or vts fail, set this variable.
1447
        do_not_launch = False
1448
1449
        # Set credentials
1450
        credentials = self.get_scan_credentials(scan_id, target)
1451
        if credentials:
1452
            cred_prefs = self.build_credentials_as_prefs(credentials)
1453
            if cred_prefs:
1454
                self.openvas_db.add_single_item(
1455
                    'internal/%s/scanprefs' % openvas_scan_id, cred_prefs
1456
                )
1457
            else:
1458
                self.add_scan_error(
1459
                    scan_id, name='', host=target, value='Malformed credential.'
1460
                )
1461
                do_not_launch = True
1462
1463
        # Set plugins to run
1464
        nvts = self.get_scan_vts(scan_id)
1465
        if nvts != '':
1466
            nvts_list, nvts_params = self.process_vts(nvts)
1467
            # Add nvts list
1468
            separ = ';'
1469
            plugin_list = 'plugin_set|||%s' % separ.join(nvts_list)
1470
            self.openvas_db.add_single_item(
1471
                'internal/%s/scanprefs' % openvas_scan_id, [plugin_list]
1472
            )
1473
            # Add nvts parameters
1474
            for elem in nvts_params:
1475
                item = '%s|||%s' % (elem[0], elem[1])
1476
                self.openvas_db.add_single_item(
1477
                    'internal/%s/scanprefs' % openvas_scan_id, [item]
1478
                )
1479
        else:
1480
            self.add_scan_error(
1481
                scan_id, name='', host=target, value='No VTS to run.'
1482
            )
1483
            do_not_launch = True
1484
1485
        if do_not_launch:
1486
            self.openvas_db.release_db(self.main_kbindex)
1487
            return 2
1488
1489
        cmd = ['openvas', '--scan-start', openvas_scan_id]
1490
        if not self.is_running_as_root and self.sudo_available:
1491
            cmd = ['sudo', '-n'] + cmd
1492
1493
        if self._niceness is not None:
1494
            cmd = ['nice', '-n', self._niceness] + cmd
1495
1496
        logger.debug("Running scan with niceness %s", self._niceness)
1497
        try:
1498
            result = subprocess.Popen(cmd, shell=False)
1499
        except OSError:
1500
            # the command is not available
1501
            return False
1502
1503
        ovas_pid = result.pid
1504
        logger.debug('pid = %s', ovas_pid)
1505
        self.openvas_db.add_single_item('internal/ovas_pid', [ovas_pid])
1506
1507
        # Wait until the scanner starts and loads all the preferences.
1508
        while (
1509
            self.openvas_db.get_single_item('internal/' + openvas_scan_id)
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1510
            == 'new'
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
1511
        ):
1512
            res = result.poll()
1513
            if res and res < 0:
1514
                self.stop_scan_cleanup(scan_id)
1515
                msg = (
1516
                    'It was not possible run the task %s, since openvas ended '
1517
                    'unexpectedly with errors during launching.' % scan_id
1518
                )
1519
                logger.error(msg)
1520
                return 1
1521
            time.sleep(1)
1522
1523
        no_id_found = False
1524
        while True:
1525
            time.sleep(3)
1526
            # Check if the client stopped the whole scan
1527
            if self.scan_is_stopped(openvas_scan_id):
1528
                return 1
1529
1530
            ctx = self.openvas_db.kb_connect(self.main_kbindex)
1531
            self.openvas_db.set_redisctx(ctx)
1532
            dbs = self.openvas_db.get_list_item('internal/dbindex')
1533
            for i in list(dbs):
1534
                if i == self.main_kbindex:
1535
                    continue
1536
                self.openvas_db.select_kb(ctx, str(i), set_global=True)
1537
                id_aux = self.openvas_db.get_single_item('internal/scan_id')
1538
                if not id_aux:
1539
                    continue
1540
                if id_aux == openvas_scan_id:
1541
                    no_id_found = False
1542
                    current_host = self.openvas_db.get_host_ip()
1543
                    self.get_openvas_result(scan_id, current_host)
1544
                    self.get_openvas_status(scan_id, target, current_host)
1545
                    self.get_openvas_timestamp_scan_host(scan_id, current_host)
1546
                    if self.host_is_finished(openvas_scan_id):
1547
                        self.set_scan_host_finished(
1548
                            scan_id, target, current_host
1549
                        )
1550
                        self.get_openvas_status(scan_id, target, current_host)
1551
                        self.get_openvas_timestamp_scan_host(
1552
                            scan_id, current_host
1553
                        )
1554
                        self.openvas_db.select_kb(
1555
                            ctx, str(self.main_kbindex), set_global=False
1556
                        )
1557
                        self.openvas_db.remove_list_item('internal/dbindex', i)
1558
                        self.openvas_db.release_db(i)
1559
1560
            # Scan end. No kb in use for this scan id
1561
            if no_id_found and self.target_is_finished(scan_id):
1562
                break
1563
            no_id_found = True
1564
1565
        # Delete keys from KB related to this scan task.
1566
        self.openvas_db.release_db(self.main_kbindex)
1567
1568
1569
def main():
1570
    """ OSP openvas main function. """
1571
    daemon_main('OSPD - openvas', OSPDopenvas)
1572
1573
1574
if __name__ == '__main__':
1575
    main()
1576