secator 0.11.1__py3-none-any.whl → 0.13.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 +175 -66
- secator/config.py +1 -0
- secator/configs/workflows/host_recon.yaml +11 -2
- secator/configs/workflows/port_scan.yaml +39 -0
- secator/configs/workflows/url_dirsearch.yaml +5 -0
- secator/configs/workflows/url_params_fuzz.yaml +2 -0
- secator/decorators.py +39 -21
- secator/definitions.py +0 -4
- secator/installer.py +29 -15
- secator/runners/_base.py +2 -1
- secator/runners/_helpers.py +13 -2
- secator/runners/command.py +3 -1
- secator/tasks/_categories.py +2 -2
- secator/tasks/arjun.py +11 -2
- 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 +12 -0
- {secator-0.11.1.dist-info → secator-0.13.0.dist-info}/METADATA +1 -1
- {secator-0.11.1.dist-info → secator-0.13.0.dist-info}/RECORD +48 -47
- {secator-0.11.1.dist-info → secator-0.13.0.dist-info}/WHEEL +0 -0
- {secator-0.11.1.dist-info → secator-0.13.0.dist-info}/entry_points.txt +0 -0
- {secator-0.11.1.dist-info → secator-0.13.0.dist-info}/licenses/LICENSE +0 -0
secator/cli.py
CHANGED
|
@@ -37,7 +37,7 @@ 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
39
|
FINDING_TYPES_LOWER = [c.__name__.lower() for c in FINDING_TYPES]
|
|
40
|
-
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
|
40
|
+
CONTEXT_SETTINGS = dict(help_option_names=['-h', '-help', '--help'])
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
#-----#
|
|
@@ -46,15 +46,16 @@ CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
@click.group(cls=OrderedGroup, invoke_without_command=True, context_settings=CONTEXT_SETTINGS)
|
|
49
|
-
@click.option('--version', '-version', is_flag=True, default=False)
|
|
49
|
+
@click.option('--version', '-version', '-v', is_flag=True, default=False)
|
|
50
|
+
@click.option('--quiet', '-quiet', '-q', is_flag=True, default=False)
|
|
50
51
|
@click.pass_context
|
|
51
|
-
def cli(ctx, version):
|
|
52
|
+
def cli(ctx, version, quiet):
|
|
52
53
|
"""Secator CLI."""
|
|
53
54
|
ctx.obj = {
|
|
54
55
|
'piped_input': S_ISFIFO(os.fstat(0).st_mode),
|
|
55
56
|
'piped_output': not sys.stdout.isatty()
|
|
56
57
|
}
|
|
57
|
-
if not ctx.obj['piped_output']:
|
|
58
|
+
if not ctx.obj['piped_output'] and not quiet:
|
|
58
59
|
console.print(ASCII, highlight=False)
|
|
59
60
|
if ctx.invoked_subcommand is None:
|
|
60
61
|
if version:
|
|
@@ -67,11 +68,16 @@ def cli(ctx, version):
|
|
|
67
68
|
# TASK #
|
|
68
69
|
#------#
|
|
69
70
|
|
|
70
|
-
@cli.group(aliases=['x', 't'])
|
|
71
|
+
@cli.group(aliases=['x', 't'], invoke_without_command=True)
|
|
72
|
+
@click.option('--list', '-list', is_flag=True, default=False)
|
|
71
73
|
@click.pass_context
|
|
72
|
-
def task(ctx):
|
|
74
|
+
def task(ctx, list=False):
|
|
73
75
|
"""Run a task."""
|
|
74
|
-
|
|
76
|
+
if list:
|
|
77
|
+
print("\n".join(sorted([t.__name__ for t in ALL_TASKS])))
|
|
78
|
+
return
|
|
79
|
+
if ctx.invoked_subcommand is None:
|
|
80
|
+
ctx.get_help()
|
|
75
81
|
|
|
76
82
|
|
|
77
83
|
for cls in ALL_TASKS:
|
|
@@ -83,11 +89,16 @@ for cls in ALL_TASKS:
|
|
|
83
89
|
#----------#
|
|
84
90
|
|
|
85
91
|
|
|
86
|
-
@cli.group(cls=OrderedGroup, aliases=['w'])
|
|
92
|
+
@cli.group(cls=OrderedGroup, aliases=['w'], invoke_without_command=True)
|
|
93
|
+
@click.option('--list', '-list', is_flag=True, default=False)
|
|
87
94
|
@click.pass_context
|
|
88
|
-
def workflow(ctx):
|
|
95
|
+
def workflow(ctx, list=False):
|
|
89
96
|
"""Run a workflow."""
|
|
90
|
-
|
|
97
|
+
if list:
|
|
98
|
+
print("\n".join(sorted([t.name for t in ALL_WORKFLOWS])))
|
|
99
|
+
return
|
|
100
|
+
if ctx.invoked_subcommand is None:
|
|
101
|
+
ctx.get_help()
|
|
91
102
|
|
|
92
103
|
|
|
93
104
|
for config in sorted(ALL_WORKFLOWS, key=lambda x: x['name']):
|
|
@@ -98,11 +109,16 @@ for config in sorted(ALL_WORKFLOWS, key=lambda x: x['name']):
|
|
|
98
109
|
# SCAN #
|
|
99
110
|
#------#
|
|
100
111
|
|
|
101
|
-
@cli.group(cls=OrderedGroup, aliases=['s'])
|
|
112
|
+
@cli.group(cls=OrderedGroup, aliases=['s'], invoke_without_command=True)
|
|
113
|
+
@click.option('--list', '-list', is_flag=True, default=False)
|
|
102
114
|
@click.pass_context
|
|
103
|
-
def scan(ctx):
|
|
115
|
+
def scan(ctx, list=False):
|
|
104
116
|
"""Run a scan."""
|
|
105
|
-
|
|
117
|
+
if list:
|
|
118
|
+
print("\n".join(sorted([t.name for t in ALL_SCANS])))
|
|
119
|
+
return
|
|
120
|
+
if ctx.invoked_subcommand is None:
|
|
121
|
+
ctx.get_help()
|
|
106
122
|
|
|
107
123
|
|
|
108
124
|
for config in sorted(ALL_SCANS, key=lambda x: x['name']):
|
|
@@ -119,7 +135,7 @@ for config in sorted(ALL_SCANS, key=lambda x: x['name']):
|
|
|
119
135
|
@click.option('-r', '--reload', is_flag=True, help='Autoreload Celery on code changes.')
|
|
120
136
|
@click.option('-Q', '--queue', type=str, default='', help='Listen to a specific queue.')
|
|
121
137
|
@click.option('-P', '--pool', type=str, default='eventlet', help='Pool implementation.')
|
|
122
|
-
@click.option('--quiet', is_flag=True, help='Quiet mode.')
|
|
138
|
+
@click.option('--quiet', is_flag=True, default=False, help='Quiet mode.')
|
|
123
139
|
@click.option('--loglevel', type=str, default='INFO', help='Log level.')
|
|
124
140
|
@click.option('--check', is_flag=True, help='Check if Celery worker is alive.')
|
|
125
141
|
@click.option('--dev', is_flag=True, help='Start a worker in dev mode (celery multi).')
|
|
@@ -860,13 +876,21 @@ def health(json, debug, strict):
|
|
|
860
876
|
import json as _json
|
|
861
877
|
print(_json.dumps(status))
|
|
862
878
|
|
|
879
|
+
# Print errors and warnings
|
|
880
|
+
error = False
|
|
881
|
+
for tool, info in status['tools'].items():
|
|
882
|
+
if not info['installed']:
|
|
883
|
+
console.print(Warning(message=f'{tool} is not installed.'))
|
|
884
|
+
error = True
|
|
885
|
+
elif info['outdated']:
|
|
886
|
+
message = (
|
|
887
|
+
f'{tool} is outdated (current:{info["version"]}, latest:{info["latest_version"]}).'
|
|
888
|
+
f' Run `secator install tools {tool}` to update it.'
|
|
889
|
+
)
|
|
890
|
+
console.print(Warning(message=message))
|
|
891
|
+
|
|
863
892
|
# Strict mode
|
|
864
893
|
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
894
|
if error:
|
|
871
895
|
sys.exit(1)
|
|
872
896
|
console.print(Info(message='Strict healthcheck passed !'))
|
|
@@ -1061,16 +1085,27 @@ def install_tools(cmds, cleanup, fail_fast):
|
|
|
1061
1085
|
if CONFIG.offline_mode:
|
|
1062
1086
|
console.print(Error(message='Cannot run this command in offline mode.'))
|
|
1063
1087
|
return
|
|
1088
|
+
tools = []
|
|
1064
1089
|
if cmds is not None:
|
|
1065
1090
|
cmds = cmds.split(',')
|
|
1066
|
-
|
|
1091
|
+
for cmd in cmds:
|
|
1092
|
+
if '==' in cmd:
|
|
1093
|
+
cmd, version = tuple(cmd.split('=='))
|
|
1094
|
+
else:
|
|
1095
|
+
cmd, version = cmd, None
|
|
1096
|
+
cls = next((cls for cls in ALL_TASKS if cls.__name__ == cmd), None)
|
|
1097
|
+
if cls:
|
|
1098
|
+
if version:
|
|
1099
|
+
cls.install_version = version
|
|
1100
|
+
tools.append(cls)
|
|
1101
|
+
else:
|
|
1102
|
+
console.print(Warning(message=f'Tool {cmd} is not supported or inexistent.'))
|
|
1067
1103
|
else:
|
|
1068
1104
|
tools = ALL_TASKS
|
|
1069
1105
|
tools.sort(key=lambda x: x.__name__)
|
|
1070
1106
|
return_code = 0
|
|
1071
1107
|
if not tools:
|
|
1072
|
-
|
|
1073
|
-
console.print(Error(message=f'No tools found for {cmd_str}.'))
|
|
1108
|
+
console.print(Error(message='No tools found for installing.'))
|
|
1074
1109
|
return
|
|
1075
1110
|
for ix, cls in enumerate(tools):
|
|
1076
1111
|
# with console.status(f'[bold yellow][{ix + 1}/{len(tools)}] Installing {cls.__name__} ...'):
|
|
@@ -1140,7 +1175,7 @@ def update(all):
|
|
|
1140
1175
|
cmd = cls.cmd.split(' ')[0]
|
|
1141
1176
|
version_flag = cls.get_version_flag()
|
|
1142
1177
|
info = get_version_info(cmd, version_flag, cls.install_github_handle)
|
|
1143
|
-
if not info['installed'] or info['
|
|
1178
|
+
if not info['installed'] or info['outdated'] or not info['latest_version']:
|
|
1144
1179
|
# with console.status(f'[bold yellow]Installing {cls.__name__} ...'):
|
|
1145
1180
|
status = ToolInstaller.install(cls)
|
|
1146
1181
|
if not status.is_ok():
|
|
@@ -1375,73 +1410,147 @@ def performance(tasks, workflows, scans, test):
|
|
|
1375
1410
|
@test.command()
|
|
1376
1411
|
@click.argument('name', type=str)
|
|
1377
1412
|
@click.option('--verbose', '-v', is_flag=True, default=False, help='Print verbose output')
|
|
1378
|
-
|
|
1379
|
-
|
|
1413
|
+
@click.option('--check', '-c', is_flag=True, default=False, help='Check task semantics only (no unit + integration tests)') # noqa: E501
|
|
1414
|
+
def task(name, verbose, check):
|
|
1415
|
+
"""Test a single task for semantics errors, and run unit + integration tests."""
|
|
1416
|
+
console.print(f'[bold gold3]:wrench: Testing task {name} ...[/]')
|
|
1380
1417
|
task = [task for task in ALL_TASKS if task.__name__ == name]
|
|
1381
1418
|
warnings = []
|
|
1419
|
+
errors = []
|
|
1382
1420
|
exit_code = 0
|
|
1383
1421
|
|
|
1384
1422
|
# 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
1423
|
task = task[0]
|
|
1387
1424
|
task_name = task.__name__
|
|
1388
1425
|
|
|
1389
1426
|
# Run install
|
|
1390
|
-
console.print(f'\n[bold gold3]:wrench: Running install tests for task {name} ...[/]') if verbose else None
|
|
1391
1427
|
cmd = f'secator install tools {task_name}'
|
|
1392
1428
|
ret_code = Command.execute(cmd, name='install', quiet=not verbose, cwd=ROOT_FOLDER)
|
|
1393
1429
|
version_info = task.get_version_info()
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1430
|
+
if verbose:
|
|
1431
|
+
console.print(f'Version info:\n{version_info}')
|
|
1432
|
+
status = version_info['status']
|
|
1433
|
+
check_test(
|
|
1434
|
+
version_info['installed'],
|
|
1435
|
+
'Check task is installed',
|
|
1436
|
+
'Failed to install command. Fix your installation command.',
|
|
1437
|
+
errors
|
|
1438
|
+
)
|
|
1439
|
+
check_test(
|
|
1440
|
+
any(cmd for cmd in [task.install_cmd, task.install_github_handle]),
|
|
1441
|
+
'Check task installation command is defined',
|
|
1442
|
+
'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
|
|
1443
|
+
errors
|
|
1444
|
+
)
|
|
1445
|
+
check_test(
|
|
1446
|
+
version_info['version'],
|
|
1447
|
+
'Check task version can be fetched',
|
|
1448
|
+
'Failed to detect current version. Consider updating your `version_flag` class attribute.',
|
|
1449
|
+
warnings,
|
|
1450
|
+
warn=True
|
|
1451
|
+
)
|
|
1452
|
+
check_test(
|
|
1453
|
+
status != 'latest unknown',
|
|
1454
|
+
'Check latest version',
|
|
1455
|
+
'Failed to detect latest version.',
|
|
1456
|
+
warnings,
|
|
1457
|
+
warn=True
|
|
1458
|
+
)
|
|
1459
|
+
check_test(
|
|
1460
|
+
not version_info['outdated'],
|
|
1461
|
+
'Check task version is up to date',
|
|
1462
|
+
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
|
|
1463
|
+
warnings,
|
|
1464
|
+
warn=True
|
|
1465
|
+
)
|
|
1397
1466
|
|
|
1398
1467
|
# 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
|
-
|
|
1468
|
+
check_test(
|
|
1469
|
+
task.__doc__,
|
|
1470
|
+
'Check task description is set (cls.__doc__)',
|
|
1471
|
+
'Task has no description (class docstring).',
|
|
1472
|
+
errors
|
|
1473
|
+
)
|
|
1474
|
+
check_test(
|
|
1475
|
+
task.cmd,
|
|
1476
|
+
'Check task command is set (cls.cmd)',
|
|
1477
|
+
'Task has no cmd attribute.',
|
|
1478
|
+
errors
|
|
1479
|
+
)
|
|
1480
|
+
check_test(
|
|
1481
|
+
task.input_type,
|
|
1482
|
+
'Check task input type is set (cls.input_type)',
|
|
1483
|
+
'Task has no input_type attribute.',
|
|
1484
|
+
warnings,
|
|
1485
|
+
warn=True
|
|
1486
|
+
)
|
|
1487
|
+
check_test(
|
|
1488
|
+
task.output_types,
|
|
1489
|
+
'Check task output types is set (cls.output_types)',
|
|
1490
|
+
'Task has no output_types attribute. Consider setting some so that secator can load your task outputs.',
|
|
1491
|
+
warnings,
|
|
1492
|
+
warn=True
|
|
1493
|
+
)
|
|
1494
|
+
check_test(
|
|
1495
|
+
task.install_version,
|
|
1496
|
+
'Check task install_version is set (cls.install_version)',
|
|
1497
|
+
'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
|
|
1498
|
+
warnings,
|
|
1499
|
+
warn=True
|
|
1500
|
+
)
|
|
1501
|
+
|
|
1502
|
+
if not check:
|
|
1503
|
+
|
|
1504
|
+
# Run unit tests
|
|
1505
|
+
cmd = f'secator test unit --tasks {name}'
|
|
1506
|
+
ret_code = run_test(cmd, exit=False, verbose=verbose)
|
|
1507
|
+
check_test(
|
|
1508
|
+
ret_code == 0,
|
|
1509
|
+
'Check unit tests pass',
|
|
1510
|
+
'Unit tests failed.',
|
|
1511
|
+
errors
|
|
1512
|
+
)
|
|
1513
|
+
|
|
1514
|
+
# Run integration tests
|
|
1515
|
+
cmd = f'secator test integration --tasks {name}'
|
|
1516
|
+
ret_code = run_test(cmd, exit=False, verbose=verbose)
|
|
1517
|
+
check_test(
|
|
1518
|
+
ret_code == 0,
|
|
1519
|
+
'Check integration tests pass',
|
|
1520
|
+
'Integration tests failed.',
|
|
1521
|
+
errors
|
|
1522
|
+
)
|
|
1428
1523
|
|
|
1429
1524
|
# Exit with exit code
|
|
1430
|
-
exit_code = 1 if len(
|
|
1525
|
+
exit_code = 1 if len(errors) > 0 else 0
|
|
1431
1526
|
if exit_code == 0:
|
|
1432
|
-
console.print(f':tada: Task {name} tests passed !
|
|
1527
|
+
console.print(f':tada: Task {name} tests passed !', style='bold green')
|
|
1433
1528
|
else:
|
|
1434
|
-
console.print(
|
|
1529
|
+
console.print('\n[bold gold3]Errors:[/]')
|
|
1530
|
+
for error in errors:
|
|
1531
|
+
console.print(error)
|
|
1532
|
+
console.print(Error(message=f'Task {name} tests failed. Please fix the issues above.'))
|
|
1435
1533
|
|
|
1534
|
+
if warnings:
|
|
1535
|
+
console.print('\n[bold gold3]Warnings:[/]')
|
|
1536
|
+
for warning in warnings:
|
|
1537
|
+
console.print(warning)
|
|
1538
|
+
|
|
1539
|
+
console.print("\n")
|
|
1436
1540
|
sys.exit(exit_code)
|
|
1437
1541
|
|
|
1438
1542
|
|
|
1439
|
-
def
|
|
1543
|
+
def check_test(condition, message, fail_message, results=[], warn=False):
|
|
1440
1544
|
console.print(f'[bold magenta]:zap: {message} ...[/]', end='')
|
|
1441
1545
|
if not condition:
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1546
|
+
if not warn:
|
|
1547
|
+
error = Error(message=fail_message)
|
|
1548
|
+
console.print(' [bold red]FAILED[/]', style='dim')
|
|
1549
|
+
results.append(error)
|
|
1550
|
+
else:
|
|
1551
|
+
warning = Warning(message=fail_message)
|
|
1552
|
+
console.print(' [bold yellow]WARNING[/]', style='dim')
|
|
1553
|
+
results.append(warning)
|
|
1445
1554
|
else:
|
|
1446
1555
|
console.print(' [bold green]OK[/]', style='dim')
|
|
1447
1556
|
return True
|
secator/config.py
CHANGED
|
@@ -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
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
type: workflow
|
|
2
|
+
name: port_scan
|
|
3
|
+
alias: pscan
|
|
4
|
+
description: Port scan
|
|
5
|
+
tags: [recon, network, http, vuln]
|
|
6
|
+
input_types:
|
|
7
|
+
- host
|
|
8
|
+
- cidr_range
|
|
9
|
+
tasks:
|
|
10
|
+
naabu:
|
|
11
|
+
description: Find open ports
|
|
12
|
+
skip_host_discovery: True
|
|
13
|
+
ports: "-" # scan all ports
|
|
14
|
+
nmap:
|
|
15
|
+
description: Search for vulnerabilities on open ports
|
|
16
|
+
skip_host_discovery: True
|
|
17
|
+
version_detection: True
|
|
18
|
+
targets_: port.host
|
|
19
|
+
ports_: port.port
|
|
20
|
+
_group:
|
|
21
|
+
searchsploit:
|
|
22
|
+
description: Search for related exploits
|
|
23
|
+
targets_:
|
|
24
|
+
- type: port
|
|
25
|
+
field: '{host}~{service_name}'
|
|
26
|
+
condition: item._source.startswith('nmap') and len(item.service_name.split('/')) > 1
|
|
27
|
+
httpx:
|
|
28
|
+
description: Probe HTTP services on open ports
|
|
29
|
+
targets_:
|
|
30
|
+
- type: port
|
|
31
|
+
field: '{host}:{port}'
|
|
32
|
+
condition: item._source.startswith('nmap')
|
|
33
|
+
results:
|
|
34
|
+
- type: port
|
|
35
|
+
|
|
36
|
+
- type: url
|
|
37
|
+
condition: item.status_code != 0
|
|
38
|
+
|
|
39
|
+
- type: vulnerability
|
|
@@ -11,6 +11,8 @@ tasks:
|
|
|
11
11
|
ffuf:
|
|
12
12
|
description: Fuzz URL params
|
|
13
13
|
wordlist: https://raw.githubusercontent.com/danielmiessler/SecLists/refs/heads/master/Discovery/Web-Content/burp-parameter-names.txt
|
|
14
|
+
auto_calibration: true
|
|
15
|
+
follow_redirect: true
|
|
14
16
|
targets_:
|
|
15
17
|
- type: url
|
|
16
18
|
field: url
|
secator/decorators.py
CHANGED
|
@@ -19,11 +19,11 @@ RUNNER_OPTS = {
|
|
|
19
19
|
'print_stat': {'is_flag': True, 'short': 'stat', 'default': False, 'help': 'Print runtime statistics'},
|
|
20
20
|
'print_format': {'default': '', 'short': 'fmt', 'help': 'Output formatting string'},
|
|
21
21
|
'enable_profiler': {'is_flag': True, 'short': 'prof', 'default': False, 'help': 'Enable runner profiling'},
|
|
22
|
-
'show': {'is_flag': True, 'short': 'sh', 'default': False, 'help': 'Show command that will be run (tasks only)'},
|
|
23
22
|
'no_process': {'is_flag': True, 'short': 'nps', 'default': False, 'help': 'Disable secator processing'},
|
|
24
23
|
# 'filter': {'default': '', 'short': 'f', 'help': 'Results filter', 'short': 'of'}, # TODO add this
|
|
25
|
-
'quiet': {'is_flag': True, 'short': 'q', 'default':
|
|
24
|
+
'quiet': {'is_flag': True, 'short': 'q', 'default': not CONFIG.runners.show_command_output, 'opposite': 'verbose', 'help': 'Enable quiet mode'}, # noqa: E501
|
|
26
25
|
'dry_run': {'is_flag': True, 'short': 'dr', 'default': False, 'help': 'Enable dry run'},
|
|
26
|
+
'show': {'is_flag': True, 'short': 'yml', 'default': False, 'help': 'Show runner yaml'},
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
RUNNER_GLOBAL_OPTS = {
|
|
@@ -163,35 +163,39 @@ def get_command_options(config):
|
|
|
163
163
|
elif opt in RUNNER_GLOBAL_OPTS:
|
|
164
164
|
prefix = 'Execution'
|
|
165
165
|
|
|
166
|
+
# Get opt value from YAML config
|
|
167
|
+
opt_conf_value = task_config_opts.get(opt)
|
|
168
|
+
|
|
166
169
|
# Get opt conf
|
|
167
170
|
conf = opt_conf.copy()
|
|
171
|
+
opt_is_flag = conf.get('is_flag', False)
|
|
172
|
+
opt_default = conf.get('default', False if opt_is_flag else None)
|
|
173
|
+
opt_is_required = conf.get('required', False)
|
|
168
174
|
conf['show_default'] = True
|
|
169
175
|
conf['prefix'] = prefix
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
opt_value_in_config = task_config_opts.get(opt)
|
|
176
|
+
conf['default'] = opt_default
|
|
177
|
+
conf['reverse'] = False
|
|
173
178
|
|
|
174
|
-
#
|
|
175
|
-
if
|
|
176
|
-
if
|
|
179
|
+
# Change CLI opt defaults if opt was overriden in YAML config
|
|
180
|
+
if opt_conf_value:
|
|
181
|
+
if opt_is_required:
|
|
177
182
|
debug('OPT (skipped: opt is required and defined in config)', obj={'opt': opt}, sub=f'cli.{config.name}', verbose=True) # noqa: E501
|
|
178
183
|
continue
|
|
179
184
|
mapped_value = cls.opt_value_map.get(opt)
|
|
180
185
|
if callable(mapped_value):
|
|
181
|
-
|
|
186
|
+
opt_conf_value = mapped_value(opt_conf_value)
|
|
182
187
|
elif mapped_value:
|
|
183
|
-
|
|
184
|
-
|
|
188
|
+
opt_conf_value = mapped_value
|
|
189
|
+
|
|
190
|
+
# Handle option defaults
|
|
191
|
+
if opt_conf_value != opt_default:
|
|
185
192
|
if opt in opt_cache:
|
|
186
193
|
continue
|
|
187
194
|
if opt_is_flag:
|
|
188
|
-
conf['
|
|
189
|
-
conf['default'] = not conf['default']
|
|
190
|
-
# print(f'{opt}: change default to {opt_value_in_config}')
|
|
191
|
-
conf['default'] = opt_value_in_config
|
|
195
|
+
conf['default'] = opt_default = opt_conf_value
|
|
192
196
|
|
|
193
|
-
#
|
|
194
|
-
if
|
|
197
|
+
# Add reverse flag
|
|
198
|
+
if opt_default is True:
|
|
195
199
|
conf['reverse'] = True
|
|
196
200
|
|
|
197
201
|
# Check if opt already processed before
|
|
@@ -205,7 +209,7 @@ def get_command_options(config):
|
|
|
205
209
|
all_opts[opt] = conf
|
|
206
210
|
|
|
207
211
|
# Debug
|
|
208
|
-
debug_conf = OrderedDict({'opt': opt, 'config_val':
|
|
212
|
+
debug_conf = OrderedDict({'opt': opt, 'config_val': opt_conf_value or 'N/A', **conf.copy()})
|
|
209
213
|
debug('OPT', obj=debug_conf, sub=f'cli.{config.name}', verbose=True)
|
|
210
214
|
|
|
211
215
|
return all_opts
|
|
@@ -236,11 +240,17 @@ def decorate_command_options(opts):
|
|
|
236
240
|
conf.pop('process', None)
|
|
237
241
|
conf.pop('requires_sudo', None)
|
|
238
242
|
reverse = conf.pop('reverse', False)
|
|
243
|
+
opposite = conf.pop('opposite', None)
|
|
239
244
|
long = f'--{opt_name}'
|
|
240
245
|
short = f'-{short_opt}' if short_opt else f'-{opt_name}'
|
|
241
246
|
if reverse:
|
|
242
|
-
|
|
243
|
-
|
|
247
|
+
if opposite:
|
|
248
|
+
long += f'/--{opposite}'
|
|
249
|
+
short += f'/-{opposite[0]}'
|
|
250
|
+
conf['help'] = conf['help'].replace(opt_name, f'{opt_name} / {opposite}')
|
|
251
|
+
else:
|
|
252
|
+
long += f'/--no-{opt_name}'
|
|
253
|
+
short += f'/-n{short_opt}' if short else f'/-n{opt_name}'
|
|
244
254
|
f = click.option(long, short, **conf)(f)
|
|
245
255
|
return f
|
|
246
256
|
return decorator
|
|
@@ -321,9 +331,16 @@ def register_runner(cli_endpoint, config):
|
|
|
321
331
|
worker = opts.pop('worker')
|
|
322
332
|
ws = opts.pop('workspace')
|
|
323
333
|
driver = opts.pop('driver', '')
|
|
334
|
+
quiet = opts['quiet']
|
|
335
|
+
dry_run = opts['dry_run']
|
|
324
336
|
show = opts['show']
|
|
325
337
|
context = {'workspace_name': ws}
|
|
326
338
|
|
|
339
|
+
# Show runner yaml
|
|
340
|
+
if show:
|
|
341
|
+
config.print()
|
|
342
|
+
sys.exit(0)
|
|
343
|
+
|
|
327
344
|
# Remove options whose values are default values
|
|
328
345
|
for k, v in options.items():
|
|
329
346
|
opt_name = k.replace('-', '_')
|
|
@@ -364,7 +381,7 @@ def register_runner(cli_endpoint, config):
|
|
|
364
381
|
hooks = deep_merge_dicts(*hooks)
|
|
365
382
|
|
|
366
383
|
# Enable sync or not
|
|
367
|
-
if sync or
|
|
384
|
+
if sync or dry_run:
|
|
368
385
|
sync = True
|
|
369
386
|
else:
|
|
370
387
|
from secator.celery import is_celery_worker_alive
|
|
@@ -394,6 +411,7 @@ def register_runner(cli_endpoint, config):
|
|
|
394
411
|
'piped_output': ctx.obj['piped_output'],
|
|
395
412
|
'caller': 'cli',
|
|
396
413
|
'sync': sync,
|
|
414
|
+
'quiet': quiet
|
|
397
415
|
})
|
|
398
416
|
|
|
399
417
|
# Start runner
|
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
|