secator 0.4.1__tar.gz → 0.5.0__tar.gz

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 (168) hide show
  1. {secator-0.4.1 → secator-0.5.0}/CHANGELOG.md +15 -0
  2. {secator-0.4.1 → secator-0.5.0}/PKG-INFO +1 -1
  3. {secator-0.4.1 → secator-0.5.0}/pyproject.toml +1 -1
  4. {secator-0.4.1 → secator-0.5.0}/secator/cli.py +65 -9
  5. {secator-0.4.1 → secator-0.5.0}/secator/output_types/exploit.py +3 -0
  6. {secator-0.4.1 → secator-0.5.0}/secator/output_types/vulnerability.py +0 -6
  7. {secator-0.4.1 → secator-0.5.0}/secator/report.py +1 -4
  8. {secator-0.4.1 → secator-0.5.0}/secator/runners/_base.py +14 -10
  9. secator-0.5.0/secator/tasks/__init__.py +8 -0
  10. {secator-0.4.1 → secator-0.5.0}/secator/tasks/nmap.py +6 -1
  11. {secator-0.4.1 → secator-0.5.0}/secator/tasks/searchsploit.py +42 -4
  12. {secator-0.4.1 → secator-0.5.0}/secator/utils.py +29 -13
  13. {secator-0.4.1 → secator-0.5.0}/secator/utils_test.py +13 -0
  14. secator-0.5.0/tests/fixtures/ls.py +36 -0
  15. {secator-0.4.1 → secator-0.5.0}/tests/unit/test_offline.py +3 -8
  16. secator-0.5.0/tests/unit/test_template.py +53 -0
  17. secator-0.4.1/secator/tasks/__init__.py +0 -10
  18. {secator-0.4.1 → secator-0.5.0}/.flake8 +0 -0
  19. {secator-0.4.1 → secator-0.5.0}/.gitignore +0 -0
  20. {secator-0.4.1 → secator-0.5.0}/CONTRIBUTING.md +0 -0
  21. {secator-0.4.1 → secator-0.5.0}/Dockerfile +0 -0
  22. {secator-0.4.1 → secator-0.5.0}/LICENSE +0 -0
  23. {secator-0.4.1 → secator-0.5.0}/README.md +0 -0
  24. {secator-0.4.1 → secator-0.5.0}/SECURITY.md +0 -0
  25. {secator-0.4.1 → secator-0.5.0}/cloudbuild.yaml +0 -0
  26. {secator-0.4.1 → secator-0.5.0}/images/aliases.cast +0 -0
  27. {secator-0.4.1 → secator-0.5.0}/images/aliases.gif +0 -0
  28. {secator-0.4.1 → secator-0.5.0}/images/demo.gif +0 -0
  29. {secator-0.4.1 → secator-0.5.0}/images/demo.tap +0 -0
  30. {secator-0.4.1 → secator-0.5.0}/images/fmt.cast +0 -0
  31. {secator-0.4.1 → secator-0.5.0}/images/fmt.gif +0 -0
  32. {secator-0.4.1 → secator-0.5.0}/images/help.png +0 -0
  33. {secator-0.4.1 → secator-0.5.0}/images/input.cast +0 -0
  34. {secator-0.4.1 → secator-0.5.0}/images/input.gif +0 -0
  35. {secator-0.4.1 → secator-0.5.0}/images/pipe.cast +0 -0
  36. {secator-0.4.1 → secator-0.5.0}/images/pipe.gif +0 -0
  37. {secator-0.4.1 → secator-0.5.0}/images/short_demo.cast +0 -0
  38. {secator-0.4.1 → secator-0.5.0}/images/short_demo.gif +0 -0
  39. {secator-0.4.1 → secator-0.5.0}/scripts/download_cves.sh +0 -0
  40. {secator-0.4.1 → secator-0.5.0}/scripts/install.sh +0 -0
  41. {secator-0.4.1 → secator-0.5.0}/scripts/install_asciinema.sh +0 -0
  42. {secator-0.4.1 → secator-0.5.0}/scripts/install_go.sh +0 -0
  43. {secator-0.4.1 → secator-0.5.0}/scripts/install_ruby.sh +0 -0
  44. {secator-0.4.1 → secator-0.5.0}/scripts/msf/exploit_cve.rc +0 -0
  45. {secator-0.4.1 → secator-0.5.0}/scripts/msf/ftp_anonymous.rc +0 -0
  46. {secator-0.4.1 → secator-0.5.0}/scripts/msf/ftp_version.rc +0 -0
  47. {secator-0.4.1 → secator-0.5.0}/scripts/msf/ftp_vsftpd_234_backdoor.rc +0 -0
  48. {secator-0.4.1 → secator-0.5.0}/scripts/msf/redis.rc +0 -0
  49. {secator-0.4.1 → secator-0.5.0}/scripts/msfinstall.sh +0 -0
  50. {secator-0.4.1 → secator-0.5.0}/scripts/stories/STORY.md +0 -0
  51. {secator-0.4.1 → secator-0.5.0}/scripts/stories/aliases.sh +0 -0
  52. {secator-0.4.1 → secator-0.5.0}/scripts/stories/demo.sh +0 -0
  53. {secator-0.4.1 → secator-0.5.0}/scripts/stories/fmt.sh +0 -0
  54. {secator-0.4.1 → secator-0.5.0}/scripts/stories/input.sh +0 -0
  55. {secator-0.4.1 → secator-0.5.0}/scripts/stories/pipe.sh +0 -0
  56. {secator-0.4.1 → secator-0.5.0}/scripts/stories/short_demo.sh +0 -0
  57. {secator-0.4.1 → secator-0.5.0}/secator/.gitignore +0 -0
  58. {secator-0.4.1 → secator-0.5.0}/secator/__init__.py +0 -0
  59. {secator-0.4.1 → secator-0.5.0}/secator/celery.py +0 -0
  60. {secator-0.4.1 → secator-0.5.0}/secator/config.py +0 -0
  61. {secator-0.4.1 → secator-0.5.0}/secator/configs/__init__.py +0 -0
  62. {secator-0.4.1 → secator-0.5.0}/secator/configs/profiles/__init__.py +0 -0
  63. {secator-0.4.1 → secator-0.5.0}/secator/configs/profiles/aggressive.yaml +0 -0
  64. {secator-0.4.1 → secator-0.5.0}/secator/configs/profiles/default.yaml +0 -0
  65. {secator-0.4.1 → secator-0.5.0}/secator/configs/profiles/stealth.yaml +0 -0
  66. {secator-0.4.1 → secator-0.5.0}/secator/configs/scans/__init__.py +0 -0
  67. {secator-0.4.1 → secator-0.5.0}/secator/configs/scans/domain.yaml +0 -0
  68. {secator-0.4.1 → secator-0.5.0}/secator/configs/scans/host.yaml +0 -0
  69. {secator-0.4.1 → secator-0.5.0}/secator/configs/scans/network.yaml +0 -0
  70. {secator-0.4.1 → secator-0.5.0}/secator/configs/scans/subdomain.yaml +0 -0
  71. {secator-0.4.1 → secator-0.5.0}/secator/configs/scans/url.yaml +0 -0
  72. {secator-0.4.1 → secator-0.5.0}/secator/configs/workflows/__init__.py +0 -0
  73. {secator-0.4.1 → secator-0.5.0}/secator/configs/workflows/cidr_recon.yaml +0 -0
  74. {secator-0.4.1 → secator-0.5.0}/secator/configs/workflows/code_scan.yaml +0 -0
  75. {secator-0.4.1 → secator-0.5.0}/secator/configs/workflows/host_recon.yaml +0 -0
  76. {secator-0.4.1 → secator-0.5.0}/secator/configs/workflows/port_scan.yaml +0 -0
  77. {secator-0.4.1 → secator-0.5.0}/secator/configs/workflows/subdomain_recon.yaml +0 -0
  78. {secator-0.4.1 → secator-0.5.0}/secator/configs/workflows/url_crawl.yaml +0 -0
  79. {secator-0.4.1 → secator-0.5.0}/secator/configs/workflows/url_dirsearch.yaml +0 -0
  80. {secator-0.4.1 → secator-0.5.0}/secator/configs/workflows/url_fuzz.yaml +0 -0
  81. {secator-0.4.1 → secator-0.5.0}/secator/configs/workflows/url_nuclei.yaml +0 -0
  82. {secator-0.4.1 → secator-0.5.0}/secator/configs/workflows/url_vuln.yaml +0 -0
  83. {secator-0.4.1 → secator-0.5.0}/secator/configs/workflows/user_hunt.yaml +0 -0
  84. {secator-0.4.1 → secator-0.5.0}/secator/configs/workflows/wordpress.yaml +0 -0
  85. {secator-0.4.1 → secator-0.5.0}/secator/decorators.py +0 -0
  86. {secator-0.4.1 → secator-0.5.0}/secator/definitions.py +0 -0
  87. {secator-0.4.1 → secator-0.5.0}/secator/exporters/__init__.py +0 -0
  88. {secator-0.4.1 → secator-0.5.0}/secator/exporters/_base.py +0 -0
  89. {secator-0.4.1 → secator-0.5.0}/secator/exporters/csv.py +0 -0
  90. {secator-0.4.1 → secator-0.5.0}/secator/exporters/gdrive.py +0 -0
  91. {secator-0.4.1 → secator-0.5.0}/secator/exporters/json.py +0 -0
  92. {secator-0.4.1 → secator-0.5.0}/secator/exporters/table.py +0 -0
  93. {secator-0.4.1 → secator-0.5.0}/secator/exporters/txt.py +0 -0
  94. {secator-0.4.1 → secator-0.5.0}/secator/hooks/__init__.py +0 -0
  95. {secator-0.4.1 → secator-0.5.0}/secator/hooks/mongodb.py +0 -0
  96. {secator-0.4.1 → secator-0.5.0}/secator/installer.py +0 -0
  97. {secator-0.4.1 → secator-0.5.0}/secator/output_types/__init__.py +0 -0
  98. {secator-0.4.1 → secator-0.5.0}/secator/output_types/_base.py +0 -0
  99. {secator-0.4.1 → secator-0.5.0}/secator/output_types/ip.py +0 -0
  100. {secator-0.4.1 → secator-0.5.0}/secator/output_types/port.py +0 -0
  101. {secator-0.4.1 → secator-0.5.0}/secator/output_types/progress.py +0 -0
  102. {secator-0.4.1 → secator-0.5.0}/secator/output_types/record.py +0 -0
  103. {secator-0.4.1 → secator-0.5.0}/secator/output_types/subdomain.py +0 -0
  104. {secator-0.4.1 → secator-0.5.0}/secator/output_types/tag.py +0 -0
  105. {secator-0.4.1 → secator-0.5.0}/secator/output_types/target.py +0 -0
  106. {secator-0.4.1 → secator-0.5.0}/secator/output_types/url.py +0 -0
  107. {secator-0.4.1 → secator-0.5.0}/secator/output_types/user_account.py +0 -0
  108. {secator-0.4.1 → secator-0.5.0}/secator/rich.py +0 -0
  109. {secator-0.4.1 → secator-0.5.0}/secator/runners/__init__.py +0 -0
  110. {secator-0.4.1 → secator-0.5.0}/secator/runners/_helpers.py +0 -0
  111. {secator-0.4.1 → secator-0.5.0}/secator/runners/command.py +0 -0
  112. {secator-0.4.1 → secator-0.5.0}/secator/runners/scan.py +0 -0
  113. {secator-0.4.1 → secator-0.5.0}/secator/runners/task.py +0 -0
  114. {secator-0.4.1 → secator-0.5.0}/secator/runners/workflow.py +0 -0
  115. {secator-0.4.1 → secator-0.5.0}/secator/serializers/__init__.py +0 -0
  116. {secator-0.4.1 → secator-0.5.0}/secator/serializers/dataclass.py +0 -0
  117. {secator-0.4.1 → secator-0.5.0}/secator/serializers/json.py +0 -0
  118. {secator-0.4.1 → secator-0.5.0}/secator/serializers/regex.py +0 -0
  119. {secator-0.4.1 → secator-0.5.0}/secator/tasks/_categories.py +0 -0
  120. {secator-0.4.1 → secator-0.5.0}/secator/tasks/cariddi.py +0 -0
  121. {secator-0.4.1 → secator-0.5.0}/secator/tasks/dalfox.py +0 -0
  122. {secator-0.4.1 → secator-0.5.0}/secator/tasks/dirsearch.py +0 -0
  123. {secator-0.4.1 → secator-0.5.0}/secator/tasks/dnsx.py +0 -0
  124. {secator-0.4.1 → secator-0.5.0}/secator/tasks/dnsxbrute.py +0 -0
  125. {secator-0.4.1 → secator-0.5.0}/secator/tasks/feroxbuster.py +0 -0
  126. {secator-0.4.1 → secator-0.5.0}/secator/tasks/ffuf.py +0 -0
  127. {secator-0.4.1 → secator-0.5.0}/secator/tasks/fping.py +0 -0
  128. {secator-0.4.1 → secator-0.5.0}/secator/tasks/gau.py +0 -0
  129. {secator-0.4.1 → secator-0.5.0}/secator/tasks/gf.py +0 -0
  130. {secator-0.4.1 → secator-0.5.0}/secator/tasks/gospider.py +0 -0
  131. {secator-0.4.1 → secator-0.5.0}/secator/tasks/grype.py +0 -0
  132. {secator-0.4.1 → secator-0.5.0}/secator/tasks/h8mail.py +0 -0
  133. {secator-0.4.1 → secator-0.5.0}/secator/tasks/httpx.py +0 -0
  134. {secator-0.4.1 → secator-0.5.0}/secator/tasks/katana.py +0 -0
  135. {secator-0.4.1 → secator-0.5.0}/secator/tasks/maigret.py +0 -0
  136. {secator-0.4.1 → secator-0.5.0}/secator/tasks/mapcidr.py +0 -0
  137. {secator-0.4.1 → secator-0.5.0}/secator/tasks/msfconsole.py +0 -0
  138. {secator-0.4.1 → secator-0.5.0}/secator/tasks/naabu.py +0 -0
  139. {secator-0.4.1 → secator-0.5.0}/secator/tasks/nuclei.py +0 -0
  140. {secator-0.4.1 → secator-0.5.0}/secator/tasks/subfinder.py +0 -0
  141. {secator-0.4.1 → secator-0.5.0}/secator/tasks/wpscan.py +0 -0
  142. {secator-0.4.1 → secator-0.5.0}/secator/template.py +0 -0
  143. {secator-0.4.1 → secator-0.5.0}/tests/__init__.py +0 -0
  144. {secator-0.4.1 → secator-0.5.0}/tests/fixtures/h8mail_breach.txt +0 -0
  145. {secator-0.4.1 → secator-0.5.0}/tests/fixtures/msfconsole_input.rc +0 -0
  146. {secator-0.4.1 → secator-0.5.0}/tests/fixtures/nmap_output.xml +0 -0
  147. {secator-0.4.1 → secator-0.5.0}/tests/integration/__init__.py +0 -0
  148. {secator-0.4.1 → secator-0.5.0}/tests/integration/inputs.py +0 -0
  149. {secator-0.4.1 → secator-0.5.0}/tests/integration/outputs.py +0 -0
  150. {secator-0.4.1 → secator-0.5.0}/tests/integration/setup.sh +0 -0
  151. {secator-0.4.1 → secator-0.5.0}/tests/integration/teardown.sh +0 -0
  152. {secator-0.4.1 → secator-0.5.0}/tests/integration/test_scans.py +0 -0
  153. {secator-0.4.1 → secator-0.5.0}/tests/integration/test_tasks.py +0 -0
  154. {secator-0.4.1 → secator-0.5.0}/tests/integration/test_worker.py +0 -0
  155. {secator-0.4.1 → secator-0.5.0}/tests/integration/test_workflows.py +0 -0
  156. {secator-0.4.1 → secator-0.5.0}/tests/integration/wordlist.txt +0 -0
  157. {secator-0.4.1 → secator-0.5.0}/tests/integration/wordlist_dns.txt +0 -0
  158. {secator-0.4.1 → secator-0.5.0}/tests/integration/wordpress_toolbox/Dockerfile +0 -0
  159. {secator-0.4.1 → secator-0.5.0}/tests/integration/wordpress_toolbox/Makefile +0 -0
  160. {secator-0.4.1 → secator-0.5.0}/tests/performance/__init__.py +0 -0
  161. {secator-0.4.1 → secator-0.5.0}/tests/performance/loadtester.py +0 -0
  162. {secator-0.4.1 → secator-0.5.0}/tests/unit/__init__.py +0 -0
  163. {secator-0.4.1 → secator-0.5.0}/tests/unit/test_celery.py +0 -0
  164. {secator-0.4.1 → secator-0.5.0}/tests/unit/test_config.py +0 -0
  165. {secator-0.4.1 → secator-0.5.0}/tests/unit/test_scans.py +0 -0
  166. {secator-0.4.1 → secator-0.5.0}/tests/unit/test_serializers.py +0 -0
  167. {secator-0.4.1 → secator-0.5.0}/tests/unit/test_tasks.py +0 -0
  168. {secator-0.4.1 → secator-0.5.0}/tests/unit/test_workflows.py +0 -0
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.0](https://github.com/freelabz/secator/compare/v0.4.1...v0.5.0) (2024-05-03)
4
+
5
+
6
+ ### Features
7
+
8
+ * add searchsploit output fields ([#278](https://github.com/freelabz/secator/issues/278)) ([00872c4](https://github.com/freelabz/secator/commit/00872c4a7f9b1ec76ee1bfd7a00919d53cbdb30a))
9
+ * **cli:** add report list / export commands ([#367](https://github.com/freelabz/secator/issues/367)) ([ab396a3](https://github.com/freelabz/secator/commit/ab396a3098c6d4c46cf9c9b29bd5c54579421646))
10
+ * **config:** load external tasks from template dir ([#373](https://github.com/freelabz/secator/issues/373)) ([0c63c02](https://github.com/freelabz/secator/commit/0c63c02c8eca477a6752f4af466c4303801019de))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **cli:** catch JSON parse errors ([#378](https://github.com/freelabz/secator/issues/378)) ([5e3d7f2](https://github.com/freelabz/secator/commit/5e3d7f2d2938a857e7599a429a6cfabf3b12347b))
16
+ * **nmap:** resolve -sS tcp syn stealth issue ([#376](https://github.com/freelabz/secator/issues/376)) ([a3efc65](https://github.com/freelabz/secator/commit/a3efc651dfa4d8fa34d611b9aea2e156352fdc45))
17
+
3
18
  ## [0.4.1](https://github.com/freelabz/secator/compare/v0.4.0...v0.4.1) (2024-04-30)
4
19
 
5
20
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: secator
3
- Version: 0.4.1
3
+ Version: 0.5.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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "secator"
7
- version = "0.4.1"
7
+ version = "0.5.0"
8
8
  authors = [{ name = "FreeLabz", email = "sales@freelabz.com" }]
9
9
  readme = "README.md"
10
10
  description = "The pentester's swiss knife."
@@ -20,7 +20,8 @@ from secator.decorators import OrderedGroup, register_runner
20
20
  from secator.definitions import ADDONS_ENABLED, ASCII, DEV_PACKAGE, OPT_NOT_SUPPORTED, VERSION
21
21
  from secator.installer import ToolInstaller, fmt_health_table_row, get_health_table, get_version_info
22
22
  from secator.rich import console
23
- from secator.runners import Command
23
+ from secator.runners import Command, Runner
24
+ from secator.report import Report
24
25
  from secator.serializers.dataclass import loads_dataclass
25
26
  from secator.utils import debug, detect_host, discover_tasks, flatten, print_results_table, print_version
26
27
 
@@ -536,17 +537,72 @@ def report():
536
537
 
537
538
  @report.command('show')
538
539
  @click.argument('json_path')
540
+ @click.option('-o', '--output', type=str, default='console', help='Format')
539
541
  @click.option('-e', '--exclude-fields', type=str, default='', help='List of fields to exclude (comma-separated)')
540
- def report_show(json_path, exclude_fields):
541
- """Show a JSON report as a nicely-formatted table."""
542
+ def report_show(json_path, output, exclude_fields):
543
+ """Show a JSON report."""
542
544
  with open(json_path, 'r') as f:
543
545
  report = loads_dataclass(f.read())
544
546
  results = flatten(list(report['results'].values()))
545
- exclude_fields = exclude_fields.split(',')
546
- print_results_table(
547
- results,
548
- title=report['info']['title'],
549
- exclude_fields=exclude_fields)
547
+ if output == 'console':
548
+ for result in results:
549
+ console.print(result)
550
+ elif output == 'table':
551
+ exclude_fields = exclude_fields.split(',')
552
+ print_results_table(
553
+ results,
554
+ title=report['info']['title'],
555
+ exclude_fields=exclude_fields)
556
+
557
+
558
+ @report.command('list')
559
+ @click.option('-ws', '--workspace', type=str)
560
+ def report_list(workspace):
561
+ reports_dir = CONFIG.dirs.reports
562
+ json_reports = reports_dir.glob("**/**/report.json")
563
+ ws_reports = {}
564
+ for path in json_reports:
565
+ ws, runner, number = str(path).split('/')[-4:-1]
566
+ if ws not in ws_reports:
567
+ ws_reports[ws] = []
568
+ with open(path, 'r') as f:
569
+ try:
570
+ content = json.loads(f.read())
571
+ data = {'path': path, 'name': content['info']['name'], 'runner': runner}
572
+ ws_reports[ws].append(data)
573
+ except json.JSONDecodeError as e:
574
+ console.print(f'[bold red]Could not load {path}: {str(e)}')
575
+
576
+ for ws in ws_reports:
577
+ if workspace and not ws == workspace:
578
+ continue
579
+ console.print(f'[bold gold3]{ws}:')
580
+ for data in sorted(ws_reports[ws], key=lambda x: x['path']):
581
+ console.print(f' • {data["path"]} ([bold blue]{data["name"]}[/] [dim]{data["runner"][:-1]}[/])')
582
+
583
+
584
+ @report.command('export')
585
+ @click.argument('json_path', type=str)
586
+ @click.option('--output-folder', '-of', type=str)
587
+ @click.option('-output', '-o', type=str)
588
+ def report_export(json_path, output_folder, output):
589
+ with open(json_path, 'r') as f:
590
+ data = loads_dataclass(f.read())
591
+ flatten(list(data['results'].values()))
592
+
593
+ runner_instance = DotMap({
594
+ "config": {
595
+ "name": data['info']['name']
596
+ },
597
+ "workspace_name": json_path.split('/')[-4],
598
+ "reports_folder": output_folder or Path.cwd(),
599
+ "data": data,
600
+ "results": flatten(list(data['results'].values()))
601
+ })
602
+ exporters = Runner.resolve_exporters(output)
603
+ report = Report(runner_instance, title=data['info']['title'], exporters=exporters)
604
+ report.data = data
605
+ report.send()
550
606
 
551
607
 
552
608
  #--------#
@@ -1052,5 +1108,5 @@ def integration(tasks, workflows, scans, test, debug):
1052
1108
  @test.command()
1053
1109
  def coverage():
1054
1110
  """Run coverage report."""
1055
- cmd = f'{sys.executable} -m coverage report -m --omit=*/site-packages/*,*/tests/*'
1111
+ cmd = f'{sys.executable} -m coverage report -m --omit=*/site-packages/*,*/tests/*,*/templates/*'
1056
1112
  run_test(cmd, 'coverage')
@@ -34,6 +34,9 @@ class Exploit(OutputType):
34
34
  ]
35
35
  _sort_by = ('matched_at', 'name')
36
36
 
37
+ def __str__(self):
38
+ return self.name
39
+
37
40
  def __repr__(self):
38
41
  s = f'[bold red]⍼[/] \[[bold red]{self.name}'
39
42
  if self.reference:
@@ -89,11 +89,5 @@ class Vulnerability(OutputType):
89
89
  s = f'[dim]{s}[/]'
90
90
  return rich_to_ansi(s)
91
91
 
92
- # def __gt__(self, other):
93
- # # favor httpx over other url info tools
94
- # if self._source == 'httpx' and other._source != 'httpx':
95
- # return True
96
- # return super().__gt__(other)
97
-
98
92
  def __str__(self):
99
93
  return self.matched_at + ' -> ' + self.name
@@ -1,7 +1,7 @@
1
1
  import operator
2
2
 
3
3
  from secator.output_types import OUTPUT_TYPES, OutputType
4
- from secator.utils import merge_opts, get_file_timestamp, print_results_table
4
+ from secator.utils import merge_opts, get_file_timestamp
5
5
  from secator.rich import console
6
6
 
7
7
 
@@ -22,9 +22,6 @@ class Report:
22
22
  self.workspace_name = runner.workspace_name
23
23
  self.output_folder = runner.reports_folder
24
24
 
25
- def as_table(self):
26
- print_results_table(self.results, self.title)
27
-
28
25
  def send(self):
29
26
  for report_cls in self.exporters:
30
27
  try:
@@ -92,7 +92,6 @@ class Runner:
92
92
  self.workspace_name = context.get('workspace_name', 'default')
93
93
  self.run_opts = run_opts.copy()
94
94
  self.sync = run_opts.get('sync', True)
95
- self.exporters = self.resolve_exporters()
96
95
  self.done = False
97
96
  self.start_time = datetime.fromtimestamp(time())
98
97
  self.last_updated = None
@@ -109,6 +108,10 @@ class Runner:
109
108
  self.uuids = []
110
109
  self.celery_result = None
111
110
 
111
+ # Determine exporters
112
+ exporters_str = self.run_opts.get('output') or self.default_exporters
113
+ self.exporters = Runner.resolve_exporters(exporters_str)
114
+
112
115
  # Determine report folder
113
116
  default_reports_folder_base = f'{CONFIG.dirs.reports}/{self.workspace_name}/{self.config.type}s'
114
117
  _id = get_task_folder_id(default_reports_folder_base)
@@ -294,7 +297,7 @@ class Runner:
294
297
  debug('running duplicate check', id=self.config.name, sub='runner.mark_duplicates')
295
298
  dupe_count = 0
296
299
  for item in self.results:
297
- debug('running duplicate check', obj=item.toDict(), obj_breaklines=True, sub='runner.mark_duplicates', level=5)
300
+ # debug('running duplicate check', obj=item.toDict(), obj_breaklines=True, sub='runner.mark_duplicates', level=5)
298
301
  others = [f for f in self.results if f == item and f._uuid != item._uuid]
299
302
  if others:
300
303
  main = max(item, *others)
@@ -303,6 +306,7 @@ class Runner:
303
306
  main._related.extend([dupe._uuid for dupe in dupes])
304
307
  main._related = list(dict.fromkeys(main._related))
305
308
  if main._uuid != item._uuid:
309
+ debug(f'found {len(others)} duplicates for', obj=item.toDict(), obj_breaklines=True, sub='runner.mark_duplicates', level=5) # noqa: E501
306
310
  item._duplicate = True
307
311
  item = self.run_hooks('on_item', item)
308
312
  if item._uuid not in main._related:
@@ -390,19 +394,19 @@ class Runner:
390
394
  return False
391
395
  return True
392
396
 
393
- def resolve_exporters(self):
397
+ @staticmethod
398
+ def resolve_exporters(exporters):
394
399
  """Resolve exporters from output options."""
395
- output = self.run_opts.get('output') or self.default_exporters
396
- if not output or output in ['false', 'False']:
400
+ if not exporters or exporters in ['false', 'False']:
397
401
  return []
398
- if isinstance(output, str):
399
- output = output.split(',')
400
- exporters = [
402
+ if isinstance(exporters, str):
403
+ exporters = exporters.split(',')
404
+ classes = [
401
405
  import_dynamic(f'secator.exporters.{o.capitalize()}Exporter', 'Exporter')
402
- for o in output
406
+ for o in exporters
403
407
  if o
404
408
  ]
405
- return [e for e in exporters if e]
409
+ return [cls for cls in classes if cls]
406
410
 
407
411
  def log_start(self):
408
412
  """Log runner start."""
@@ -0,0 +1,8 @@
1
+ from secator.utils import discover_tasks
2
+ TASKS = discover_tasks()
3
+ __all__ = [
4
+ cls.__name__
5
+ for cls in TASKS
6
+ ]
7
+ for cls in TASKS:
8
+ exec(f'from .{cls.__name__} import {cls.__name__}')
@@ -51,7 +51,8 @@ class nmap(VulnMulti):
51
51
 
52
52
  # Nmap opts
53
53
  PORTS: '-p',
54
- 'output_path': '-oX'
54
+ 'output_path': '-oX',
55
+ 'tcp_syn_stealth': '-sS'
55
56
  }
56
57
  opt_value_map = {
57
58
  PORTS: lambda x: ','.join([str(p) for p in x]) if isinstance(x, list) else x
@@ -73,6 +74,10 @@ class nmap(VulnMulti):
73
74
  output_path = f'{self.reports_folder}/.outputs/{self.unique_name}.xml'
74
75
  self.output_path = output_path
75
76
  self.cmd += f' -oX {self.output_path}'
77
+ tcp_syn_stealth = self.get_opt_value('tcp_syn_stealth')
78
+ if tcp_syn_stealth:
79
+ self.cmd = f'sudo {self.cmd}'
80
+ self.cmd = self.cmd.replace('-sT', '')
76
81
 
77
82
  def yielder(self):
78
83
  yield from super().yielder()
@@ -1,3 +1,5 @@
1
+ import re
2
+
1
3
  from secator.decorators import task
2
4
  from secator.definitions import (CVES, EXTRA_DATA, ID, MATCHED_AT, NAME,
3
5
  PROVIDER, REFERENCE, TAGS, OPT_NOT_SUPPORTED)
@@ -5,6 +7,9 @@ from secator.output_types import Exploit
5
7
  from secator.runners import Command
6
8
 
7
9
 
10
+ SEARCHSPLOIT_TITLE_REGEX = re.compile(r'^((?:[a-zA-Z\-_!\.()]+\d?\s?)+)\.?\s*(.*)$')
11
+
12
+
8
13
  @task()
9
14
  class searchsploit(Command):
10
15
  """Exploit-DB command line search tool."""
@@ -19,12 +24,15 @@ class searchsploit(Command):
19
24
  output_types = [Exploit]
20
25
  output_map = {
21
26
  Exploit: {
22
- NAME: lambda x: '-'.join(x['Title'].split('-')[1:]).strip(),
23
- PROVIDER: lambda x: 'EDB',
27
+ NAME: 'Title',
24
28
  ID: 'EDB-ID',
29
+ PROVIDER: lambda x: 'EDB',
25
30
  CVES: lambda x: [c for c in x['Codes'].split(';') if c.startswith('CVE-')],
26
31
  REFERENCE: lambda x: f'https://exploit-db.com/exploits/{x["EDB-ID"]}',
27
- EXTRA_DATA: lambda x: {'verified': x['Verified']}
32
+ TAGS: lambda x: searchsploit.tags_extractor(x),
33
+ EXTRA_DATA: lambda x: {
34
+ k.lower().replace('date_', ''): v for k, v in x.items() if k not in ['Title', 'EDB-ID', 'Codes', 'Tags', 'Source'] and v != '' # noqa: E501
35
+ }
28
36
  }
29
37
  }
30
38
  install_cmd = 'sudo git clone https://gitlab.com/exploit-database/exploitdb.git /opt/exploitdb || true && sudo ln -sf /opt/exploitdb/searchsploit /usr/local/bin/searchsploit' # noqa: E501
@@ -34,6 +42,18 @@ class searchsploit(Command):
34
42
  input_chunk_size = 1
35
43
  profile = 'io'
36
44
 
45
+ @staticmethod
46
+ def tags_extractor(item):
47
+ tags = []
48
+ for tag in item['Tags'].split(','):
49
+ _tag = '_'.join(
50
+ tag.lower().replace('-', '_',).replace('(', '').replace(')', '').split(' ')
51
+ )
52
+ if not _tag:
53
+ continue
54
+ tags.append(tag)
55
+ return tags
56
+
37
57
  @staticmethod
38
58
  def before_init(self):
39
59
  _in = self.input
@@ -49,5 +69,23 @@ class searchsploit(Command):
49
69
  def on_item_pre_convert(self, item):
50
70
  if self.matched_at:
51
71
  item[MATCHED_AT] = self.matched_at
52
- item[TAGS] = [self.input.replace('\'', '')]
72
+ return item
73
+
74
+ @staticmethod
75
+ def on_item(self, item):
76
+ match = SEARCHSPLOIT_TITLE_REGEX.match(item.name)
77
+ # if not match:
78
+ # self._print(f'[bold red]{item.name} ({item.reference}) did not match SEARCHSPLOIT_TITLE_REGEX. Please report this issue.[/]') # noqa: E501
79
+ if match:
80
+ group = match.groups()
81
+ product = '-'.join(group[0].strip().split(' '))
82
+ if len(group[1]) > 1:
83
+ versions, title = tuple(group[1].split(' - '))
84
+ item.name = title
85
+ product_info = [f'{product.lower()} {v.strip()}' for v in versions.split('/')]
86
+ item.tags = product_info + item.tags
87
+ # else:
88
+ # self._print(f'[bold red]{item.name} ({item.reference}) did not quite match SEARCHSPLOIT_TITLE_REGEX. Please report this issue.[/]') # noqa: E501
89
+ input_tag = '-'.join(self.input.replace('\'', '').split(' '))
90
+ item.tags = [input_tag] + item.tags
53
91
  return item
@@ -1,4 +1,5 @@
1
1
  import inspect
2
+ import importlib
2
3
  import itertools
3
4
  import logging
4
5
  import operator
@@ -8,7 +9,7 @@ import select
8
9
  import sys
9
10
  import warnings
10
11
  from datetime import datetime
11
- from importlib import import_module
12
+
12
13
  from inspect import isclass
13
14
  from pathlib import Path
14
15
  from pkgutil import iter_modules
@@ -138,7 +139,7 @@ def discover_internal_tasks():
138
139
  if module_name.startswith('_'):
139
140
  continue
140
141
  try:
141
- module = import_module(f'secator.tasks.{module_name}')
142
+ module = importlib.import_module(f'secator.tasks.{module_name}')
142
143
  except ImportError as e:
143
144
  console.print(f'[bold red]Could not import secator.tasks.{module_name}:[/]')
144
145
  console.print(f'\t[bold red]{type(e).__name__}[/]: {str(e)}')
@@ -160,17 +161,32 @@ def discover_internal_tasks():
160
161
 
161
162
  def discover_external_tasks():
162
163
  """Find external secator tasks."""
163
- if not os.path.exists('config.secator'):
164
- return []
165
- with open('config.secator', 'r') as f:
166
- classes = f.read().splitlines()
167
164
  output = []
168
- for cls_path in classes:
169
- cls = import_dynamic(cls_path, cls_root='Command')
170
- if not cls:
171
- continue
172
- # logger.warning(f'Added external tool {cls_path}')
173
- output.append(cls)
165
+ sys.dont_write_bytecode = True
166
+ for path in CONFIG.dirs.templates.glob('**/*.py'):
167
+ try:
168
+ task_name = path.stem
169
+ module_name = f'secator.tasks.{task_name}'
170
+
171
+ # console.print(f'Importing module {module_name} from {path}')
172
+ spec = importlib.util.spec_from_file_location(module_name, path)
173
+ module = importlib.util.module_from_spec(spec)
174
+ # console.print(f'Adding module "{module_name}" to sys path')
175
+ sys.modules[module_name] = module
176
+
177
+ # console.print(f'Executing module "{module}"')
178
+ spec.loader.exec_module(module)
179
+
180
+ # console.print(f'Checking that {module} contains task {task_name}')
181
+ if not hasattr(module, task_name):
182
+ console.print(f'[bold orange1]Could not load external task "{task_name}" from module {path.name}[/] ({path})')
183
+ continue
184
+ cls = getattr(module, task_name)
185
+ console.print(f'[bold green]Successfully loaded external task "{task_name}"[/] ({path})')
186
+ output.append(cls)
187
+ except Exception as e:
188
+ console.print(f'[bold red]Could not load external module {path.name}. Reason: {str(e)}.[/] ({path})')
189
+ sys.dont_write_bytecode = False
174
190
  return output
175
191
 
176
192
 
@@ -194,7 +210,7 @@ def import_dynamic(cls_path, cls_root='Command'):
194
210
  """
195
211
  try:
196
212
  package, name = cls_path.rsplit(".", maxsplit=1)
197
- cls = getattr(import_module(package), name)
213
+ cls = getattr(importlib.import_module(package), name)
198
214
  root_cls = inspect.getmro(cls)[-2]
199
215
  if root_cls.__name__ == cls_root:
200
216
  return cls
@@ -1,6 +1,7 @@
1
1
  import contextlib
2
2
  import json
3
3
  import os
4
+ import sys
4
5
  import unittest.mock
5
6
 
6
7
  from fp.fp import FreeProxy
@@ -182,3 +183,15 @@ class CommandOutputTester: # Mixin for unittest.TestCase
182
183
  raise
183
184
 
184
185
  console.print('[bold green] ok[/]')
186
+
187
+
188
+ def clear_modules():
189
+ """Clear all secator modules imports.
190
+ See https://stackoverflow.com/questions/7460363/re-import-module-under-test-to-lose-context for context.
191
+ """
192
+ keys_to_delete = []
193
+ for k, _ in sys.modules.items():
194
+ if k.startswith('secator'):
195
+ keys_to_delete.append(k)
196
+ for k in keys_to_delete:
197
+ del sys.modules[k]
@@ -0,0 +1,36 @@
1
+ from secator.runners import Command
2
+ from secator.decorators import task
3
+ from secator.output_types import Vulnerability
4
+
5
+
6
+ @task()
7
+ class ls(Command):
8
+ cmd = 'ls -al'
9
+ output_types = [Vulnerability]
10
+ output_map = {
11
+ Vulnerability: {}
12
+ }
13
+
14
+ @staticmethod
15
+ def item_loader(self, line):
16
+ fields = ['permissions', 'link_count', 'owner', 'group', 'size', 'month', 'day', 'hour', 'path']
17
+ result = [c for c in line.split(' ') if c]
18
+ if len(result) != len(fields):
19
+ return None
20
+ data = {}
21
+ for ix, value in enumerate(result):
22
+ data[fields[ix]] = value
23
+
24
+ # Output vulnerabilities
25
+ permissions = data['permissions']
26
+ path = data['path']
27
+ full_path = f'{self.input}/{path}'
28
+ if permissions[-2] == 'w': # found a vulnerability !
29
+ yield Vulnerability(
30
+ name='World-writeable path',
31
+ severity='high',
32
+ confidence='high',
33
+ provider='ls',
34
+ matched_at=full_path,
35
+ extra_data={k: v for k, v in data.items() if k != 'path'}
36
+ )
@@ -1,17 +1,12 @@
1
1
  import os
2
- import sys
3
2
  import unittest
4
3
 
4
+ from secator.utils_test import clear_modules
5
+
5
6
 
6
7
  class TestOffline(unittest.TestCase):
7
8
  def setUp(self):
8
- try:
9
- # This allows to drop the secator module loaded from other tests in order to reload the config with modified
10
- # environment variables.
11
- # See https://stackoverflow.com/questions/7460363/re-import-module-under-test-to-lose-context for context.
12
- del sys.modules['secator']
13
- except KeyError:
14
- pass
9
+ clear_modules()
15
10
  os.environ['SECATOR_OFFLINE_MODE'] = '1'
16
11
 
17
12
  def test_offline_cve_lookup(self):
@@ -0,0 +1,53 @@
1
+ import unittest
2
+ from secator.config import CONFIG
3
+ from secator.output_types import Vulnerability
4
+ from secator.utils_test import FIXTURES_DIR, clear_modules
5
+ import os
6
+
7
+ import shutil
8
+
9
+
10
+ class TestTemplate(unittest.TestCase):
11
+ def setUp(self):
12
+ self.template_dir = CONFIG.dirs.templates
13
+ self.custom_task_path = self.template_dir / 'ls.py'
14
+ self.writeable_file = self.template_dir / 'test.txt'
15
+ self.custom_workflow_path = self.template_dir / 'ls.yml'
16
+ shutil.copy(f'{FIXTURES_DIR}/ls.py', self.custom_task_path)
17
+ shutil.copy(f'{FIXTURES_DIR}/ls.yml', self.custom_workflow_path)
18
+ self.writeable_file.touch()
19
+ os.chmod(self.writeable_file, 0o007)
20
+ self.expected_vuln = Vulnerability(
21
+ name='World-writeable path',
22
+ severity='high',
23
+ confidence='high',
24
+ provider='ls',
25
+ matched_at=f'{str(self.writeable_file)}',
26
+ _source='ls',
27
+ )
28
+ clear_modules()
29
+ self.maxDiff = None
30
+
31
+ def tearDown(self):
32
+ self.custom_task_path.unlink()
33
+ self.custom_workflow_path.unlink()
34
+ self.writeable_file.unlink()
35
+
36
+ def test_external_task(self):
37
+ from secator.tasks import ls
38
+ results = ls(str(self.template_dir)).run()
39
+ self.assertEqual(len(results), 1)
40
+ self.assertTrue(self.expected_vuln == Vulnerability.load(results[0].toDict()))
41
+
42
+ def test_external_workflow(self):
43
+ from secator.cli import ALL_WORKFLOWS
44
+ from secator.runners import Workflow
45
+ ls_workflow = None
46
+ for w in ALL_WORKFLOWS:
47
+ if w.name == 'ls':
48
+ ls_workflow = w
49
+ self.assertIsNotNone(ls_workflow)
50
+ results = Workflow(ls_workflow, targets=[str(self.template_dir)]).run()
51
+ self.assertEqual(len(results), 2)
52
+ self.assertTrue(self.expected_vuln == Vulnerability.load(results[1].toDict()))
53
+
@@ -1,10 +0,0 @@
1
- from secator.utils import discover_internal_tasks, discover_external_tasks
2
- INTERNAL_TASKS = discover_internal_tasks()
3
- EXTERNAL_TASKS = discover_external_tasks()
4
- ALL_TASKS = INTERNAL_TASKS + EXTERNAL_TASKS
5
- __all__ = [
6
- cls.__name__
7
- for cls in ALL_TASKS
8
- ]
9
- for cls in INTERNAL_TASKS:
10
- exec(f'from .{cls.__name__} import {cls.__name__}')
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes