secator 0.12.0__py3-none-any.whl → 0.14.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of secator might be problematic. Click here for more details.
- secator/cli.py +199 -66
- secator/configs/profiles/aggressive.yaml +6 -5
- secator/configs/profiles/default.yaml +6 -7
- secator/configs/profiles/insane.yaml +8 -0
- secator/configs/profiles/paranoid.yaml +8 -0
- secator/configs/profiles/polite.yaml +8 -0
- secator/configs/profiles/sneaky.yaml +8 -0
- secator/configs/profiles/tor.yaml +5 -0
- secator/configs/workflows/host_recon.yaml +11 -2
- secator/configs/workflows/url_dirsearch.yaml +5 -0
- secator/decorators.py +1 -0
- secator/definitions.py +0 -4
- secator/installer.py +29 -15
- secator/report.py +2 -2
- secator/runners/_base.py +32 -1
- secator/runners/_helpers.py +13 -2
- secator/runners/command.py +2 -1
- secator/runners/scan.py +1 -0
- secator/runners/task.py +1 -0
- secator/tasks/_categories.py +2 -2
- secator/tasks/arjun.py +2 -1
- secator/tasks/bbot.py +30 -4
- secator/tasks/bup.py +2 -1
- secator/tasks/cariddi.py +15 -3
- secator/tasks/dalfox.py +2 -1
- secator/tasks/dirsearch.py +1 -1
- secator/tasks/dnsx.py +2 -1
- secator/tasks/dnsxbrute.py +2 -1
- secator/tasks/feroxbuster.py +3 -2
- secator/tasks/ffuf.py +2 -1
- secator/tasks/gau.py +2 -1
- secator/tasks/gitleaks.py +4 -3
- secator/tasks/gospider.py +3 -2
- secator/tasks/grype.py +1 -0
- secator/tasks/h8mail.py +2 -1
- secator/tasks/httpx.py +3 -2
- secator/tasks/katana.py +4 -3
- secator/tasks/maigret.py +1 -1
- secator/tasks/mapcidr.py +2 -1
- secator/tasks/msfconsole.py +4 -3
- secator/tasks/naabu.py +4 -2
- secator/tasks/nuclei.py +15 -9
- secator/tasks/searchsploit.py +3 -2
- secator/tasks/subfinder.py +2 -1
- secator/tasks/testssl.py +4 -3
- secator/tasks/trivy.py +2 -2
- secator/tasks/wafw00f.py +2 -1
- secator/tasks/wpprobe.py +2 -1
- secator/tasks/wpscan.py +6 -3
- secator/template.py +1 -1
- {secator-0.12.0.dist-info → secator-0.14.0.dist-info}/METADATA +1 -1
- {secator-0.12.0.dist-info → secator-0.14.0.dist-info}/RECORD +55 -51
- secator/configs/profiles/stealth.yaml +0 -7
- {secator-0.12.0.dist-info → secator-0.14.0.dist-info}/WHEEL +0 -0
- {secator-0.12.0.dist-info → secator-0.14.0.dist-info}/entry_points.txt +0 -0
- {secator-0.12.0.dist-info → secator-0.14.0.dist-info}/licenses/LICENSE +0 -0
secator/cli.py
CHANGED
|
@@ -36,8 +36,9 @@ click.rich_click.USE_RICH_MARKUP = True
|
|
|
36
36
|
ALL_TASKS = discover_tasks()
|
|
37
37
|
ALL_WORKFLOWS = [t for t in TEMPLATES if t.type == 'workflow']
|
|
38
38
|
ALL_SCANS = [t for t in TEMPLATES if t.type == 'scan']
|
|
39
|
+
ALL_PROFILES = [t for t in TEMPLATES if t.type == 'profile']
|
|
39
40
|
FINDING_TYPES_LOWER = [c.__name__.lower() for c in FINDING_TYPES]
|
|
40
|
-
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
|
41
|
+
CONTEXT_SETTINGS = dict(help_option_names=['-h', '-help', '--help'])
|
|
41
42
|
|
|
42
43
|
|
|
43
44
|
#-----#
|
|
@@ -46,15 +47,16 @@ CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
@click.group(cls=OrderedGroup, invoke_without_command=True, context_settings=CONTEXT_SETTINGS)
|
|
49
|
-
@click.option('--version', '-version', is_flag=True, default=False)
|
|
50
|
+
@click.option('--version', '-version', '-v', is_flag=True, default=False)
|
|
51
|
+
@click.option('--quiet', '-quiet', '-q', is_flag=True, default=False)
|
|
50
52
|
@click.pass_context
|
|
51
|
-
def cli(ctx, version):
|
|
53
|
+
def cli(ctx, version, quiet):
|
|
52
54
|
"""Secator CLI."""
|
|
53
55
|
ctx.obj = {
|
|
54
56
|
'piped_input': S_ISFIFO(os.fstat(0).st_mode),
|
|
55
57
|
'piped_output': not sys.stdout.isatty()
|
|
56
58
|
}
|
|
57
|
-
if not ctx.obj['piped_output']:
|
|
59
|
+
if not ctx.obj['piped_output'] and not quiet:
|
|
58
60
|
console.print(ASCII, highlight=False)
|
|
59
61
|
if ctx.invoked_subcommand is None:
|
|
60
62
|
if version:
|
|
@@ -67,11 +69,16 @@ def cli(ctx, version):
|
|
|
67
69
|
# TASK #
|
|
68
70
|
#------#
|
|
69
71
|
|
|
70
|
-
@cli.group(aliases=['x', 't'])
|
|
72
|
+
@cli.group(aliases=['x', 't'], invoke_without_command=True)
|
|
73
|
+
@click.option('--list', '-list', is_flag=True, default=False)
|
|
71
74
|
@click.pass_context
|
|
72
|
-
def task(ctx):
|
|
75
|
+
def task(ctx, list=False):
|
|
73
76
|
"""Run a task."""
|
|
74
|
-
|
|
77
|
+
if list:
|
|
78
|
+
print("\n".join(sorted([t.__name__ for t in ALL_TASKS])))
|
|
79
|
+
return
|
|
80
|
+
if ctx.invoked_subcommand is None:
|
|
81
|
+
ctx.get_help()
|
|
75
82
|
|
|
76
83
|
|
|
77
84
|
for cls in ALL_TASKS:
|
|
@@ -83,11 +90,16 @@ for cls in ALL_TASKS:
|
|
|
83
90
|
#----------#
|
|
84
91
|
|
|
85
92
|
|
|
86
|
-
@cli.group(cls=OrderedGroup, aliases=['w'])
|
|
93
|
+
@cli.group(cls=OrderedGroup, aliases=['w'], invoke_without_command=True)
|
|
94
|
+
@click.option('--list', '-list', is_flag=True, default=False)
|
|
87
95
|
@click.pass_context
|
|
88
|
-
def workflow(ctx):
|
|
96
|
+
def workflow(ctx, list=False):
|
|
89
97
|
"""Run a workflow."""
|
|
90
|
-
|
|
98
|
+
if list:
|
|
99
|
+
print("\n".join(sorted([t.name for t in ALL_WORKFLOWS])))
|
|
100
|
+
return
|
|
101
|
+
if ctx.invoked_subcommand is None:
|
|
102
|
+
ctx.get_help()
|
|
91
103
|
|
|
92
104
|
|
|
93
105
|
for config in sorted(ALL_WORKFLOWS, key=lambda x: x['name']):
|
|
@@ -98,17 +110,41 @@ for config in sorted(ALL_WORKFLOWS, key=lambda x: x['name']):
|
|
|
98
110
|
# SCAN #
|
|
99
111
|
#------#
|
|
100
112
|
|
|
101
|
-
@cli.group(cls=OrderedGroup, aliases=['s'])
|
|
113
|
+
@cli.group(cls=OrderedGroup, aliases=['s'], invoke_without_command=True)
|
|
114
|
+
@click.option('--list', '-list', is_flag=True, default=False)
|
|
102
115
|
@click.pass_context
|
|
103
|
-
def scan(ctx):
|
|
116
|
+
def scan(ctx, list=False):
|
|
104
117
|
"""Run a scan."""
|
|
105
|
-
|
|
118
|
+
if list:
|
|
119
|
+
print("\n".join(sorted([t.name for t in ALL_SCANS])))
|
|
120
|
+
return
|
|
121
|
+
if ctx.invoked_subcommand is None:
|
|
122
|
+
ctx.get_help()
|
|
106
123
|
|
|
107
124
|
|
|
108
125
|
for config in sorted(ALL_SCANS, key=lambda x: x['name']):
|
|
109
126
|
register_runner(scan, config)
|
|
110
127
|
|
|
111
128
|
|
|
129
|
+
@cli.group(aliases=['p'])
|
|
130
|
+
@click.pass_context
|
|
131
|
+
def profile(ctx):
|
|
132
|
+
"""Show profiles"""
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@profile.command('list')
|
|
137
|
+
def profile_list():
|
|
138
|
+
table = Table()
|
|
139
|
+
table.add_column("Profile name", style="bold gold3")
|
|
140
|
+
table.add_column("Description", overflow='fold')
|
|
141
|
+
table.add_column("Options", overflow='fold')
|
|
142
|
+
for profile in ALL_PROFILES:
|
|
143
|
+
opts_str = ','.join(f'{k}={v}' for k, v in profile.opts.items())
|
|
144
|
+
table.add_row(profile.name, profile.description, opts_str)
|
|
145
|
+
console.print(table)
|
|
146
|
+
|
|
147
|
+
|
|
112
148
|
#--------#
|
|
113
149
|
# WORKER #
|
|
114
150
|
#--------#
|
|
@@ -669,7 +705,7 @@ def report_show(report_query, output, runner_type, time_delta, type, query, work
|
|
|
669
705
|
all_results.extend(runner.results)
|
|
670
706
|
continue
|
|
671
707
|
report = Report(runner, title=f"Consolidated report - {current}", exporters=exporters)
|
|
672
|
-
report.build(extractors=extractors if not unified else [])
|
|
708
|
+
report.build(extractors=extractors if not unified else [], dedupe=unified)
|
|
673
709
|
file_date = get_file_date(path)
|
|
674
710
|
runner_name = data['info']['name']
|
|
675
711
|
console.print(
|
|
@@ -860,13 +896,21 @@ def health(json, debug, strict):
|
|
|
860
896
|
import json as _json
|
|
861
897
|
print(_json.dumps(status))
|
|
862
898
|
|
|
899
|
+
# Print errors and warnings
|
|
900
|
+
error = False
|
|
901
|
+
for tool, info in status['tools'].items():
|
|
902
|
+
if not info['installed']:
|
|
903
|
+
console.print(Warning(message=f'{tool} is not installed.'))
|
|
904
|
+
error = True
|
|
905
|
+
elif info['outdated']:
|
|
906
|
+
message = (
|
|
907
|
+
f'{tool} is outdated (current:{info["version"]}, latest:{info["latest_version"]}).'
|
|
908
|
+
f' Run `secator install tools {tool}` to update it.'
|
|
909
|
+
)
|
|
910
|
+
console.print(Warning(message=message))
|
|
911
|
+
|
|
863
912
|
# Strict mode
|
|
864
913
|
if strict:
|
|
865
|
-
error = False
|
|
866
|
-
for tool, info in status['tools'].items():
|
|
867
|
-
if not info['installed']:
|
|
868
|
-
console.print(Error(message=f'{tool} is not installed.'))
|
|
869
|
-
error = True
|
|
870
914
|
if error:
|
|
871
915
|
sys.exit(1)
|
|
872
916
|
console.print(Info(message='Strict healthcheck passed !'))
|
|
@@ -1061,16 +1105,27 @@ def install_tools(cmds, cleanup, fail_fast):
|
|
|
1061
1105
|
if CONFIG.offline_mode:
|
|
1062
1106
|
console.print(Error(message='Cannot run this command in offline mode.'))
|
|
1063
1107
|
return
|
|
1108
|
+
tools = []
|
|
1064
1109
|
if cmds is not None:
|
|
1065
1110
|
cmds = cmds.split(',')
|
|
1066
|
-
|
|
1111
|
+
for cmd in cmds:
|
|
1112
|
+
if '==' in cmd:
|
|
1113
|
+
cmd, version = tuple(cmd.split('=='))
|
|
1114
|
+
else:
|
|
1115
|
+
cmd, version = cmd, None
|
|
1116
|
+
cls = next((cls for cls in ALL_TASKS if cls.__name__ == cmd), None)
|
|
1117
|
+
if cls:
|
|
1118
|
+
if version:
|
|
1119
|
+
cls.install_version = version
|
|
1120
|
+
tools.append(cls)
|
|
1121
|
+
else:
|
|
1122
|
+
console.print(Warning(message=f'Tool {cmd} is not supported or inexistent.'))
|
|
1067
1123
|
else:
|
|
1068
1124
|
tools = ALL_TASKS
|
|
1069
1125
|
tools.sort(key=lambda x: x.__name__)
|
|
1070
1126
|
return_code = 0
|
|
1071
1127
|
if not tools:
|
|
1072
|
-
|
|
1073
|
-
console.print(Error(message=f'No tools found for {cmd_str}.'))
|
|
1128
|
+
console.print(Error(message='No tools found for installing.'))
|
|
1074
1129
|
return
|
|
1075
1130
|
for ix, cls in enumerate(tools):
|
|
1076
1131
|
# with console.status(f'[bold yellow][{ix + 1}/{len(tools)}] Installing {cls.__name__} ...'):
|
|
@@ -1140,7 +1195,7 @@ def update(all):
|
|
|
1140
1195
|
cmd = cls.cmd.split(' ')[0]
|
|
1141
1196
|
version_flag = cls.get_version_flag()
|
|
1142
1197
|
info = get_version_info(cmd, version_flag, cls.install_github_handle)
|
|
1143
|
-
if not info['installed'] or info['
|
|
1198
|
+
if not info['installed'] or info['outdated'] or not info['latest_version']:
|
|
1144
1199
|
# with console.status(f'[bold yellow]Installing {cls.__name__} ...'):
|
|
1145
1200
|
status = ToolInstaller.install(cls)
|
|
1146
1201
|
if not status.is_ok():
|
|
@@ -1375,73 +1430,151 @@ def performance(tasks, workflows, scans, test):
|
|
|
1375
1430
|
@test.command()
|
|
1376
1431
|
@click.argument('name', type=str)
|
|
1377
1432
|
@click.option('--verbose', '-v', is_flag=True, default=False, help='Print verbose output')
|
|
1378
|
-
|
|
1379
|
-
|
|
1433
|
+
@click.option('--check', '-c', is_flag=True, default=False, help='Check task semantics only (no unit + integration tests)') # noqa: E501
|
|
1434
|
+
def task(name, verbose, check):
|
|
1435
|
+
"""Test a single task for semantics errors, and run unit + integration tests."""
|
|
1436
|
+
console.print(f'[bold gold3]:wrench: Testing task {name} ...[/]')
|
|
1380
1437
|
task = [task for task in ALL_TASKS if task.__name__ == name]
|
|
1381
1438
|
warnings = []
|
|
1439
|
+
errors = []
|
|
1382
1440
|
exit_code = 0
|
|
1383
1441
|
|
|
1384
1442
|
# Check if task is correctly registered
|
|
1385
|
-
check_error(task, 'Check task is registered', 'Task is not registered. Make sure there is no syntax errors in the task class definition.', warnings) # noqa: E501
|
|
1386
1443
|
task = task[0]
|
|
1387
1444
|
task_name = task.__name__
|
|
1388
1445
|
|
|
1446
|
+
# Check task command is set
|
|
1447
|
+
check_test(
|
|
1448
|
+
task.cmd,
|
|
1449
|
+
'Check task command is set (cls.cmd)',
|
|
1450
|
+
'Task has no cmd attribute.',
|
|
1451
|
+
errors
|
|
1452
|
+
)
|
|
1453
|
+
if errors:
|
|
1454
|
+
sys.exit(0)
|
|
1455
|
+
|
|
1389
1456
|
# Run install
|
|
1390
|
-
console.print(f'\n[bold gold3]:wrench: Running install tests for task {name} ...[/]') if verbose else None
|
|
1391
1457
|
cmd = f'secator install tools {task_name}'
|
|
1392
1458
|
ret_code = Command.execute(cmd, name='install', quiet=not verbose, cwd=ROOT_FOLDER)
|
|
1393
1459
|
version_info = task.get_version_info()
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1460
|
+
if verbose:
|
|
1461
|
+
console.print(f'Version info:\n{version_info}')
|
|
1462
|
+
status = version_info['status']
|
|
1463
|
+
check_test(
|
|
1464
|
+
version_info['installed'],
|
|
1465
|
+
'Check task is installed',
|
|
1466
|
+
'Failed to install command. Fix your installation command.',
|
|
1467
|
+
errors
|
|
1468
|
+
)
|
|
1469
|
+
check_test(
|
|
1470
|
+
any(cmd for cmd in [task.install_pre, task.install_cmd, task.install_github_handle]),
|
|
1471
|
+
'Check task installation command is defined',
|
|
1472
|
+
'Task has no installation command. Please define one or more of the following class attributes: `install_pre`, `install_cmd`, `install_post`, `install_github_handle`.', # noqa: E501
|
|
1473
|
+
errors
|
|
1474
|
+
)
|
|
1475
|
+
check_test(
|
|
1476
|
+
version_info['version'],
|
|
1477
|
+
'Check task version can be fetched',
|
|
1478
|
+
'Failed to detect current version. Consider updating your `version_flag` class attribute.',
|
|
1479
|
+
warnings,
|
|
1480
|
+
warn=True
|
|
1481
|
+
)
|
|
1482
|
+
check_test(
|
|
1483
|
+
status != 'latest unknown',
|
|
1484
|
+
'Check latest version',
|
|
1485
|
+
'Failed to detect latest version.',
|
|
1486
|
+
warnings,
|
|
1487
|
+
warn=True
|
|
1488
|
+
)
|
|
1489
|
+
check_test(
|
|
1490
|
+
not version_info['outdated'],
|
|
1491
|
+
'Check task version is up to date',
|
|
1492
|
+
f'Task is not up to date (current version: {version_info["version"]}, latest: {version_info["latest_version"]}). Consider updating your `install_version` class attribute.', # noqa: E501
|
|
1493
|
+
warnings,
|
|
1494
|
+
warn=True
|
|
1495
|
+
)
|
|
1397
1496
|
|
|
1398
1497
|
# Run task-specific tests
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1498
|
+
check_test(
|
|
1499
|
+
task.__doc__,
|
|
1500
|
+
'Check task description is set (cls.__doc__)',
|
|
1501
|
+
'Task has no description (class docstring).',
|
|
1502
|
+
errors
|
|
1503
|
+
)
|
|
1504
|
+
check_test(
|
|
1505
|
+
task.input_type,
|
|
1506
|
+
'Check task input type is set (cls.input_type)',
|
|
1507
|
+
'Task has no input_type attribute.',
|
|
1508
|
+
warnings,
|
|
1509
|
+
warn=True
|
|
1510
|
+
)
|
|
1511
|
+
check_test(
|
|
1512
|
+
task.output_types,
|
|
1513
|
+
'Check task output types is set (cls.output_types)',
|
|
1514
|
+
'Task has no output_types attribute. Consider setting some so that secator can load your task outputs.',
|
|
1515
|
+
warnings,
|
|
1516
|
+
warn=True
|
|
1517
|
+
)
|
|
1518
|
+
check_test(
|
|
1519
|
+
task.install_version,
|
|
1520
|
+
'Check task install_version is set (cls.install_version)',
|
|
1521
|
+
'Task has no install_version attribute. Consider setting it to pin the tool version and ensure it does not break in the future.', # noqa: E501
|
|
1522
|
+
warnings,
|
|
1523
|
+
warn=True
|
|
1524
|
+
)
|
|
1525
|
+
|
|
1526
|
+
if not check:
|
|
1527
|
+
|
|
1528
|
+
# Run unit tests
|
|
1529
|
+
cmd = f'secator test unit --tasks {name}'
|
|
1530
|
+
ret_code = run_test(cmd, exit=False, verbose=verbose)
|
|
1531
|
+
check_test(
|
|
1532
|
+
ret_code == 0,
|
|
1533
|
+
'Check unit tests pass',
|
|
1534
|
+
'Unit tests failed.',
|
|
1535
|
+
errors
|
|
1536
|
+
)
|
|
1537
|
+
|
|
1538
|
+
# Run integration tests
|
|
1539
|
+
cmd = f'secator test integration --tasks {name}'
|
|
1540
|
+
ret_code = run_test(cmd, exit=False, verbose=verbose)
|
|
1541
|
+
check_test(
|
|
1542
|
+
ret_code == 0,
|
|
1543
|
+
'Check integration tests pass',
|
|
1544
|
+
'Integration tests failed.',
|
|
1545
|
+
errors
|
|
1546
|
+
)
|
|
1428
1547
|
|
|
1429
1548
|
# Exit with exit code
|
|
1430
|
-
exit_code = 1 if len(
|
|
1549
|
+
exit_code = 1 if len(errors) > 0 else 0
|
|
1431
1550
|
if exit_code == 0:
|
|
1432
|
-
console.print(f':tada: Task {name} tests passed !
|
|
1551
|
+
console.print(f':tada: Task {name} tests passed !', style='bold green')
|
|
1433
1552
|
else:
|
|
1434
|
-
console.print(
|
|
1553
|
+
console.print('\n[bold gold3]Errors:[/]')
|
|
1554
|
+
for error in errors:
|
|
1555
|
+
console.print(error)
|
|
1556
|
+
console.print(Error(message=f'Task {name} tests failed. Please fix the issues above.'))
|
|
1435
1557
|
|
|
1558
|
+
if warnings:
|
|
1559
|
+
console.print('\n[bold gold3]Warnings:[/]')
|
|
1560
|
+
for warning in warnings:
|
|
1561
|
+
console.print(warning)
|
|
1562
|
+
|
|
1563
|
+
console.print("\n")
|
|
1436
1564
|
sys.exit(exit_code)
|
|
1437
1565
|
|
|
1438
1566
|
|
|
1439
|
-
def
|
|
1567
|
+
def check_test(condition, message, fail_message, results=[], warn=False):
|
|
1440
1568
|
console.print(f'[bold magenta]:zap: {message} ...[/]', end='')
|
|
1441
1569
|
if not condition:
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1570
|
+
if not warn:
|
|
1571
|
+
error = Error(message=fail_message)
|
|
1572
|
+
console.print(' [bold red]FAILED[/]', style='dim')
|
|
1573
|
+
results.append(error)
|
|
1574
|
+
else:
|
|
1575
|
+
warning = Warning(message=fail_message)
|
|
1576
|
+
console.print(' [bold yellow]WARNING[/]', style='dim')
|
|
1577
|
+
results.append(warning)
|
|
1445
1578
|
else:
|
|
1446
1579
|
console.print(' [bold green]OK[/]', style='dim')
|
|
1447
1580
|
return True
|
|
@@ -7,10 +7,19 @@ input_types:
|
|
|
7
7
|
- host
|
|
8
8
|
- cidr_range
|
|
9
9
|
tasks:
|
|
10
|
+
naabu:
|
|
11
|
+
description: Find open ports
|
|
12
|
+
ports: "-" # scan all ports
|
|
10
13
|
nmap:
|
|
11
14
|
description: Search for vulnerabilities on open ports
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
version_detection: True
|
|
16
|
+
script: vulners
|
|
17
|
+
targets_:
|
|
18
|
+
- port.host
|
|
19
|
+
- target.name
|
|
20
|
+
ports_:
|
|
21
|
+
- port.port
|
|
22
|
+
ports: "-" # default if no port found by naabu
|
|
14
23
|
_group/1:
|
|
15
24
|
httpx:
|
|
16
25
|
description: Probe HTTP services on open ports
|
secator/decorators.py
CHANGED
|
@@ -13,6 +13,7 @@ from secator.utils import (deduplicate, expand_input, get_command_category)
|
|
|
13
13
|
|
|
14
14
|
RUNNER_OPTS = {
|
|
15
15
|
'output': {'type': str, 'default': None, 'help': 'Output options (-o table,json,csv,gdrive)', 'short': 'o'},
|
|
16
|
+
'profiles': {'type': str, 'default': 'default', 'help': 'Profiles', 'short': 'pf'},
|
|
16
17
|
'workspace': {'type': str, 'default': 'default', 'help': 'Workspace', 'short': 'ws'},
|
|
17
18
|
'print_json': {'is_flag': True, 'short': 'json', 'default': False, 'help': 'Print items as JSON lines'},
|
|
18
19
|
'print_raw': {'is_flag': True, 'short': 'raw', 'default': False, 'help': 'Print items in raw format'},
|
secator/definitions.py
CHANGED
|
@@ -23,10 +23,6 @@ ASCII = rf"""
|
|
|
23
23
|
DEBUG = CONFIG.debug.level
|
|
24
24
|
DEBUG_COMPONENT = CONFIG.debug.component.split(',')
|
|
25
25
|
|
|
26
|
-
# Default tasks settings
|
|
27
|
-
DEFAULT_NUCLEI_FLAGS = os.environ.get('DEFAULT_NUCLEI_FLAGS', '-stats -sj -si 20 -hm -or')
|
|
28
|
-
DEFAULT_FEROXBUSTER_FLAGS = os.environ.get('DEFAULT_FEROXBUSTER_FLAGS', '--auto-bail --no-state')
|
|
29
|
-
|
|
30
26
|
# Constants
|
|
31
27
|
OPT_NOT_SUPPORTED = -1
|
|
32
28
|
OPT_PIPE_INPUT = -1
|
secator/installer.py
CHANGED
|
@@ -31,8 +31,10 @@ class InstallerStatus(Enum):
|
|
|
31
31
|
INSTALL_FAILED = 'INSTALL_FAILED'
|
|
32
32
|
INSTALL_NOT_SUPPORTED = 'INSTALL_NOT_SUPPORTED'
|
|
33
33
|
INSTALL_SKIPPED_OK = 'INSTALL_SKIPPED_OK'
|
|
34
|
+
INSTALL_VERSION_NOT_SPECIFIED = 'INSTALL_VERSION_NOT_SPECIFIED'
|
|
34
35
|
GITHUB_LATEST_RELEASE_NOT_FOUND = 'GITHUB_LATEST_RELEASE_NOT_FOUND'
|
|
35
36
|
GITHUB_RELEASE_NOT_FOUND = 'RELEASE_NOT_FOUND'
|
|
37
|
+
GITHUB_RELEASE_UNMATCHED_DISTRIBUTION = 'RELEASE_UNMATCHED_DISTRIBUTION'
|
|
36
38
|
GITHUB_RELEASE_FAILED_DOWNLOAD = 'GITHUB_RELEASE_FAILED_DOWNLOAD'
|
|
37
39
|
GITHUB_BINARY_NOT_FOUND_IN_ARCHIVE = 'GITHUB_BINARY_NOT_FOUND_IN_ARCHIVE'
|
|
38
40
|
UNKNOWN_DISTRIBUTION = 'UNKNOWN_DISTRIBUTION'
|
|
@@ -83,12 +85,12 @@ class ToolInstaller:
|
|
|
83
85
|
# Install binaries from GH
|
|
84
86
|
gh_status = InstallerStatus.UNKNOWN
|
|
85
87
|
if tool_cls.install_github_handle and not CONFIG.security.force_source_install:
|
|
86
|
-
gh_status = GithubInstaller.install(tool_cls.install_github_handle)
|
|
88
|
+
gh_status = GithubInstaller.install(tool_cls.install_github_handle, version=tool_cls.install_version or 'latest')
|
|
87
89
|
status = gh_status
|
|
88
90
|
|
|
89
91
|
# Install from source
|
|
90
92
|
if tool_cls.install_cmd and not gh_status.is_ok():
|
|
91
|
-
status = SourceInstaller.install(tool_cls.install_cmd)
|
|
93
|
+
status = SourceInstaller.install(tool_cls.install_cmd, tool_cls.install_version)
|
|
92
94
|
if not status.is_ok():
|
|
93
95
|
cls.print_status(status, name)
|
|
94
96
|
return status
|
|
@@ -167,12 +169,14 @@ class SourceInstaller:
|
|
|
167
169
|
"""Install a tool from source."""
|
|
168
170
|
|
|
169
171
|
@classmethod
|
|
170
|
-
def install(cls, config, install_prereqs=True):
|
|
172
|
+
def install(cls, config, version=None, install_prereqs=True):
|
|
171
173
|
"""Install from source.
|
|
172
174
|
|
|
173
175
|
Args:
|
|
174
176
|
cls: ToolInstaller class.
|
|
175
177
|
config (dict): A dict of distros as keys and a command as value.
|
|
178
|
+
version (str, optional): Version to install.
|
|
179
|
+
install_prereqs (bool, optional): Install pre-requisites.
|
|
176
180
|
|
|
177
181
|
Returns:
|
|
178
182
|
Status: install status.
|
|
@@ -204,6 +208,11 @@ class SourceInstaller:
|
|
|
204
208
|
if not status.is_ok():
|
|
205
209
|
return status
|
|
206
210
|
|
|
211
|
+
# Handle version
|
|
212
|
+
if '[install_version]' in install_cmd:
|
|
213
|
+
version = version or 'latest'
|
|
214
|
+
install_cmd = install_cmd.replace('[install_version]', version)
|
|
215
|
+
|
|
207
216
|
# Run command
|
|
208
217
|
ret = Command.execute(install_cmd, cls_attributes={'shell': True}, quiet=False)
|
|
209
218
|
return InstallerStatus.SUCCESS if ret.return_code == 0 else InstallerStatus.INSTALL_FAILED
|
|
@@ -213,7 +222,7 @@ class GithubInstaller:
|
|
|
213
222
|
"""Install a tool from GitHub releases."""
|
|
214
223
|
|
|
215
224
|
@classmethod
|
|
216
|
-
def install(cls, github_handle):
|
|
225
|
+
def install(cls, github_handle, version='latest'):
|
|
217
226
|
"""Find and install a release from a GitHub handle {user}/{repo}.
|
|
218
227
|
|
|
219
228
|
Args:
|
|
@@ -223,35 +232,38 @@ class GithubInstaller:
|
|
|
223
232
|
InstallerStatus: status.
|
|
224
233
|
"""
|
|
225
234
|
_, repo = tuple(github_handle.split('/'))
|
|
226
|
-
|
|
227
|
-
if not
|
|
228
|
-
return InstallerStatus.
|
|
235
|
+
release = cls.get_release(github_handle, version=version)
|
|
236
|
+
if not release:
|
|
237
|
+
return InstallerStatus.GITHUB_RELEASE_NOT_FOUND
|
|
229
238
|
|
|
230
239
|
# Find the right asset to download
|
|
231
240
|
system, arch, os_identifiers, arch_identifiers = cls._get_platform_identifier()
|
|
232
|
-
download_url = cls._find_matching_asset(
|
|
241
|
+
download_url = cls._find_matching_asset(release['assets'], os_identifiers, arch_identifiers)
|
|
233
242
|
if not download_url:
|
|
234
243
|
console.print(Error(message=f'Could not find a GitHub release matching distribution (system: {system}, arch: {arch}).')) # noqa: E501
|
|
235
|
-
return InstallerStatus.
|
|
244
|
+
return InstallerStatus.GITHUB_RELEASE_UNMATCHED_DISTRIBUTION
|
|
236
245
|
|
|
237
246
|
# Download and unpack asset
|
|
238
247
|
console.print(Info(message=f'Found release URL: {download_url}'))
|
|
239
248
|
return cls._download_and_unpack(download_url, CONFIG.dirs.bin, repo)
|
|
240
249
|
|
|
241
250
|
@classmethod
|
|
242
|
-
def
|
|
243
|
-
"""Get
|
|
251
|
+
def get_release(cls, github_handle, version='latest'):
|
|
252
|
+
"""Get release from GitHub.
|
|
244
253
|
|
|
245
254
|
Args:
|
|
246
255
|
github_handle (str): A GitHub handle {user}/{repo}.
|
|
247
256
|
|
|
248
257
|
Returns:
|
|
249
|
-
dict:
|
|
258
|
+
dict: Release JSON from GitHub releases.
|
|
250
259
|
"""
|
|
251
260
|
if not github_handle:
|
|
252
261
|
return False
|
|
253
262
|
owner, repo = tuple(github_handle.split('/'))
|
|
254
|
-
|
|
263
|
+
if version == 'latest':
|
|
264
|
+
url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest"
|
|
265
|
+
else:
|
|
266
|
+
url = f"https://api.github.com/repos/{owner}/{repo}/releases/tags/{version}"
|
|
255
267
|
headers = {}
|
|
256
268
|
if CONFIG.cli.github_token:
|
|
257
269
|
headers['Authorization'] = f'Bearer {CONFIG.cli.github_token}'
|
|
@@ -268,7 +280,7 @@ class GithubInstaller:
|
|
|
268
280
|
|
|
269
281
|
@classmethod
|
|
270
282
|
def get_latest_version(cls, github_handle):
|
|
271
|
-
latest_release = cls.
|
|
283
|
+
latest_release = cls.get_release(github_handle, version='latest')
|
|
272
284
|
if not latest_release:
|
|
273
285
|
return None
|
|
274
286
|
return latest_release['tag_name'].lstrip('v')
|
|
@@ -444,6 +456,7 @@ def get_version_info(name, version_flag=None, install_github_handle=None, instal
|
|
|
444
456
|
'latest_version': None,
|
|
445
457
|
'location': None,
|
|
446
458
|
'status': '',
|
|
459
|
+
'outdated': False,
|
|
447
460
|
'errors': []
|
|
448
461
|
}
|
|
449
462
|
|
|
@@ -504,6 +517,7 @@ def get_version_info(name, version_flag=None, install_github_handle=None, instal
|
|
|
504
517
|
if version and latest_version:
|
|
505
518
|
if parse_version(version) < parse_version(latest_version):
|
|
506
519
|
info['status'] = 'outdated'
|
|
520
|
+
info['outdated'] = True
|
|
507
521
|
else:
|
|
508
522
|
info['status'] = 'latest'
|
|
509
523
|
elif not version:
|
|
@@ -529,7 +543,7 @@ def get_distro_config():
|
|
|
529
543
|
distrib = system
|
|
530
544
|
|
|
531
545
|
if system == "Linux":
|
|
532
|
-
distrib = distro.id()
|
|
546
|
+
distrib = distro.id() or distro.like()
|
|
533
547
|
|
|
534
548
|
if distrib in ["ubuntu", "debian", "linuxmint", "popos", "kali"]:
|
|
535
549
|
installer = "apt install -y --no-install-recommends"
|