secator 0.1.0__py2.py3-none-any.whl → 0.2.0__py2.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 CHANGED
@@ -12,10 +12,12 @@ from rich.rule import Rule
12
12
 
13
13
  from secator.config import ConfigLoader
14
14
  from secator.decorators import OrderedGroup, register_runner
15
- from secator.definitions import (ASCII, CVES_FOLDER, DATA_FOLDER,
15
+ from secator.definitions import (ASCII, CVES_FOLDER, DATA_FOLDER, # noqa: F401
16
+ BUILD_ADDON_ENABLED, DEV_ADDON_ENABLED, DEV_PACKAGE,
17
+ GOOGLE_ADDON_ENABLED, LIB_FOLDER, MONGODB_ADDON_ENABLED,
16
18
  OPT_NOT_SUPPORTED, PAYLOADS_FOLDER,
17
- ROOT_FOLDER, SCRIPTS_FOLDER, VERSION,
18
- WORKER_ADDON_ENABLED, DEV_ADDON_ENABLED, DEV_PACKAGE)
19
+ REDIS_ADDON_ENABLED, REVSHELLS_FOLDER, ROOT_FOLDER,
20
+ TRACE_ADDON_ENABLED, VERSION, WORKER_ADDON_ENABLED)
19
21
  from secator.rich import console
20
22
  from secator.runners import Command
21
23
  from secator.serializers.dataclass import loads_dataclass
@@ -28,10 +30,6 @@ ALL_TASKS = discover_tasks()
28
30
  ALL_CONFIGS = ConfigLoader.load_all()
29
31
  ALL_WORKFLOWS = ALL_CONFIGS.workflow
30
32
  ALL_SCANS = ALL_CONFIGS.scan
31
- DEFAULT_CMD_OPTS = {
32
- 'no_capture': True,
33
- 'print_cmd': True,
34
- }
35
33
 
36
34
 
37
35
  #-----#
@@ -48,7 +46,11 @@ def cli(ctx, no_banner, version):
48
46
  print(ASCII, file=sys.stderr)
49
47
  if ctx.invoked_subcommand is None:
50
48
  if version:
51
- print(f'Current Version: v{VERSION}')
49
+ console.print(f'[bold gold3]Current version[/]: v{VERSION}', highlight=False)
50
+ console.print(f'[bold gold3]Python binary[/]: {sys.executable}')
51
+ if DEV_PACKAGE:
52
+ console.print(f'[bold gold3]Root folder[/]: {ROOT_FOLDER}')
53
+ console.print(f'[bold gold3]Lib folder[/]: {LIB_FOLDER}')
52
54
  else:
53
55
  ctx.get_help()
54
56
 
@@ -142,11 +144,7 @@ def worker(hostname, concurrency, reload, queue, pool, check, dev, stop, show):
142
144
  if reload:
143
145
  patterns = "celery.py;tasks/*.py;runners/*.py;serializers/*.py;output_types/*.py;hooks/*.py;exporters/*.py"
144
146
  cmd = f'watchmedo auto-restart --directory=./ --patterns="{patterns}" --recursive -- {cmd}'
145
- Command.run_command(
146
- cmd,
147
- name='secator worker',
148
- **DEFAULT_CMD_OPTS
149
- )
147
+ Command.execute(cmd, name='secator worker')
150
148
 
151
149
 
152
150
  #--------#
@@ -211,11 +209,7 @@ def which(command):
211
209
  Returns:
212
210
  secator.Command: Command instance.
213
211
  """
214
- return Command.run_command(
215
- f'which {command}',
216
- quiet=True,
217
- print_errors=False
218
- )
212
+ return Command.execute(f'which {command}', quiet=True, print_errors=False)
219
213
 
220
214
 
221
215
  def version(cls):
@@ -245,11 +239,7 @@ def get_version(version_cmd):
245
239
  str: Version string.
246
240
  """
247
241
  regex = r'[0-9]+\.[0-9]+\.?[0-9]*\.?[a-zA-Z]*'
248
- ret = Command.run_command(
249
- version_cmd,
250
- quiet=True,
251
- print_errors=False
252
- )
242
+ ret = Command.execute(version_cmd, quiet=True, print_errors=False)
253
243
  match = re.findall(regex, ret.output)
254
244
  if not match:
255
245
  return 'n/a'
@@ -265,7 +255,7 @@ def health(json, debug):
265
255
  status = {'tools': {}, 'languages': {}, 'secator': {}}
266
256
 
267
257
  def print_status(cmd, return_code, version=None, bin=None, category=None):
268
- s = '[bold green]ok [/]' if return_code == 0 else '[bold red]failed [/]'
258
+ s = '[bold green]ok [/]' if return_code == 0 else '[bold red]missing [/]'
269
259
  s = f'[bold magenta]{cmd:<15}[/] {s} '
270
260
  if return_code == 0 and version:
271
261
  if version == 'N/A':
@@ -287,12 +277,12 @@ def health(json, debug):
287
277
 
288
278
  # Check languages
289
279
  console.print('\n:wrench: [bold gold3]Checking installed languages ...[/]')
290
- version_cmds = {'go': 'version', 'python3': '--version', 'ruby': '--version', 'rustc': '--version'}
280
+ version_cmds = {'go': 'version', 'python3': '--version', 'ruby': '--version'}
291
281
  for lang, version_flag in version_cmds.items():
292
282
  ret = which(lang)
293
283
  ret2 = get_version(f'{lang} {version_flag}')
294
284
  if not json:
295
- print_status(lang, ret.return_code, ret2, ret.output, 'lang')
285
+ print_status(lang, ret.return_code, ret2, ret.output, 'langs')
296
286
  status['languages'][lang] = {'installed': ret.return_code == 0}
297
287
 
298
288
  # Check tools
@@ -305,6 +295,14 @@ def health(json, debug):
305
295
  print_status(tool.__name__, ret.return_code, ret2, ret.output, 'tools')
306
296
  status['tools'][tool.__name__] = {'installed': ret.return_code == 0}
307
297
 
298
+ # Check addons
299
+ console.print('\n:wrench: [bold gold3]Checking installed addons ...[/]')
300
+ for addon in ['google', 'mongodb', 'redis', 'dev', 'trace', 'build']:
301
+ addon_var = globals()[f'{addon.upper()}_ADDON_ENABLED']
302
+ ret = 0 if addon_var == 1 else 1
303
+ bin = None if addon_var == 0 else ' '
304
+ print_status(addon, ret, 'N/A', bin, 'addons')
305
+
308
306
  # Print JSON health
309
307
  if json:
310
308
  console.print(status)
@@ -316,12 +314,7 @@ def health(json, debug):
316
314
 
317
315
  def run_install(cmd, title, next_steps=None):
318
316
  with console.status(f'[bold yellow] Installing {title}...'):
319
- ret = Command.run_command(
320
- cmd,
321
- cls_attributes={'shell': True},
322
- print_cmd=True,
323
- print_line=True
324
- )
317
+ ret = Command.execute(cmd, cls_attributes={'shell': True}, print_cmd=True, print_line=True)
325
318
  if ret.return_code != 0:
326
319
  console.print(f':exclamation_mark: Failed to install {title}.', style='bold red')
327
320
  else:
@@ -349,7 +342,7 @@ def addons():
349
342
  def install_worker():
350
343
  "Install worker addon."
351
344
  run_install(
352
- cmd=f'{sys.executable} -m pip install secator[worker]',
345
+ cmd=f'{sys.executable} -m pip install .[worker]',
353
346
  title='worker addon',
354
347
  next_steps=[
355
348
  'Run "secator worker" to run a Celery worker using the file system as a backend and broker.',
@@ -363,7 +356,7 @@ def install_worker():
363
356
  def install_google():
364
357
  "Install google addon."
365
358
  run_install(
366
- cmd=f'{sys.executable} -m pip install secator[google]',
359
+ cmd=f'{sys.executable} -m pip install .[google]',
367
360
  title='google addon',
368
361
  next_steps=[
369
362
  'Set the "GOOGLE_CREDENTIALS_PATH" and "GOOGLE_DRIVE_PARENT_FOLDER_ID" environment variables.',
@@ -376,7 +369,7 @@ def install_google():
376
369
  def install_mongodb():
377
370
  "Install mongodb addon."
378
371
  run_install(
379
- cmd=f'{sys.executable} -m pip install secator[mongodb]',
372
+ cmd=f'{sys.executable} -m pip install .[mongodb]',
380
373
  title='mongodb addon',
381
374
  next_steps=[
382
375
  '[dim]\[optional][/] Run "docker run --name mongo -p 27017:27017 -d mongo:latest" to run a local MongoDB instance.',
@@ -390,7 +383,7 @@ def install_mongodb():
390
383
  def install_redis():
391
384
  "Install redis addon."
392
385
  run_install(
393
- cmd=f'{sys.executable} -m pip install secator[redis]',
386
+ cmd=f'{sys.executable} -m pip install .[redis]',
394
387
  title='redis addon',
395
388
  next_steps=[
396
389
  '[dim]\[optional][/] Run "docker run --name redis -p 6379:6379 -d redis" to run a local Redis instance.',
@@ -416,6 +409,34 @@ def install_dev():
416
409
  )
417
410
 
418
411
 
412
+ @addons.command('trace')
413
+ def install_trace():
414
+ "Install trace addon."
415
+ run_install(
416
+ cmd=f'{sys.executable} -m pip install secator[trace]',
417
+ title='dev addon',
418
+ next_steps=[
419
+ 'Run "secator test lint" to run lint tests.',
420
+ 'Run "secator test unit" to run unit tests.',
421
+ 'Run "secator test integration" to run integration tests.',
422
+ ]
423
+ )
424
+
425
+
426
+ @addons.command('build')
427
+ def install_build():
428
+ "Install build addon."
429
+ run_install(
430
+ cmd=f'{sys.executable} -m pip install secator[build]',
431
+ title='build addon',
432
+ next_steps=[
433
+ 'Run "secator test lint" to run lint tests.',
434
+ 'Run "secator test unit" to run unit tests.',
435
+ 'Run "secator test integration" to run integration tests.',
436
+ ]
437
+ )
438
+
439
+
419
440
  @install.group()
420
441
  def langs():
421
442
  "Install languages."
@@ -427,7 +448,10 @@ def install_go():
427
448
  """Install Go."""
428
449
  run_install(
429
450
  cmd='wget -O - https://raw.githubusercontent.com/freelabz/secator/main/scripts/install_go.sh | sudo sh',
430
- title='Go'
451
+ title='Go',
452
+ next_steps=[
453
+ 'Add ~/go/bin to your $PATH'
454
+ ]
431
455
  )
432
456
 
433
457
 
@@ -459,21 +483,13 @@ def install_tools(cmds):
459
483
  @install.command('cves')
460
484
  @click.option('--force', is_flag=True)
461
485
  def install_cves(force):
462
- """Install CVEs to file system for passive vulnerability search."""
486
+ """Install CVEs (enables passive vulnerability search)."""
463
487
  cve_json_path = f'{CVES_FOLDER}/circl-cve-search-expanded.json'
464
488
  if not os.path.exists(cve_json_path) or force:
465
489
  with console.status('[bold yellow]Downloading zipped CVEs from cve.circl.lu ...[/]'):
466
- Command.run_command(
467
- 'wget https://cve.circl.lu/static/circl-cve-search-expanded.json.gz',
468
- cwd=CVES_FOLDER,
469
- **DEFAULT_CMD_OPTS
470
- )
490
+ Command.execute('wget https://cve.circl.lu/static/circl-cve-search-expanded.json.gz', cwd=CVES_FOLDER)
471
491
  with console.status('[bold yellow]Unzipping CVEs ...[/]'):
472
- Command.run_command(
473
- f'gunzip {CVES_FOLDER}/circl-cve-search-expanded.json.gz',
474
- cwd=CVES_FOLDER,
475
- **DEFAULT_CMD_OPTS
476
- )
492
+ Command.execute(f'gunzip {CVES_FOLDER}/circl-cve-search-expanded.json.gz', cwd=CVES_FOLDER)
477
493
  with console.status(f'[bold yellow]Installing CVEs to {CVES_FOLDER} ...[/]'):
478
494
  with open(cve_json_path, 'r') as f:
479
495
  for line in f:
@@ -591,8 +607,8 @@ def utils():
591
607
  @utils.command()
592
608
  @click.option('--timeout', type=float, default=0.2, help='Proxy timeout (in seconds)')
593
609
  @click.option('--number', '-n', type=int, default=1, help='Number of proxies')
594
- def get_proxy(timeout, number):
595
- """Get a random proxy."""
610
+ def proxy(timeout, number):
611
+ """Get random proxies from FreeProxy."""
596
612
  proxy = FreeProxy(timeout=timeout, rand=True, anonym=True)
597
613
  for _ in range(number):
598
614
  url = proxy.get()
@@ -605,8 +621,9 @@ def get_proxy(timeout, number):
605
621
  @click.option('--port', '-p', type=int, default=9001, show_default=True, help='Specify PORT for revshell')
606
622
  @click.option('--interface', '-i', type=str, help='Interface to use to detect IP')
607
623
  @click.option('--listen', '-l', is_flag=True, default=False, help='Spawn netcat listener on specified port')
608
- def revshells(name, host, port, interface, listen):
609
- """Show reverse shell source codes and run netcat listener."""
624
+ @click.option('--force', is_flag=True)
625
+ def revshell(name, host, port, interface, listen, force):
626
+ """Show reverse shell source codes and run netcat listener (-l)."""
610
627
  if host is None: # detect host automatically
611
628
  host = detect_host(interface)
612
629
  if not host:
@@ -615,7 +632,18 @@ def revshells(name, host, port, interface, listen):
615
632
  style='bold red')
616
633
  return
617
634
 
618
- with open(f'{SCRIPTS_FOLDER}/revshells.json') as f:
635
+ # Download reverse shells JSON from repo
636
+ revshells_json = f'{REVSHELLS_FOLDER}/revshells.json'
637
+ if not os.path.exists(revshells_json) or force:
638
+ ret = Command.execute(
639
+ f'wget https://raw.githubusercontent.com/freelabz/secator/main/scripts/revshells.json && mv revshells.json {REVSHELLS_FOLDER}', # noqa: E501
640
+ cls_attributes={'shell': True}
641
+ )
642
+ if not ret.return_code == 0:
643
+ sys.exit(1)
644
+
645
+ # Parse JSON into shells
646
+ with open(revshells_json) as f:
619
647
  shells = json.loads(f.read())
620
648
  for sh in shells:
621
649
  sh['alias'] = '_'.join(sh['name'].lower()
@@ -663,10 +691,7 @@ def revshells(name, host, port, interface, listen):
663
691
  if listen:
664
692
  console.print(f'Starting netcat listener on port {port} ...', style='bold gold3')
665
693
  cmd = f'nc -lvnp {port}'
666
- Command.run_command(
667
- cmd,
668
- **DEFAULT_CMD_OPTS
669
- )
694
+ Command.execute(cmd)
670
695
 
671
696
 
672
697
  @utils.command()
@@ -675,10 +700,10 @@ def revshells(name, host, port, interface, listen):
675
700
  @click.option('--port', '-p', type=int, default=9001, help='HTTP server port')
676
701
  @click.option('--interface', '-i', type=str, default=None, help='Interface to use to auto-detect host IP')
677
702
  def serve(directory, host, port, interface):
678
- """Serve payloads in HTTP server."""
703
+ """Run HTTP server to serve payloads."""
679
704
  LSE_URL = 'https://github.com/diego-treitos/linux-smart-enumeration/releases/latest/download/lse.sh'
680
705
  LINPEAS_URL = 'https://github.com/carlospolop/PEASS-ng/releases/latest/download/linpeas.sh'
681
- SUDOKILLER_URL = 'https://raw.githubusercontent.com/TH3xACE/SUDO_KILLER/master/SUDO_KILLERv2.4.2.sh'
706
+ SUDOKILLER_URL = 'https://raw.githubusercontent.com/TH3xACE/SUDO_KILLER/V3/SUDO_KILLERv3.sh'
682
707
  PAYLOADS = [
683
708
  {
684
709
  'fname': 'lse.sh',
@@ -703,20 +728,11 @@ def serve(directory, host, port, interface):
703
728
  with console.status(f'[bold yellow][{ix}/{len(PAYLOADS)}] Downloading {fname} [dim]({descr})[/] ...[/]'):
704
729
  cmd = payload['command']
705
730
  console.print(f'[bold magenta]{fname} [dim]({descr})[/] ...[/]', )
706
- opts = DEFAULT_CMD_OPTS.copy()
707
- opts['no_capture'] = False
708
- Command.run_command(
709
- cmd,
710
- cls_attributes={'shell': True},
711
- cwd=directory,
712
- **opts
713
- )
731
+ Command.execute(cmd, cls_attributes={'shell': True}, cwd=directory)
714
732
  console.print()
715
733
 
716
734
  console.print(Rule())
717
735
  console.print(f'Available payloads in {directory}: ', style='bold yellow')
718
- opts = DEFAULT_CMD_OPTS.copy()
719
- opts['print_cmd'] = False
720
736
  for fname in os.listdir(directory):
721
737
  if not host:
722
738
  host = detect_host(interface)
@@ -731,12 +747,8 @@ def serve(directory, host, port, interface):
731
747
  console.print(f'wget http://{host}:{port}/{fname}', style='dim italic')
732
748
  console.print('')
733
749
  console.print(Rule())
734
- console.print('Starting HTTP server ...', style='bold yellow')
735
- Command.run_command(
736
- f'{sys.executable} -m http.server {port}',
737
- cwd=directory,
738
- **DEFAULT_CMD_OPTS
739
- )
750
+ console.print(f'Started HTTP server on port {port}, waiting for incoming connections ...', style='bold yellow')
751
+ Command.execute(f'{sys.executable} -m http.server {port}', cwd=directory)
740
752
 
741
753
 
742
754
  @utils.command()
@@ -772,18 +784,15 @@ def record(record_name, script, interactive, width, height, output_dir):
772
784
  console.print(f'Removed existing {output_cast_path}', style='bold green')
773
785
 
774
786
  with console.status('[bold gold3]Recording with asciinema ...[/]'):
775
- Command.run_command(
787
+ Command.execute(
776
788
  f'asciinema-automation -aa "-c /bin/sh" {script} {output_cast_path} --timeout 200',
777
789
  cls_attributes=attrs,
778
790
  raw=True,
779
- **DEFAULT_CMD_OPTS,
780
791
  )
781
792
  console.print(f'Generated {output_cast_path}', style='bold green')
782
793
  elif interactive:
783
794
  os.environ.update(attrs['env'])
784
- Command.run_command(
785
- f'asciinema rec -c /bin/bash --stdin --overwrite {output_cast_path}',
786
- )
795
+ Command.execute(f'asciinema rec -c /bin/bash --stdin --overwrite {output_cast_path}')
787
796
 
788
797
  # Resize cast file
789
798
  if os.path.exists(output_cast_path):
@@ -810,11 +819,10 @@ def record(record_name, script, interactive, width, height, output_dir):
810
819
 
811
820
  # Edit cast file to reduce long timeouts
812
821
  with console.status('[bold gold3] Editing cast file to reduce long commands ...'):
813
- Command.run_command(
822
+ Command.execute(
814
823
  f'asciinema-edit quantize --range 1 {output_cast_path} --out {output_cast_path}.tmp',
815
824
  cls_attributes=attrs,
816
825
  raw=True,
817
- **DEFAULT_CMD_OPTS,
818
826
  )
819
827
  if os.path.exists(f'{output_cast_path}.tmp'):
820
828
  os.replace(f'{output_cast_path}.tmp', output_cast_path)
@@ -822,14 +830,78 @@ def record(record_name, script, interactive, width, height, output_dir):
822
830
 
823
831
  # Convert to GIF
824
832
  with console.status(f'[bold gold3]Converting to {output_gif_path} ...[/]'):
825
- Command.run_command(
833
+ Command.execute(
826
834
  f'agg {output_cast_path} {output_gif_path}',
827
835
  cls_attributes=attrs,
828
- **DEFAULT_CMD_OPTS,
829
836
  )
830
837
  console.print(f'Generated {output_gif_path}', style='bold green')
831
838
 
832
839
 
840
+ @utils.group('build')
841
+ def build():
842
+ """Build secator."""
843
+ pass
844
+
845
+
846
+ @build.command('pypi')
847
+ def build_pypi():
848
+ """Build secator PyPI package."""
849
+ if not DEV_PACKAGE:
850
+ console.print('[bold red]You MUST use a development version of secator to make builds.[/]')
851
+ sys.exit(1)
852
+ if not BUILD_ADDON_ENABLED:
853
+ console.print('[bold red]Missing build addon: please run `secator install addons build`')
854
+ sys.exit(1)
855
+ with console.status('[bold gold3]Building PyPI package...[/]'):
856
+ ret = Command.execute(f'{sys.executable} -m hatch build', name='hatch build', cwd=ROOT_FOLDER)
857
+ sys.exit(ret.return_code)
858
+
859
+
860
+ @build.command('docker')
861
+ @click.option('--dev', '-dev', is_flag=True, default=False, help='Build dev version')
862
+ def build_docker(dev):
863
+ """Build secator Docker image."""
864
+ version = 'dev' if dev else VERSION
865
+ with console.status('[bold gold3]Building Docker image...[/]'):
866
+ ret = Command.execute(f'docker build -t freelabz/secator:{version} .', name='docker build', cwd=ROOT_FOLDER)
867
+ sys.exit(ret.return_code)
868
+
869
+
870
+ @utils.group('publish')
871
+ def publish():
872
+ """Publish secator."""
873
+ pass
874
+
875
+
876
+ @publish.command('pypi')
877
+ def publish_pypi():
878
+ """Publish secator PyPI package."""
879
+ if not DEV_PACKAGE:
880
+ console.print('[bold red]You MUST use a development version of secator to make builds.[/]')
881
+ sys.exit(1)
882
+ if not BUILD_ADDON_ENABLED:
883
+ console.print('[bold red]Missing build addon: please run `secator install addons build`')
884
+ sys.exit(1)
885
+ os.environ['HATCH_INDEX_USER'] = '__token__'
886
+ hatch_token = os.environ.get('HATCH_INDEX_AUTH')
887
+ if not hatch_token:
888
+ console.print('[bold red]Missing PyPI auth token (HATCH_INDEX_AUTH env variable).')
889
+ sys.exit(1)
890
+ with console.status('[bold gold3]Publishing PyPI package...[/]'):
891
+ ret = Command.execute(f'{sys.executable} -m hatch publish', name='hatch publish', cwd=ROOT_FOLDER)
892
+ sys.exit(ret.return_code)
893
+
894
+
895
+ @publish.command('docker')
896
+ @click.option('--dev', '-dev', is_flag=True, default=False, help='Build dev version')
897
+ def publish_docker(dev):
898
+ """Publish secator Docker image."""
899
+ version = 'dev' if dev else VERSION
900
+ with console.status('[bold gold3]Publishing PyPI package...[/]'):
901
+ ret = Command.execute(f'docker push freelabz/secator:{version}', name='docker push', cwd=ROOT_FOLDER)
902
+ sys.exit(ret.return_code)
903
+
904
+
833
905
  #------#
834
906
  # TEST #
835
907
  #------#
@@ -854,12 +926,7 @@ def run_test(cmd, name):
854
926
  cmd: Command to run.
855
927
  name: Name of the test.
856
928
  """
857
- result = Command.run_command(
858
- cmd,
859
- name=name + ' tests',
860
- cwd=ROOT_FOLDER,
861
- **DEFAULT_CMD_OPTS
862
- )
929
+ result = Command.execute(cmd, name=name + ' tests', cwd=ROOT_FOLDER)
863
930
  if result.return_code == 0:
864
931
  console.print(f':tada: {name.capitalize()} tests passed !', style='bold green')
865
932
  sys.exit(result.return_code)
secator/definitions.py CHANGED
@@ -10,13 +10,13 @@ load_dotenv(find_dotenv(usecwd=True), override=False)
10
10
  # Globals
11
11
  VERSION = get_distribution('secator').version
12
12
  ASCII = f"""
13
- __
13
+ __
14
14
  ________ _________ _/ /_____ _____
15
15
  / ___/ _ \/ ___/ __ `/ __/ __ \/ ___/
16
16
  (__ / __/ /__/ /_/ / /_/ /_/ / /
17
17
  /____/\___/\___/\__,_/\__/\____/_/ v{VERSION}
18
18
 
19
- freelabz.com
19
+ freelabz.com
20
20
  """ # noqa: W605,W291
21
21
 
22
22
  # Secator folders
@@ -157,27 +157,55 @@ WORDS = 'words'
157
157
 
158
158
  # Check worker addon
159
159
  try:
160
- import eventlet # noqa: F401
161
- WORKER_ADDON_ENABLED = 1
160
+ import eventlet # noqa: F401
161
+ WORKER_ADDON_ENABLED = 1
162
162
  except ModuleNotFoundError:
163
- WORKER_ADDON_ENABLED = 0
163
+ WORKER_ADDON_ENABLED = 0
164
+
165
+ # Check google addon
166
+ try:
167
+ import gspread # noqa: F401
168
+ GOOGLE_ADDON_ENABLED = 1
169
+ except ModuleNotFoundError:
170
+ GOOGLE_ADDON_ENABLED = 0
164
171
 
165
172
  # Check mongodb addon
166
173
  try:
167
- import pymongo # noqa: F401
168
- MONGODB_ADDON_ENABLED = 1
174
+ import pymongo # noqa: F401
175
+ MONGODB_ADDON_ENABLED = 1
176
+ except ModuleNotFoundError:
177
+ MONGODB_ADDON_ENABLED = 0
178
+
179
+ # Check redis addon
180
+ try:
181
+ import redis # noqa: F401
182
+ REDIS_ADDON_ENABLED = 1
169
183
  except ModuleNotFoundError:
170
- MONGODB_ADDON_ENABLED = 0
184
+ REDIS_ADDON_ENABLED = 0
171
185
 
172
186
  # Check dev addon
173
187
  try:
174
- import flake8 # noqa: F401
175
- DEV_ADDON_ENABLED = 1
188
+ import flake8 # noqa: F401
189
+ DEV_ADDON_ENABLED = 1
190
+ except ModuleNotFoundError:
191
+ DEV_ADDON_ENABLED = 0
192
+
193
+ # Check build addon
194
+ try:
195
+ import hatch # noqa: F401
196
+ BUILD_ADDON_ENABLED = 1
197
+ except ModuleNotFoundError:
198
+ BUILD_ADDON_ENABLED = 0
199
+
200
+ # Check trace addon
201
+ try:
202
+ import memray # noqa: F401
203
+ TRACE_ADDON_ENABLED = 1
176
204
  except ModuleNotFoundError:
177
- DEV_ADDON_ENABLED = 0
205
+ TRACE_ADDON_ENABLED = 0
178
206
 
179
207
  # Check dev package
180
208
  if not os.path.exists(TESTS_FOLDER):
181
- DEV_PACKAGE = 0
209
+ DEV_PACKAGE = 0
182
210
  else:
183
- DEV_PACKAGE = 1
211
+ DEV_PACKAGE = 1
@@ -134,6 +134,9 @@ class Command(Runner):
134
134
  # No capturing of stdout / stderr.
135
135
  self.no_capture = self.run_opts.get('no_capture', False)
136
136
 
137
+ # No processing of output lines.
138
+ self.no_process = self.run_opts.get('no_process', False)
139
+
137
140
  # Proxy config (global)
138
141
  self.proxy = self.run_opts.pop('proxy', False)
139
142
  self.configure_proxy()
@@ -155,7 +158,7 @@ class Command(Runner):
155
158
  if self.print_cmd and not self.has_children:
156
159
  if self.sync and self.description:
157
160
  self._print(f'\n:wrench: {self.description} ...', color='bold gold3', rich=True)
158
- self._print(self.cmd, color='bold cyan', rich=True)
161
+ self._print(self.cmd.replace('[', '\\['), color='bold cyan', rich=True)
159
162
 
160
163
  # Print built input
161
164
  if self.print_input_file and self.input_path:
@@ -256,13 +259,7 @@ class Command(Runner):
256
259
  if not cls.install_cmd:
257
260
  console.print(f'{cls.__name__} install is not supported yet. Please install it manually.', style='bold red')
258
261
  return
259
- ret = cls.run_command(
260
- cls.install_cmd,
261
- name=cls.__name__,
262
- print_cmd=True,
263
- print_line=True,
264
- cls_attributes={'shell': True}
265
- )
262
+ ret = cls.execute(cls.install_cmd, name=cls.__name__, cls_attributes={'shell': True})
266
263
  if ret.return_code != 0:
267
264
  console.print(f':exclamation_mark: Failed to install {cls.__name__}.', style='bold red')
268
265
  else:
@@ -270,16 +267,30 @@ class Command(Runner):
270
267
  return ret
271
268
 
272
269
  @classmethod
273
- def run_command(cls, cmd, name=None, cls_attributes={}, **kwargs):
274
- """Run adhoc command. Can be used without defining an inherited class to run a command, while still enjoying
275
- all the good stuff in this class.
270
+ def execute(cls, cmd, name=None, cls_attributes={}, **kwargs):
271
+ """Execute an ad-hoc command.
272
+
273
+ Can be used without defining an inherited class to run a command, while still enjoying all the good stuff in
274
+ this class.
275
+
276
+ Args:
277
+ cls (object): Class.
278
+ cmd (str): Command.
279
+ name (str): Printed name.
280
+ cls_attributes (dict): Class attributes.
281
+ kwargs (dict): Options.
282
+
283
+ Returns:
284
+ secator.runners.Command: instance of the Command.
276
285
  """
277
286
  name = name or cmd.split(' ')[0]
287
+ kwargs['no_process'] = True
288
+ kwargs['print_cmd'] = not kwargs.get('quiet', False)
289
+ kwargs['print_item'] = not kwargs.get('quiet', False)
290
+ kwargs['print_line'] = not kwargs.get('quiet', False)
278
291
  cmd_instance = type(name, (Command,), {'cmd': cmd})(**kwargs)
279
292
  for k, v in cls_attributes.items():
280
293
  setattr(cmd_instance, k, v)
281
- cmd_instance.print_line = not kwargs.get('quiet', False)
282
- cmd_instance.print_item = not kwargs.get('quiet', False)
283
294
  cmd_instance.run()
284
295
  return cmd_instance
285
296
 
@@ -400,6 +411,9 @@ class Command(Runner):
400
411
 
401
412
  # Strip line endings
402
413
  line = line.rstrip()
414
+ if self.no_process:
415
+ yield line
416
+ continue
403
417
 
404
418
  # Some commands output ANSI text, so we need to remove those ANSI chars
405
419
  if self.encoding == 'ansi':
secator/tasks/ffuf.py CHANGED
@@ -7,7 +7,7 @@ from secator.definitions import (AUTO_CALIBRATION, CONTENT_LENGTH,
7
7
  MATCH_WORDS, METHOD, OPT_NOT_SUPPORTED,
8
8
  PERCENT, PROXY, RATE_LIMIT, RETRIES,
9
9
  STATUS_CODE, THREADS, TIME, TIMEOUT,
10
- USER_AGENT, WORDLIST)
10
+ USER_AGENT, WORDLIST, WORDLISTS_FOLDER)
11
11
  from secator.output_types import Progress, Url
12
12
  from secator.serializers import JSONSerializer, RegexSerializer
13
13
  from secator.tasks._categories import HttpFuzzer
@@ -70,10 +70,7 @@ class ffuf(HttpFuzzer):
70
70
  },
71
71
  }
72
72
  encoding = 'ansi'
73
- install_cmd = (
74
- 'go install -v github.com/ffuf/ffuf@latest && '
75
- 'sudo git clone https://github.com/danielmiessler/SecLists /usr/share/seclists || true'
76
- )
73
+ install_cmd = f'go install -v github.com/ffuf/ffuf@latest && sudo git clone https://github.com/danielmiessler/SecLists {WORDLISTS_FOLDER}/seclists || true' # noqa: E501
77
74
  proxychains = False
78
75
  proxy_socks5 = True
79
76
  proxy_http = True
secator/tasks/katana.py CHANGED
@@ -70,7 +70,7 @@ class katana(HttpCrawler):
70
70
  }
71
71
  }
72
72
  item_loaders = []
73
- install_cmd = 'go install -v github.com/projectdiscovery/katana/cmd/katana@latest'
73
+ install_cmd = 'sudo apt install build-essential && go install -v github.com/projectdiscovery/katana/cmd/katana@latest'
74
74
  proxychains = False
75
75
  proxy_socks5 = True
76
76
  proxy_http = True
@@ -135,7 +135,7 @@ class msfconsole(VulnMulti):
135
135
  # self.client = MsfRpcClient(pw, ssl=True, **run_opts)
136
136
  #
137
137
  # # def start_msgrpc(self):
138
- # # code, out = run_command(f'msfrpcd -P {self.password}')
138
+ # # code, out = Command.execute(f'msfrpcd -P {self.password}')
139
139
  # # logger.info(out)
140
140
  #
141
141
  # def get_lhost(self):
secator/tasks/naabu.py CHANGED
@@ -45,7 +45,7 @@ class naabu(ReconPort):
45
45
  }
46
46
  }
47
47
  output_types = [Port]
48
- install_cmd = 'sudo apt install -y libpcap-dev && go install -v github.com/projectdiscovery/naabu/v2/cmd/naabu@latest'
48
+ install_cmd = 'sudo apt install -y build-essential libpcap-dev && go install -v github.com/projectdiscovery/naabu/v2/cmd/naabu@latest' # noqa: E501
49
49
  proxychains = False
50
50
  proxy_socks5 = True
51
51
  proxy_http = False
secator/tasks/wpscan.py CHANGED
@@ -66,7 +66,7 @@ class wpscan(VulnHttp):
66
66
  },
67
67
  }
68
68
  output_types = [Vulnerability, Tag]
69
- install_cmd = 'sudo gem install wpscan'
69
+ install_cmd = 'sudo apt install build-essential && sudo gem install wpscan'
70
70
  proxychains = False
71
71
  proxy_http = True
72
72
  proxy_socks5 = False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: secator
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: The pentester's swiss knife.
5
5
  Project-URL: Homepage, https://github.com/freelabz/secator
6
6
  Project-URL: Issues, https://github.com/freelabz/secator/issues
@@ -34,6 +34,8 @@ Requires-Dist: rich-click<1.7
34
34
  Requires-Dist: rich<14
35
35
  Requires-Dist: validators<1
36
36
  Requires-Dist: xmltodict<1
37
+ Provides-Extra: build
38
+ Requires-Dist: hatch<2; extra == 'build'
37
39
  Provides-Extra: dev
38
40
  Requires-Dist: asciinema-automation<1; extra == 'dev'
39
41
  Requires-Dist: coverage<8; extra == 'dev'
@@ -319,6 +321,26 @@ secator install addons trace
319
321
 
320
322
  </details>
321
323
 
324
+ <details>
325
+ <summary>build</summary>
326
+
327
+ Add `hatch` for building and publishing the PyPI package.
328
+
329
+ ```sh
330
+ secator install addons build
331
+ ```
332
+
333
+ </details>
334
+
335
+
336
+ ### Install CVEs
337
+
338
+ `secator` makes remote API calls to https://cve.circl.lu/ to get in-depth information about the CVEs it encounters.
339
+ We provide a subcommand to download all known CVEs locally so that future lookups are made from disk instead:
340
+ ```sh
341
+ secator install cves
342
+ ```
343
+
322
344
  ### Checking installation health
323
345
 
324
346
  To figure out which languages or tools are installed on your system (along with their version):
@@ -1,10 +1,10 @@
1
1
  secator/.gitignore,sha256=da8MUc3hdb6Mo0WjZu2upn5uZMbXcBGvhdhTQ1L89HI,3093
2
2
  secator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  secator/celery.py,sha256=zXjg7EKneWjErBTNrJCHOXCJzs-P5jBi5gYqrSqjW4k,12227
4
- secator/cli.py,sha256=x06YzjuNTvpOQACjKIpTRo3DSHm2CONiXXB74OTPOJY,28170
4
+ secator/cli.py,sha256=T-Sv82THpBjzHu5xOfMLNCL7AyGMYgzqp3K1Ot9fb0w,31798
5
5
  secator/config.py,sha256=iOeRzq7u1rvR1-Oq5v9wGxQYB613X0xKGLIcrfhEGc4,3693
6
6
  secator/decorators.py,sha256=IRH4CSesOJXKrzpSJ8xM2ZMUAFTk3GcqRI0SYIpIpag,10492
7
- secator/definitions.py,sha256=EBI_HCf7E8O2UroXicOXrzcOt-Tp25-xiVQJVAW1duw,6044
7
+ secator/definitions.py,sha256=hORMT71MLn8buGdSj7PC23sDQTgAuBachPo8OVqlU5Q,6507
8
8
  secator/report.py,sha256=g0stVCcx9klbUS01uKvWcxNE9MJfNFMexYA2SoDIWJU,2596
9
9
  secator/rich.py,sha256=7-uKJrQWiCKM0gPNIr_cr1c9KrcJSVd2ht-DgLXhBro,3392
10
10
  secator/utils.py,sha256=oJEEls4Z8SfTxiG6keCfqLMMWA97ftS5aiABhBPRduU,9964
@@ -58,7 +58,7 @@ secator/output_types/vulnerability.py,sha256=p0DTbr5w7Vv5D3dgbdnvsG5qXzqVVk4YPOP
58
58
  secator/runners/__init__.py,sha256=EBbOk37vkBy9p8Hhrbi-2VtM_rTwQ3b-0ggTyiD22cE,290
59
59
  secator/runners/_base.py,sha256=Or9bDSsxcwYTUeW6G7-Pmag82_yGUtREuzSZWj9IgHY,27268
60
60
  secator/runners/_helpers.py,sha256=7UUboSsr4b6srIOOHtSSYhJ9Jxq_qaMVbbF2gVEBnR4,3703
61
- secator/runners/command.py,sha256=NDLcwbOcOu1JpKQbrRAK91phIj-JiTrdbcgXyBjokdA,18982
61
+ secator/runners/command.py,sha256=eKrQY34m-EpEDqo9Rqr_9k2dp1xIg7JVjVt34c55aKs,19402
62
62
  secator/runners/scan.py,sha256=FjmlL_zkraqhS3rBwy5jHnGsKt2n7Hb2gi4qhgeGenw,1727
63
63
  secator/runners/task.py,sha256=JS8JPCW6v3_f_jbFV1-robR53epPPDj0PdxqAtXKmEs,2775
64
64
  secator/runners/workflow.py,sha256=Mz8Q4OT48B-o-iQHgZ84WpZfwaECQOp8KRdIirg3He0,3766
@@ -74,7 +74,7 @@ secator/tasks/dirsearch.py,sha256=2hJeJZJwaAl3-UAjBwlmjW1w9bxjVWxxwfcaTTxqClc,23
74
74
  secator/tasks/dnsx.py,sha256=6v2ttbycLLt6p-1B05P5662QNdFgS-ozrKjzN3w8hSk,1722
75
75
  secator/tasks/dnsxbrute.py,sha256=_wjanOvxKsxZzuSPGiBOsd7TRrbshQgyEEZUCP0tVN4,1172
76
76
  secator/tasks/feroxbuster.py,sha256=400i6Egj9jn_Ap_zRfca2RG8c1P30CZBAYLC8UzyW-g,2965
77
- secator/tasks/ffuf.py,sha256=y6vDlxs5fN7rPQjS71wlu4FBwijjTIVbsbRwh3ldOnY,2492
77
+ secator/tasks/ffuf.py,sha256=1qICKOdvcFfiqtPS2dk6TfIIpc77STjnAvZADksKKZY,2521
78
78
  secator/tasks/fping.py,sha256=P2EAPUGgwEC4Geh2zUbBPKF9bdqrlrdDg-R_TYLTFng,1127
79
79
  secator/tasks/gau.py,sha256=YB89dsUVwLaRplIpEiiUA7mwTM7s3vyH4Cs6ZjzcAnY,1357
80
80
  secator/tasks/gf.py,sha256=WlhoEyL6xE79w6nE5XNSXHs-jVeO10njqJxBF8w20sA,945
@@ -82,18 +82,18 @@ secator/tasks/gospider.py,sha256=-zIttWmabtt5qWkxCFSeCKmC2swUhv038j3rbFReXSE,212
82
82
  secator/tasks/grype.py,sha256=Q8VJbLt6YLYUqlsbR1OxzGDAqEVaDS_nNQ0klOm53O0,2372
83
83
  secator/tasks/h8mail.py,sha256=hZBpfV6M1mbpD_PbDHxLI5HMvqAvTeY_W0lbkq3Hugo,2037
84
84
  secator/tasks/httpx.py,sha256=MMjg705z49YooEqglZ4J1UqAKQffDPWMV6kv3fjWZS0,3929
85
- secator/tasks/katana.py,sha256=Vr9YUcooVyIpkaGeC-O4meSqQHnebNze7jy662llR_E,4283
85
+ secator/tasks/katana.py,sha256=zOoPjJLTRdBqvTLJ4fU_VWstqpRUc7YTFoLuDz3qJ3I,4319
86
86
  secator/tasks/maigret.py,sha256=PZDTICJ4LZF3joKe-dXu2alffakD_1sxBuNEUBtJDm4,2098
87
87
  secator/tasks/mapcidr.py,sha256=O6zssQMMrg3JGXIhldgOD28WNATAb_wfj0svHr0DRxg,928
88
- secator/tasks/msfconsole.py,sha256=pCHY9UMU2VXYNza06Nxw7lZpDUMk5nMU-C3P7Z4Nz04,6069
89
- secator/tasks/naabu.py,sha256=FgrlIuTX-p4FqXNzck2XGXRjFjPH97w04y5M2JkYo_0,1514
88
+ secator/tasks/msfconsole.py,sha256=VlhEzsdYMHb6eJy4HBRdXMtRKhdzf5KtQGh7qZqO9Rs,6073
89
+ secator/tasks/naabu.py,sha256=6XfERGS5ywDBMho9Bp7v7W_Ke2BQSFqJuawEOuMWwEk,1544
90
90
  secator/tasks/nmap.py,sha256=LS5FBo-vFxbHVK4DxF5x-O2cAvAK3zL1pROT1GddX9E,9459
91
91
  secator/tasks/nuclei.py,sha256=iOBKsEY9kkytp_cenYx6061kcEUCjqyT_c3PwoYkHPY,3292
92
92
  secator/tasks/searchsploit.py,sha256=RD2uv3GFI3Eb-DiTzJp59jyXnvAZRACq-WjDI1NgFM0,1664
93
93
  secator/tasks/subfinder.py,sha256=_T7erWmfriqLeN5kquO3-L9DlR0mEjYPPC7NMzwTqwg,1033
94
- secator/tasks/wpscan.py,sha256=9SVUM5Bwsm52GvanJPygzKPkOp10b-x7_vGtTV9ZqH4,5377
95
- secator-0.1.0.dist-info/METADATA,sha256=5qae-jdIDw61K0YpOUFQZYpnlqcDCDpx8OL_a7b7S7o,13065
96
- secator-0.1.0.dist-info/WHEEL,sha256=wpsUbWzR9la66_V7_eWTdyvs6WD26tazKT2BBEAC-EM,105
97
- secator-0.1.0.dist-info/entry_points.txt,sha256=lPgsqqUXWgiuGSfKy-se5gHdQlAXIwS_A46NYq7Acic,44
98
- secator-0.1.0.dist-info/licenses/LICENSE,sha256=19W5Jsy4WTctNkqmZIqLRV1gTDOp01S3LDj9iSgWaJ0,2867
99
- secator-0.1.0.dist-info/RECORD,,
94
+ secator/tasks/wpscan.py,sha256=OgFCWEPOjOVdFreBXZDLBRc-PrFmTUv97UaXmaAS9yc,5413
95
+ secator-0.2.0.dist-info/METADATA,sha256=m1I9y0Z8qy8ySO5_WHvtQWApNsFgf_tMG5TvpKRd8ds,13553
96
+ secator-0.2.0.dist-info/WHEEL,sha256=wpsUbWzR9la66_V7_eWTdyvs6WD26tazKT2BBEAC-EM,105
97
+ secator-0.2.0.dist-info/entry_points.txt,sha256=lPgsqqUXWgiuGSfKy-se5gHdQlAXIwS_A46NYq7Acic,44
98
+ secator-0.2.0.dist-info/licenses/LICENSE,sha256=19W5Jsy4WTctNkqmZIqLRV1gTDOp01S3LDj9iSgWaJ0,2867
99
+ secator-0.2.0.dist-info/RECORD,,