Completed
Push — master ( 58773c...d019f6 )
by Juan José
16s queued 11s
created

ospd_openvas.daemon.OSPDopenvas.get_vt_iterator()   A

Complexity

Conditions 2

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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