Passed
Push — master ( ead621...422e90 )
by Juan José
01:34 queued 11s
created

ospd_openvas.daemon.OSPDopenvas.feed_locked()   A

Complexity

Conditions 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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