1 | # -*- coding: utf-8 -*- |
||
2 | # Copyright (C) 2017-2021 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_uuid, |
||
33 | generate_id, |
||
34 | generate_random_ips, |
||
35 | ) |
||
36 | |||
37 | __version__ = "0.1.0" |
||
38 | |||
39 | HELP_TEXT = """ |
||
40 | Random Report Generation Script {version} (C) 2017-2021 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_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_uuid() for host in hosts} |
||
107 | host_names = {host: generate_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_uuid()}) |
||
154 | |||
155 | e.SubElement(result_elem, 'name').text = "a_result" + generate_id() |
||
156 | own = e.SubElement(result_elem, 'owner') |
||
157 | e.SubElement(own, 'name').text = generate_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))) |
||
0 ignored issues
–
show
introduced
by
![]() |
|||
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) |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
Comprehensibility
Best Practice
introduced
by
|
|||
467 |