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.

Files changed (56) hide show
  1. secator/cli.py +199 -66
  2. secator/configs/profiles/aggressive.yaml +6 -5
  3. secator/configs/profiles/default.yaml +6 -7
  4. secator/configs/profiles/insane.yaml +8 -0
  5. secator/configs/profiles/paranoid.yaml +8 -0
  6. secator/configs/profiles/polite.yaml +8 -0
  7. secator/configs/profiles/sneaky.yaml +8 -0
  8. secator/configs/profiles/tor.yaml +5 -0
  9. secator/configs/workflows/host_recon.yaml +11 -2
  10. secator/configs/workflows/url_dirsearch.yaml +5 -0
  11. secator/decorators.py +1 -0
  12. secator/definitions.py +0 -4
  13. secator/installer.py +29 -15
  14. secator/report.py +2 -2
  15. secator/runners/_base.py +32 -1
  16. secator/runners/_helpers.py +13 -2
  17. secator/runners/command.py +2 -1
  18. secator/runners/scan.py +1 -0
  19. secator/runners/task.py +1 -0
  20. secator/tasks/_categories.py +2 -2
  21. secator/tasks/arjun.py +2 -1
  22. secator/tasks/bbot.py +30 -4
  23. secator/tasks/bup.py +2 -1
  24. secator/tasks/cariddi.py +15 -3
  25. secator/tasks/dalfox.py +2 -1
  26. secator/tasks/dirsearch.py +1 -1
  27. secator/tasks/dnsx.py +2 -1
  28. secator/tasks/dnsxbrute.py +2 -1
  29. secator/tasks/feroxbuster.py +3 -2
  30. secator/tasks/ffuf.py +2 -1
  31. secator/tasks/gau.py +2 -1
  32. secator/tasks/gitleaks.py +4 -3
  33. secator/tasks/gospider.py +3 -2
  34. secator/tasks/grype.py +1 -0
  35. secator/tasks/h8mail.py +2 -1
  36. secator/tasks/httpx.py +3 -2
  37. secator/tasks/katana.py +4 -3
  38. secator/tasks/maigret.py +1 -1
  39. secator/tasks/mapcidr.py +2 -1
  40. secator/tasks/msfconsole.py +4 -3
  41. secator/tasks/naabu.py +4 -2
  42. secator/tasks/nuclei.py +15 -9
  43. secator/tasks/searchsploit.py +3 -2
  44. secator/tasks/subfinder.py +2 -1
  45. secator/tasks/testssl.py +4 -3
  46. secator/tasks/trivy.py +2 -2
  47. secator/tasks/wafw00f.py +2 -1
  48. secator/tasks/wpprobe.py +2 -1
  49. secator/tasks/wpscan.py +6 -3
  50. secator/template.py +1 -1
  51. {secator-0.12.0.dist-info → secator-0.14.0.dist-info}/METADATA +1 -1
  52. {secator-0.12.0.dist-info → secator-0.14.0.dist-info}/RECORD +55 -51
  53. secator/configs/profiles/stealth.yaml +0 -7
  54. {secator-0.12.0.dist-info → secator-0.14.0.dist-info}/WHEEL +0 -0
  55. {secator-0.12.0.dist-info → secator-0.14.0.dist-info}/entry_points.txt +0 -0
  56. {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
- pass
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
- pass
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
- pass
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
- tools = [cls for cls in ALL_TASKS if cls.__name__ in cmds]
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
- cmd_str = ' '.join(cmds)
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['status'] == 'outdated' or not info['latest_version']:
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
- def task(name, verbose):
1379
- """Test task."""
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
- check_error(version_info['installed'], 'Check task is installed', 'Failed to install command. Fix your installation command.', warnings) # noqa: E501
1395
- check_error(any(cmd for cmd in [task.install_cmd, task.install_github_handle]), 'Check task installation command is defined', 'Task has no installation command. Please define a `install_cmd` or `install_github_handle` class attribute.', warnings) # noqa: E501
1396
- check_error(version_info['version'], 'Check task version can be fetched', 'Failed to detect version info. Fix your `version_flag` class attribute.', warnings) # noqa: E501
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
- console.print(f'\n[bold gold3]:wrench: Running task-specific tests for {name} ...[/]') if verbose else None
1400
- check_error(task.__doc__, 'Check task description is set (cls.__doc__)', 'Task has no description (class docstring).', warnings) # noqa: E501
1401
- check_error(task.cmd, 'Check task command is set (cls.cmd)', 'Task has no cmd attribute.', warnings)
1402
- check_error(task.input_type, 'Check task input type is set (cls.input_type)', 'Task has no input_type attribute.', warnings) # noqa: E501
1403
- check_error(task.output_types, 'Check task output types is set (cls.output_types)', 'Task has no output_types attribute.', warnings) # noqa: E501
1404
-
1405
- # Print all warnings
1406
- exit_code = 1 if len(warnings) > 0 else 0
1407
- if exit_code == 1:
1408
- console.print()
1409
- console.print("[bold red]Issues:[/]")
1410
- for warning in warnings:
1411
- console.print(warning)
1412
- console.print()
1413
- console.print(Info(message=f'Skipping unit and integration tests for {name} due to previous errors.'))
1414
- console.print(Error(message=f'Task {name} tests failed. Please fix the issues above before making a PR.'))
1415
- sys.exit(exit_code)
1416
-
1417
- # Run unit tests
1418
- console.print(f'\n[bold gold3]:wrench: Running unit tests for {name} ...[/]') if verbose else None
1419
- cmd = f'secator test unit --tasks {name}'
1420
- ret_code = run_test(cmd, exit=False, verbose=verbose)
1421
- check_error(ret_code == 0, 'Check unit tests pass', 'Unit tests failed.', warnings)
1422
-
1423
- # Run integration tests
1424
- console.print(f'\n[bold gold3]:wrench: Running integration tests for {name} ...[/]') if verbose else None
1425
- cmd = f'secator test integration --tasks {name}'
1426
- ret_code = run_test(cmd, exit=False, verbose=verbose)
1427
- check_error(ret_code == 0, 'Check integration tests pass', 'Integration tests failed.', warnings)
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(warnings) > 0 else 0
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 ! You are free to make a PR.', style='bold green')
1551
+ console.print(f':tada: Task {name} tests passed !', style='bold green')
1433
1552
  else:
1434
- console.print(Error(message=f'Task {name} tests failed. Please fix the issues above before making a PR.'))
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 check_error(condition, message, error, warnings=[]):
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
- warning = Warning(message=error)
1443
- warnings.append(warning)
1444
- console.print(' [bold red]FAILED[/]', style='dim')
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
@@ -1,7 +1,8 @@
1
1
  type: profile
2
2
  name: aggressive
3
- options:
4
- rate_limit: 100000
5
- delay: 0
6
- proxy: random
7
- user_agent: random
3
+ description: "Internal networks or time-sensitive scans"
4
+ opts:
5
+ rate_limit: 10000
6
+ delay: 0
7
+ timeout: 1
8
+ retries: 1
@@ -1,9 +1,8 @@
1
1
  type: profile
2
2
  name: default
3
- options:
4
- rate_limit: 1000
5
- delay: 1
6
- proxy: null
7
- user_agent: 'Mozilla ...'
8
- nuclei.retries: 5
9
- nuclei.timeout: 15
3
+ description: "General scanning"
4
+ opts:
5
+ rate_limit: 1000
6
+ delay: 0
7
+ timeout: 5
8
+ retries: 3
@@ -0,0 +1,8 @@
1
+ type: profile
2
+ name: insane
3
+ description: "Local LAN scanning or stress scanning"
4
+ opts:
5
+ rate_limit: 100000
6
+ delay: 0
7
+ timeout: 1
8
+ retries: 0
@@ -0,0 +1,8 @@
1
+ type: profile
2
+ name: paranoid
3
+ description: "Maximum stealth"
4
+ opts:
5
+ rate_limit: 5
6
+ delay: 5
7
+ timeout: 15
8
+ retries: 5
@@ -0,0 +1,8 @@
1
+ type: profile
2
+ name: polite
3
+ description: "Avoid overloading network"
4
+ opts:
5
+ rate_limit: 100
6
+ delay: 0
7
+ timeout: 10
8
+ retries: 5
@@ -0,0 +1,8 @@
1
+ type: profile
2
+ name: sneaky
3
+ description: "IDS/IPS evasion, sensitive networks"
4
+ opts:
5
+ rate_limit: 10
6
+ delay: 2
7
+ timeout: 15
8
+ retries: 5
@@ -0,0 +1,5 @@
1
+ type: profile
2
+ name: tor
3
+ description: "Anonymous scan using Tor network"
4
+ opts:
5
+ proxy: tor
@@ -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
- skip_host_discovery: True
13
- ports: "-" # scan all ports
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
@@ -14,6 +14,11 @@ tasks:
14
14
  field: '{name}/FUZZ'
15
15
  cariddi:
16
16
  description: Crawl HTTP directories for content
17
+ info: True
18
+ secrets: True
19
+ errors: True
20
+ juicy_extensions: 1
21
+ juicy_endpoints: True
17
22
  targets_:
18
23
  - target.name
19
24
  - url.url
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
- latest_release = cls.get_latest_release(github_handle)
227
- if not latest_release:
228
- return InstallerStatus.GITHUB_LATEST_RELEASE_NOT_FOUND
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(latest_release['assets'], os_identifiers, arch_identifiers)
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.GITHUB_RELEASE_NOT_FOUND
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 get_latest_release(cls, github_handle):
243
- """Get latest release from GitHub.
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: Latest release JSON from GitHub releases.
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
- url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest"
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.get_latest_release(github_handle)
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"