Passed
Pull Request — master (#244)
by
unknown
01:25
created

random-report-gen.gmp.generate_host_detail_elem()   A

Complexity

Conditions 3

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 11
nop 4
dl 0
loc 15
rs 9.85
c 0
b 0
f 0
1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2018-2019 Greenbone Networks GmbH
3
#
4
# SPDX-License-Identifier: GPL-3.0-or-later
5
#
6
# This program is free software: you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation, either version 3 of the License, or
9
# (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, see <http://www.gnu.org/licenses/>.
18
19
# pylint: disable=too-many-lines
20
21
import uuid
22
import string
23
import time
24
import textwrap
25
import json
26
27
from random import randrange, choice, gauss, seed
28
from argparse import ArgumentParser, RawTextHelpFormatter
29
from pathlib import Path
30
31
from lxml import etree as e
32
33
__version__ = "0.1.0"
34
35
HELP_TEXT = """
36
    Random Report Generation Script {version} (C) 2017-2019 Greenbone Networks GmbH
37
38
    This program is free software: you can redistribute it and/or modify
39
    it under the terms of the GNU General Public License as published by
40
    the Free Software Foundation, either version 3 of the License, or
41
    (at your option) any later version.
42
43
    This program is distributed in the hope that it will be useful,
44
    but WITHOUT ANY WARRANTY; without even the implied warranty of
45
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
46
    GNU General Public License for more details.
47
48
    You should have received a copy of the GNU General Public License
49
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
50
51
    This script generates randomized report data.
52
    """.format(
53
    version=__version__
54
)
55
56
57
def generate_uuid():
58
    return str(uuid.uuid4())
59
60
61
def generate_ips(n_hosts):
62
    exclude_127 = [i for i in range(1, 256)]
63
    exclude_127.remove(127)
64
    return [
65
        '{}.{}.{}.{}'.format(
66
            choice(exclude_127),
67
            randrange(0, 256),
68
            randrange(0, 256),
69
            randrange(1, 255),
70
        )
71
        for i in range(n_hosts)
72
    ]
73
74
75
def generate_ports(n_ports):
76
    protocol = ['/tcp', '/udp']
77
    return [str(randrange(0, 65536)) + choice(protocol) for i in range(n_ports)]
78
79
80
def id_generator(size=12, chars=string.ascii_uppercase + string.digits):
81
    return ''.join(choice(chars) for _ in range(size))
82
83
84
def generate_report_elem(task, **kwargs):
85
    rep_format_id = 'a994b278-1f62-11e1-96ac-406186ea4fc5'
86
    rep_id = generate_uuid()
87
    outer_report_elem = e.Element(
88
        'report',
89
        attrib={
90
            'extension': 'xml',
91
            'id': rep_id,
92
            'format_id': rep_format_id,
93
            'content_type': 'text/xml',
94
        },
95
    )
96
    owner_elem = e.SubElement(outer_report_elem, 'owner')
97
    e.SubElement(owner_elem, 'name').text = 'testowner'
98
    e.SubElement(outer_report_elem, 'name').text = 'testname'
99
    e.SubElement(outer_report_elem, 'writeable').text = str(0)
100
    e.SubElement(outer_report_elem, 'in_use').text = str(0)
101
    task_elem = e.SubElement(outer_report_elem, 'task', attrib={'id': task[0]})
102
    e.SubElement(task_elem, 'name').text = task[1]
103
    repform_elem = e.SubElement(
104
        outer_report_elem, 'report_format', attrib={'id': rep_format_id}
105
    )
106
    e.SubElement(repform_elem, 'name').text = 'XML'
107
108
    # Generating inner <report> tag
109
    outer_report_elem.append(generate_inner_report(rep_id, **kwargs))
110
111
    return outer_report_elem
112
113
114
def generate_inner_report(rep_id, n_results, n_hosts, data, **kwargs):
115
    report_elem = e.Element('report', attrib={'id': rep_id})
116
    results_elem = e.SubElement(
117
        report_elem, 'results', {'max': str(n_results), 'start': '1'}
118
    )
119
120
    # Create Hosts, Ports, Data
121
    hosts = generate_ips(n_hosts)  # Host IPs
122
    ports = generate_ports(n_hosts)
123
    oid_dict = {host: [] for host in hosts}
124
    asset_dict = {host: generate_uuid() for host in hosts}
125
    host_names = {host: id_generator() for host in hosts}
126
    max_sev = 0.0
127
128
    # Create <result> tags with random data
129
    for _ in range(n_results):
130
        host_ip = choice(hosts)
131
        host_port = choice(ports)
132
        result_elem, oid, severity = generate_result_elem(
133
            data["vulns"],
134
            host_ip,
135
            host_port,
136
            asset_dict[host_ip],
137
            host_names[host_ip],
138
        )
139
        if float(severity) > max_sev:
140
            max_sev = float(severity)
141
142
        oid_dict[host_ip].append(oid)
143
        results_elem.append(result_elem)
144
145
    e.SubElement(report_elem, "result_count").text = str(n_results)
146
147
    sev_elem = e.Element("severity")
148
    e.SubElement(sev_elem, "full").text = str(max_sev)
149
    e.SubElement(sev_elem, "filtered").text = str(max_sev)
150
151
    report_elem.append(sev_elem)
152
153
    # Create <host> tags with random data
154
    for host in hosts:
155
        if len(oid_dict[host]) > 0:
156
            report_elem.append(
157
                generate_host_elem(
158
                    host,
159
                    oid_dict[host][0],
160
                    asset_dict[host],
161
                    host_names[host],
162
                    data=data,
163
                    **kwargs,
164
                )
165
            )
166
167
    return report_elem
168
169
170
def generate_result_elem(vulns, host_ip, host_port, host_asset, host_name):
171
    result_elem = e.Element('result', {'id': generate_uuid()})
172
173
    e.SubElement(result_elem, 'name').text = "a_result" + id_generator()
174
    own = e.SubElement(result_elem, 'owner')
175
    e.SubElement(own, 'name').text = id_generator()
176
177
    elem = e.Element('modification_time')
178
    e.SubElement(result_elem, 'modification_time').text = (
179
        time.strftime("%Y-%m-%dT%H:%M:%S%z", time.localtime(time.time()))[:-2]
180
        + ':00'
181
    )  # Hell of a Timeformat :D
182
    e.SubElement(result_elem, 'comment').text = ''
183
    e.SubElement(result_elem, 'creation_time').text = (
184
        time.strftime("%Y-%m-%dT%H:%M:%S%z", time.localtime(time.time() - 20))[
185
            :-2
186
        ]
187
        + ':00'
188
    )
189
190
    host_elem = e.Element('host')
191
    host_elem.text = host_ip
192
    e.SubElement(host_elem, 'asset', {'asset_id': host_asset}).text = ''
193
    e.SubElement(host_elem, 'hostname').text = host_name
194
    result_elem.append(host_elem)
195
196
    port_elem = e.Element('port')
197
    port_elem.text = host_port
198
    result_elem.append(port_elem)
199
200
    nvt = vulns[randrange(len(vulns))]
201
    e.SubElement(result_elem, 'severity').text = nvt['severity']
202
    nvt_elem = e.Element('nvt', {'oid': nvt['oid']})
203
    result_elem.append(nvt_elem)
204
205
    e.SubElement(result_elem, 'notes').text = 'TestNotes'
206
207
    result_elem.append(elem)
208
209
    return result_elem, nvt['oid'], nvt['severity']
210
211
212
def generate_host_detail_elem(
213
    name, value, source_name=None, source_description=None
214
):
215
    host_detail_elem = e.Element('detail')
216
    e.SubElement(host_detail_elem, 'name').text = name
217
    e.SubElement(host_detail_elem, 'value').text = value
218
219
    if source_name:
220
        source_elem = e.SubElement(host_detail_elem, 'source')
221
        e.SubElement(source_elem, 'name').text = source_name
222
223
        if source_description:
224
            e.SubElement(source_elem, 'description').text = source_description
225
226
    return host_detail_elem
227
228
229
def generate_additional_host_details(
230
    n_details, host_details, *, not_vuln=False
231
):
232
    host_detail_elems = []
233
234
    for _ in range(n_details):
235
        details = None
236
237
        if not_vuln:
238
            details = host_details.copy()
239
            details["source_name"] += str(randrange(14259, 103585))
240
        else:
241
            details = choice(host_details)
242
243
        host_detail_elems.append(
244
            generate_host_detail_elem(
245
                details['name'],
246
                details['value'],
247
                source_name=details.get('source_name'),
248
                source_description=details.get('source_description'),
249
            )
250
        )
251
252
    return host_detail_elems
253
254
255
def generate_host_elem(
256
    host_ip, oid, host_asset, host_name, n_host_details, n_not_vuln, data
257
):
258
    host_elem = e.Element('host')
259
    e.SubElement(host_elem, 'ip').text = host_ip
260
    e.SubElement(host_elem, 'asset', {'asset_id': host_asset}).text = ''
261
262
    e.SubElement(host_elem, 'start').text = (
263
        time.strftime(
264
            "%Y-%m-%dT%H:%M:%S%z", time.localtime(time.time() - 1000)
265
        )[:-2]
266
        + ':00'
267
    )
268
    e.SubElement(host_elem, 'end').text = (
269
        time.strftime("%Y-%m-%dT%H:%M:%S%z", time.localtime(time.time() - 30))[
270
            :-2
271
        ]
272
        + ':00'
273
    )
274
275
    app = choice(list(data["apps"]))
276
    os = choice(list(data["oss"]))
277
278
    host_elem.append(
279
        generate_host_detail_elem('App', data["apps"].get(app), source_name=oid)
280
    )
281
    host_elem.append(
282
        generate_host_detail_elem(
283
            data["apps"].get(app), '/usr/bin/foo', source_name=oid
284
        )
285
    )
286
    host_elem.append(
287
        generate_host_detail_elem(
288
            'hostname',
289
            host_name,
290
            source_name=oid,
291
            source_description="Host Details",
292
        )
293
    )
294
    host_elem.append(
295
        generate_host_detail_elem(
296
            'best_os_txt',
297
            list(os)[0],
298
            source_name=oid,
299
            source_description="Host Details",
300
        )
301
    )
302
    host_elem.append(
303
        generate_host_detail_elem(
304
            'best_os_cpe',
305
            data["oss"].get(os),
306
            source_name=oid,
307
            source_description="Host Details",
308
        )
309
    )
310
311
    if n_host_details:
312
        host_elem.extend(
313
            generate_additional_host_details(
314
                n_host_details, data["host_details"]
315
            )
316
        )
317
318
    dev = n_not_vuln / 10
319
    if n_not_vuln:
320
        host_elem.extend(
321
            generate_additional_host_details(
322
                n_not_vuln + randrange(-dev, dev),
323
                data["not_vuln"],
324
                not_vuln=True,
325
            )
326
        )
327
328
    return host_elem
329
330
331
def generate_reports(task, n_reports, with_gauss, **kwargs):
332
    reports = []
333
334
    if with_gauss:
335
        n_reports = abs(int(gauss(n_reports, 1)))
336
        if n_reports == 0:
337
            n_reports += 1
338
339
    for _ in range(n_reports):
340
        if with_gauss:
341
            n_results = abs(int(gauss(n_results, 2)))
0 ignored issues
show
introduced by
The variable n_results does not seem to be defined for all execution paths.
Loading history...
342
343
        report_elem = generate_report_elem(task, **kwargs)
344
        report_elem = e.tostring(report_elem)
345
        reports.append(report_elem)
346
347
    return reports
348
349
350
def generate_data(gmp, n_tasks, **kwargs):
351
    for i in range(n_tasks):
352
        index = '{{0:0>{}}}'.format(len(str(n_tasks)))
353
        task_name = 'Task_for_GenReport:_{}'.format(index.format(i + 1))
354
355
        gmp.create_container_task(task_name)
356
357
        task_id = gmp.get_tasks(filter='name={}'.format(task_name)).xpath(
358
            '//@id'
359
        )[0]
360
361
        reports = generate_reports(task=(task_id, task_name), **kwargs)
362
363
        for report in reports[0:]:
364
            gmp.import_report(report, task_id=task_id, in_assets=True)
365
366
367
def main(gmp, args):
368
    # pylint: disable=undefined-variable
369
370
    parser = ArgumentParser(
371
        prog="random-report-gen",
372
        prefix_chars="-",
373
        description=HELP_TEXT,
374
        formatter_class=RawTextHelpFormatter,
375
        add_help=False,
376
        epilog=textwrap.dedent(
377
            """
378
        Example:
379
            $ gvm-script --gmp-username name --gmp-password pass
380
            ssh --hostname <gsm> scripts/gen-random-reports.gmp.py -T 5 -r 4 -R 3 --hosts 10
381
        """
382
        ),
383
    )
384
385
    parser.add_argument(
386
        "-H", action="help", help="Show this help message and exit."
387
    )
388
389
    parser.add_argument(
390
        "--datafile",
391
        default=Path(args.script[0]).parent / "default_report_data.json",
392
        help="A json file containing the following information: vulnerabilities, operating systems, applications and host details. Take the default json file as an example.",
393
    )
394
395
    parser.add_argument(
396
        "--tasks",
397
        "-T",
398
        type=int,
399
        default="1",
400
        help="Number of Tasks to be generated.",
401
    )
402
403
    parser.add_argument(
404
        "--reports",
405
        "-r",
406
        type=int,
407
        default="5",
408
        help="Number of Reports per Task.",
409
    )
410
411
    parser.add_argument(
412
        "--results",
413
        "-R",
414
        type=int,
415
        default="5",
416
        help="Number of Results per Report.",
417
    )
418
419
    parser.add_argument(
420
        "--hosts",
421
        type=int,
422
        default="5",
423
        help="Number of randomized hosts to select from.",
424
    )
425
426
    parser.add_argument(
427
        "--host-details",
428
        dest="host_details",
429
        type=int,
430
        default="2",
431
        help="Number of additional host details per host.",
432
    )
433
434
    parser.add_argument(
435
        "--not-vuln-details",
436
        dest="not_vuln",
437
        type=int,
438
        default="10",
439
        help="Number of 'NOT_VULN' host details per host.",
440
    )
441
442
    parser.add_argument(
443
        "--with-gauss",
444
        dest="with_gauss",
445
        action="store_true",
446
        help="if you would like for the number of reports/task and results/report to be randomized along a Gaussian distribution.",
447
    )
448
449
    parser.add_argument(
450
        "--seed", help="RNG Seed, in case the same data should be generated."
451
    )
452
453
    script_args = parser.parse_args(args.script_args)
454
455
    seed() if not script_args.seed else seed(script_args.seed)
456
457
    with open(str(script_args.datafile)) as file:
458
        data = json.load(file)
459
460
    print('\n  Generating randomized data(s)...\n')
461
462
    generate_data(
463
        gmp,
464
        n_tasks=script_args.tasks,
465
        n_reports=script_args.reports,
466
        n_results=script_args.results,
467
        n_hosts=script_args.hosts,
468
        n_host_details=script_args.host_details,
469
        n_not_vuln=script_args.not_vuln,
470
        data=data,
471
        with_gauss=script_args.with_gauss,
472
    )
473
474
    print('\n  Generation done.\n')
475
476
477
if __name__ == '__gmp__':
478
    main(gmp, args)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable gmp does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable args does not seem to be defined.
Loading history...
479