Passed
Pull Request — master (#195)
by Juan José
01:25
created

ospd_openvas.daemon.OSPDopenvas.feed_is_healthy()   A

Complexity

Conditions 1

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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