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