Completed
Push — master ( 422e90...9c87d7 )
by Juan José
17s queued 13s
created

OSPDopenvas.create_feed_lock_file()   A

Complexity

Conditions 4

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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