secator 0.10.1a12__py3-none-any.whl → 0.15.1__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 (73) hide show
  1. secator/celery.py +10 -5
  2. secator/celery_signals.py +2 -11
  3. secator/cli.py +309 -69
  4. secator/config.py +3 -2
  5. secator/configs/profiles/aggressive.yaml +6 -5
  6. secator/configs/profiles/default.yaml +6 -7
  7. secator/configs/profiles/insane.yaml +8 -0
  8. secator/configs/profiles/paranoid.yaml +8 -0
  9. secator/configs/profiles/polite.yaml +8 -0
  10. secator/configs/profiles/sneaky.yaml +8 -0
  11. secator/configs/profiles/tor.yaml +5 -0
  12. secator/configs/workflows/host_recon.yaml +11 -2
  13. secator/configs/workflows/url_dirsearch.yaml +5 -0
  14. secator/configs/workflows/url_params_fuzz.yaml +25 -0
  15. secator/configs/workflows/wordpress.yaml +4 -1
  16. secator/decorators.py +64 -34
  17. secator/definitions.py +8 -4
  18. secator/installer.py +84 -49
  19. secator/output_types/__init__.py +2 -1
  20. secator/output_types/certificate.py +78 -0
  21. secator/output_types/stat.py +3 -0
  22. secator/output_types/user_account.py +1 -1
  23. secator/report.py +2 -2
  24. secator/rich.py +1 -1
  25. secator/runners/_base.py +50 -11
  26. secator/runners/_helpers.py +15 -3
  27. secator/runners/command.py +85 -21
  28. secator/runners/scan.py +6 -3
  29. secator/runners/task.py +1 -0
  30. secator/runners/workflow.py +22 -4
  31. secator/tasks/_categories.py +25 -17
  32. secator/tasks/arjun.py +92 -0
  33. secator/tasks/bbot.py +33 -4
  34. secator/tasks/bup.py +4 -2
  35. secator/tasks/cariddi.py +17 -4
  36. secator/tasks/dalfox.py +4 -2
  37. secator/tasks/dirsearch.py +4 -2
  38. secator/tasks/dnsx.py +5 -2
  39. secator/tasks/dnsxbrute.py +4 -1
  40. secator/tasks/feroxbuster.py +5 -2
  41. secator/tasks/ffuf.py +7 -3
  42. secator/tasks/fping.py +4 -1
  43. secator/tasks/gau.py +5 -2
  44. secator/tasks/gf.py +4 -2
  45. secator/tasks/gitleaks.py +79 -0
  46. secator/tasks/gospider.py +5 -2
  47. secator/tasks/grype.py +5 -2
  48. secator/tasks/h8mail.py +4 -2
  49. secator/tasks/httpx.py +6 -3
  50. secator/tasks/katana.py +6 -3
  51. secator/tasks/maigret.py +4 -2
  52. secator/tasks/mapcidr.py +5 -3
  53. secator/tasks/msfconsole.py +8 -6
  54. secator/tasks/naabu.py +16 -5
  55. secator/tasks/nmap.py +31 -29
  56. secator/tasks/nuclei.py +18 -10
  57. secator/tasks/searchsploit.py +8 -3
  58. secator/tasks/subfinder.py +6 -3
  59. secator/tasks/testssl.py +276 -0
  60. secator/tasks/trivy.py +98 -0
  61. secator/tasks/wafw00f.py +85 -0
  62. secator/tasks/wpprobe.py +96 -0
  63. secator/tasks/wpscan.py +8 -4
  64. secator/template.py +61 -67
  65. secator/utils.py +31 -18
  66. secator/utils_test.py +34 -10
  67. {secator-0.10.1a12.dist-info → secator-0.15.1.dist-info}/METADATA +11 -3
  68. secator-0.15.1.dist-info/RECORD +128 -0
  69. secator/configs/profiles/stealth.yaml +0 -7
  70. secator-0.10.1a12.dist-info/RECORD +0 -116
  71. {secator-0.10.1a12.dist-info → secator-0.15.1.dist-info}/WHEEL +0 -0
  72. {secator-0.10.1a12.dist-info → secator-0.15.1.dist-info}/entry_points.txt +0 -0
  73. {secator-0.10.1a12.dist-info → secator-0.15.1.dist-info}/licenses/LICENSE +0 -0
secator/tasks/naabu.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from secator.decorators import task
2
- from secator.definitions import (DELAY, HOST, OPT_NOT_SUPPORTED, PORT, PORTS,
2
+ from secator.definitions import (DELAY, HOST, IP, OPT_NOT_SUPPORTED, PORT, PORTS,
3
3
  PROXY, RATE_LIMIT, RETRIES, STATE, THREADS,
4
4
  TIMEOUT, TOP_PORTS)
5
5
  from secator.output_types import Port
@@ -10,14 +10,17 @@ from secator.tasks._categories import ReconPort
10
10
  @task()
11
11
  class naabu(ReconPort):
12
12
  """Port scanning tool written in Go."""
13
- cmd = 'naabu -Pn -silent'
13
+ cmd = 'naabu'
14
+ tags = ['port', 'scan']
14
15
  input_flag = '-host'
16
+ input_types = [HOST, IP]
15
17
  file_flag = '-list'
16
18
  json_flag = '-json'
17
19
  opts = {
18
20
  PORTS: {'type': str, 'short': 'p', 'help': 'Ports'},
19
21
  TOP_PORTS: {'type': str, 'short': 'tp', 'help': 'Top ports'},
20
22
  'scan_type': {'type': str, 'short': 'st', 'help': 'Scan type (SYN (s)/CONNECT(c))'},
23
+ 'skip_host_discovery': {'is_flag': True, 'short': 'Pn', 'default': False, 'help': 'Skip host discovery'},
21
24
  # 'health_check': {'is_flag': True, 'short': 'hc', 'help': 'Health check'}
22
25
  }
23
26
  opt_key_map = {
@@ -47,7 +50,8 @@ class naabu(ReconPort):
47
50
  }
48
51
  }
49
52
  output_types = [Port]
50
- install_cmd = 'go install -v github.com/projectdiscovery/naabu/v2/cmd/naabu@v2.3.3'
53
+ install_version = 'v2.3.3'
54
+ install_cmd = 'go install -v github.com/projectdiscovery/naabu/v2/cmd/naabu@[install_version]'
51
55
  install_github_handle = 'projectdiscovery/naabu'
52
56
  install_pre = {'apt': ['libpcap-dev'], 'apk': ['libpcap-dev', 'libc6-compat'], 'pacman|brew': ['libpcap']}
53
57
  install_post = {'arch|alpine': 'sudo ln -sf /usr/lib/libpcap.so /usr/lib/libpcap.so.0.8'}
@@ -62,8 +66,15 @@ class naabu(ReconPort):
62
66
  if input == 'localhost':
63
67
  self.inputs[ix] = '127.0.0.1'
64
68
 
69
+ @staticmethod
70
+ def on_cmd(self):
71
+ scan_type = self.get_opt_value('scan_type')
72
+ if scan_type == 's':
73
+ self.requires_sudo = True
74
+
65
75
  @staticmethod
66
76
  def on_item(self, item):
67
- if item.host == '127.0.0.1':
68
- item.host = 'localhost'
77
+ if isinstance(item, Port):
78
+ if item.host == '127.0.0.1':
79
+ item.host = 'localhost'
69
80
  return item
secator/tasks/nmap.py CHANGED
@@ -24,7 +24,9 @@ logger = logging.getLogger(__name__)
24
24
  class nmap(VulnMulti):
25
25
  """Network Mapper is a free and open source utility for network discovery and security auditing."""
26
26
  cmd = 'nmap'
27
+ tags = ['port', 'scan']
27
28
  input_flag = None
29
+ input_types = [HOST, IP]
28
30
  input_chunk_size = 1
29
31
  file_flag = '-iL'
30
32
  opt_prefix = '--'
@@ -36,7 +38,7 @@ class nmap(VulnMulti):
36
38
 
37
39
  # Script scanning
38
40
  SCRIPT: {'type': str, 'default': None, 'help': 'NSE scripts'},
39
- 'script_args': {'type': str, 'default': None, 'help': 'NSE script arguments (n1=v1,n2=v2,...)'},
41
+ 'script_args': {'type': str, 'short': 'sargs', 'default': None, 'help': 'NSE script arguments (n1=v1,n2=v2,...)'},
40
42
 
41
43
  # Host discovery
42
44
  'skip_host_discovery': {'is_flag': True, 'short': 'Pn', 'default': False, 'help': 'Skip host discovery (no ping)'},
@@ -44,42 +46,45 @@ class nmap(VulnMulti):
44
46
  # Service and version detection
45
47
  'version_detection': {'is_flag': True, 'short': 'sV', 'default': False, 'help': 'Enable version detection (slow)'},
46
48
  'detect_all': {'is_flag': True, 'short': 'A', 'default': False, 'help': 'Enable OS detection, version detection, script scanning, and traceroute on open ports'}, # noqa: E501
47
- 'detect_os': {'is_flag': True, 'short': 'O', 'default': False, 'help': 'Enable OS detection'},
49
+ 'detect_os': {'is_flag': True, 'short': 'O', 'default': False, 'help': 'Enable OS detection', 'requires_sudo': True},
48
50
 
49
51
  # Scan techniques
50
- 'tcp_syn_stealth': {'is_flag': True, 'short': 'sS', 'default': False, 'help': 'TCP SYN Stealth'},
52
+ 'tcp_syn_stealth': {'is_flag': True, 'short': 'sS', 'default': False, 'help': 'TCP SYN Stealth', 'requires_sudo': True}, # noqa: E501
51
53
  'tcp_connect': {'is_flag': True, 'short': 'sT', 'default': False, 'help': 'TCP Connect scan'},
52
- 'udp_scan': {'is_flag': True, 'short': 'sU', 'default': False, 'help': 'UDP scan'},
53
- 'tcp_null_scan': {'is_flag': True, 'short': 'sN', 'default': False, 'help': 'TCP Null scan'},
54
- 'tcp_fin_scan': {'is_flag': True, 'short': 'sF', 'default': False, 'help': 'TCP FIN scan'},
55
- 'tcp_xmas_scan': {'is_flag': True, 'short': 'sX', 'default': False, 'help': 'TCP Xmas scan'},
56
- 'tcp_ack_scan': {'is_flag': True, 'short': 'sA', 'default': False, 'help': 'TCP ACK scan'},
57
- 'tcp_window_scan': {'is_flag': True, 'short': 'sW', 'default': False, 'help': 'TCP Window scan'},
58
- 'tcp_maimon_scan': {'is_flag': True, 'short': 'sM', 'default': False, 'help': 'TCP Maimon scan'},
59
- 'sctp_init_scan': {'is_flag': True, 'short': 'sY', 'default': False, 'help': 'SCTP Init scan'},
60
- 'sctp_cookie_echo_scan': {'is_flag': True, 'short': 'sZ', 'default': False, 'help': 'SCTP Cookie Echo scan'},
54
+ 'udp_scan': {'is_flag': True, 'short': 'sU', 'default': False, 'help': 'UDP scan', 'requires_sudo': True},
55
+ 'tcp_null_scan': {'is_flag': True, 'short': 'sN', 'default': False, 'help': 'TCP Null scan', 'requires_sudo': True},
56
+ 'tcp_fin_scan': {'is_flag': True, 'short': 'sF', 'default': False, 'help': 'TCP FIN scan', 'requires_sudo': True},
57
+ 'tcp_xmas_scan': {'is_flag': True, 'short': 'sX', 'default': False, 'help': 'TCP Xmas scan', 'requires_sudo': True},
58
+ 'tcp_ack_scan': {'is_flag': True, 'short': 'sA', 'default': False, 'help': 'TCP ACK scan', 'requires_sudo': True},
59
+ 'tcp_window_scan': {'is_flag': True, 'short': 'sW', 'default': False, 'help': 'TCP Window scan', 'requires_sudo': True}, # noqa: E501
60
+ 'tcp_maimon_scan': {'is_flag': True, 'short': 'sM', 'default': False, 'help': 'TCP Maimon scan', 'requires_sudo': True}, # noqa: E501
61
+ 'sctp_init_scan': {'is_flag': True, 'short': 'sY', 'default': False, 'help': 'SCTP Init scan', 'requires_sudo': True},
62
+ 'sctp_cookie_echo_scan': {'is_flag': True, 'short': 'sZ', 'default': False, 'help': 'SCTP Cookie Echo scan', 'requires_sudo': True}, # noqa: E501
61
63
  'ping_scan': {'is_flag': True, 'short': 'sn', 'default': False, 'help': 'Ping scan (disable port scan)'},
62
- 'ip_protocol_scan': {'type': str, 'short': 'sO', 'default': None, 'help': 'IP protocol scan'},
64
+ 'ip_protocol_scan': {'type': str, 'short': 'sO', 'default': None, 'help': 'IP protocol scan', 'requires_sudo': True},
63
65
  'script_scan': {'is_flag': True, 'short': 'sC', 'default': False, 'help': 'Enable default scanning'},
64
- 'zombie_host': {'type': str, 'short': 'sI', 'default': None, 'help': 'Use a zombie host for idle scan'},
66
+ 'zombie_host': {'type': str, 'short': 'sI', 'default': None, 'help': 'Use a zombie host for idle scan', 'requires_sudo': True}, # noqa: E501
65
67
  'ftp_relay_host': {'type': str, 'short': 'sB', 'default': None, 'help': 'FTP bounce scan relay host'},
66
68
 
67
69
  # Firewall / IDS evasion and spoofing
68
70
  'spoof_source_port': {'type': int, 'short': 'g', 'default': None, 'help': 'Send packets from a specific port'},
69
71
  'spoof_source_ip': {'type': str, 'short': 'S', 'default': None, 'help': 'Spoof source IP address'},
70
72
  'spoof_source_mac': {'type': str, 'short': 'spoofmac', 'default': None, 'help': 'Spoof MAC address'},
71
- 'fragment': {'is_flag': True, 'short': 'fragment', 'default': False, 'help': 'Fragment packets'},
72
- 'mtu': {'type': int, 'short': 'mtu', 'default': None, 'help': 'Fragment packets with given MTU'},
73
- 'ttl': {'type': int, 'short': 'ttl', 'default': None, 'help': 'Set TTL'},
74
- 'badsum': {'is_flag': True, 'short': 'badsum', 'default': False, 'help': 'Create a bad checksum in the TCP header'},
73
+ 'fragment': {'is_flag': True, 'short': 'fragment', 'default': False, 'help': 'Fragment packets', 'requires_sudo': True}, # noqa: E501
74
+ 'mtu': {'type': int, 'short': 'mtu', 'default': None, 'help': 'Fragment packets with given MTU', 'requires_sudo': True}, # noqa: E501
75
+ 'ttl': {'type': int, 'short': 'ttl', 'default': None, 'help': 'Set TTL', 'requires_sudo': True},
76
+ 'badsum': {'is_flag': True, 'short': 'badsum', 'default': False, 'help': 'Create a bad checksum in the TCP header', 'requires_sudo': True}, # noqa: E501
75
77
  'ipv6': {'is_flag': True, 'short': 'ipv6', 'default': False, 'help': 'Enable IPv6 scanning'},
76
78
 
77
79
  # Host discovery
78
- 'traceroute': {'is_flag': True, 'short': 'traceroute', 'default': False, 'help': 'Traceroute'},
79
- 'disable_arp_ping': {'is_flag': True, 'short': 'disable-arp-ping', 'default': False, 'help': 'Disable ARP ping'},
80
+ 'traceroute': {'is_flag': True, 'short': 'traceroute', 'default': False, 'help': 'Traceroute', 'requires_sudo': True},
81
+ 'disable_arp_ping': {'is_flag': True, 'short': 'dap', 'default': False, 'help': 'Disable ARP ping'},
80
82
 
81
83
  # Misc
82
- 'output_path': {'type': str, 'short': 'oX', 'default': None, 'help': 'Output XML file path'},
84
+ 'output_path': {'type': str, 'short': 'oX', 'default': None, 'help': 'Output XML file path', 'internal': True, 'display': False}, # noqa: E501
85
+ 'debug': {'is_flag': True, 'short': 'd', 'default': False, 'help': 'Enable debug mode'},
86
+ 'verbose': {'is_flag': True, 'short': 'v', 'default': False, 'help': 'Enable verbose mode'},
87
+ 'timing': {'type': int, 'short': 'T', 'default': None, 'help': 'Timing template (0: paranoid, 1: sneaky, 2: polite, 3: normal, 4: aggressive, 5: insane)'}, # noqa: E501
83
88
  }
84
89
  opt_key_map = {
85
90
  HEADER: OPT_NOT_SUPPORTED,
@@ -117,7 +122,7 @@ class nmap(VulnMulti):
117
122
  'spoof_source_port': '-g',
118
123
  'spoof_source_ip': '-S',
119
124
  'spoof_source_mac': '--spoof-mac',
120
- 'fragmentation': '-f',
125
+ 'fragment': '-f',
121
126
  'mtu': '--mtu',
122
127
  'ttl': '--ttl',
123
128
  'badsum': '--badsum',
@@ -144,20 +149,17 @@ class nmap(VulnMulti):
144
149
  profile = 'io'
145
150
 
146
151
  @staticmethod
147
- def on_init(self):
152
+ def on_cmd(self):
148
153
  output_path = self.get_opt_value(OUTPUT_PATH)
149
154
  if not output_path:
150
155
  output_path = f'{self.reports_folder}/.outputs/{self.unique_name}.xml'
151
156
  self.output_path = output_path
152
157
  self.cmd += f' -oX {self.output_path}'
153
- tcp_syn_stealth = self.get_opt_value('tcp_syn_stealth')
154
- tcp_connect = self.get_opt_value('tcp_connect')
155
- udp_scan = self.get_opt_value('udp_scan')
156
- if tcp_syn_stealth or udp_scan:
157
- self.cmd = f'sudo {self.cmd}'
158
+ tcp_syn_stealth = self.cmd_options.get('tcp_syn_stealth')
159
+ tcp_connect = self.cmd_options.get('tcp_connect')
158
160
  if tcp_connect and tcp_syn_stealth:
159
161
  self._print(
160
- 'Options -sT (SYN stealth scan) and -sS (CONNECT scan) are conflicting. Keeping only -sT.',
162
+ 'Options -sT (SYN stealth scan) and -sS (CONNECT scan) are conflicting. Keeping only -sS.',
161
163
  'bold gold3')
162
164
  self.cmd = self.cmd.replace('-sT ', '')
163
165
 
secator/tasks/nuclei.py CHANGED
@@ -4,7 +4,7 @@ from secator.definitions import (CONFIDENCE, CVSS_SCORE, DELAY, DESCRIPTION,
4
4
  MATCHED_AT, NAME, OPT_NOT_SUPPORTED, PERCENT,
5
5
  PROVIDER, PROXY, RATE_LIMIT, REFERENCES,
6
6
  RETRIES, SEVERITY, TAGS, THREADS, TIMEOUT,
7
- USER_AGENT)
7
+ USER_AGENT, HOST, URL)
8
8
  from secator.output_types import Progress, Vulnerability
9
9
  from secator.serializers import JSONSerializer
10
10
  from secator.tasks._categories import VulnMulti
@@ -14,21 +14,28 @@ from secator.tasks._categories import VulnMulti
14
14
  class nuclei(VulnMulti):
15
15
  """Fast and customisable vulnerability scanner based on simple YAML based DSL."""
16
16
  cmd = 'nuclei'
17
+ tags = ['vuln', 'scan']
18
+ input_types = [HOST, IP, URL]
17
19
  file_flag = '-l'
18
20
  input_flag = '-u'
19
21
  json_flag = '-jsonl'
20
22
  opts = {
21
- 'templates': {'type': str, 'short': 't', 'help': 'Templates'},
22
- 'tags': {'type': str, 'help': 'Tags'},
23
- 'exclude_tags': {'type': str, 'short': 'etags', 'help': 'Exclude tags'},
24
- 'exclude_severity': {'type': str, 'short': 'es', 'help': 'Exclude severity'},
25
- 'template_id': {'type': str, 'short': 'tid', 'help': 'Template id'},
23
+ 'bulk_size': {'type': int, 'short': 'bs', 'help': 'Maximum number of hosts to be analyzed in parallel per template'}, # noqa: E501
26
24
  'debug': {'type': str, 'help': 'Debug mode'},
25
+ 'exclude_severity': {'type': str, 'short': 'es', 'help': 'Exclude severity'},
26
+ 'exclude_tags': {'type': str, 'short': 'etags', 'help': 'Exclude tags'},
27
+ 'input_mode': {'type': str, 'short': 'im', 'help': 'Mode of input file (list, burp, jsonl, yaml, openapi, swagger)'},
28
+ 'hang_monitor': {'is_flag': True, 'short': 'hm', 'default': True, 'help': 'Enable nuclei hang monitoring'},
29
+ 'headless_bulk_size': {'type': int, 'short': 'hbs', 'help': 'Maximum number of headless hosts to be analzyed in parallel per template'}, # noqa: E501
30
+ 'new_templates': {'type': str, 'short': 'nt', 'help': 'Run only new templates added in latest nuclei-templates release'}, # noqa: E501
31
+ 'automatic_scan': {'is_flag': True, 'short': 'as', 'help': 'Automatic web scan using wappalyzer technology detection to tags mapping'}, # noqa: E501
32
+ 'omit_raw': {'is_flag': True, 'short': 'or', 'default': True, 'help': 'Omit requests/response pairs in the JSON, JSONL, and Markdown outputs (for findings only)'}, # noqa: E501
27
33
  'stats': {'is_flag': True, 'short': 'stats', 'default': True, 'help': 'Display statistics about the running scan'},
28
34
  'stats_json': {'is_flag': True, 'short': 'sj', 'default': True, 'help': 'Display statistics in JSONL(ines) format'},
29
- 'stats_interval': {'type': str, 'short': 'si', 'default': 20, 'help': 'Number of seconds to wait between showing a statistics update'}, # noqa: E501
30
- 'hang_monitor': {'is_flag': True, 'short': 'hm', 'default': True, 'help': 'Enable nuclei hang monitoring'},
31
- 'omit_raw': {'is_flag': True, 'short': 'or', 'default': True, 'help': 'Omit requests/response pairs in the JSON, JSONL, and Markdown outputs (for findings only)'} # noqa: E501
35
+ 'stats_interval': {'type': str, 'short': 'si', 'help': 'Number of seconds to wait between showing a statistics update'}, # noqa: E501
36
+ 'tags': {'type': str, 'help': 'Tags'},
37
+ 'templates': {'type': str, 'short': 't', 'help': 'Templates'},
38
+ 'template_id': {'type': str, 'short': 'tid', 'help': 'Template id'},
32
39
  }
33
40
  opt_key_map = {
34
41
  HEADER: 'header',
@@ -76,7 +83,8 @@ class nuclei(VulnMulti):
76
83
  install_pre = {
77
84
  '*': ['git']
78
85
  }
79
- install_cmd = 'go install -v github.com/projectdiscovery/nuclei/v2/cmd/nuclei@latest'
86
+ install_version = 'v3.4.2'
87
+ install_cmd = 'go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@[install_version]'
80
88
  install_github_handle = 'projectdiscovery/nuclei'
81
89
  install_post = {
82
90
  '*': 'nuclei -ut'
@@ -3,7 +3,7 @@ import re
3
3
  from secator.config import CONFIG
4
4
  from secator.decorators import task
5
5
  from secator.definitions import (CVES, EXTRA_DATA, ID, MATCHED_AT, NAME,
6
- PROVIDER, REFERENCE, TAGS, OPT_NOT_SUPPORTED)
6
+ PROVIDER, REFERENCE, TAGS, TECHNOLOGY, OPT_NOT_SUPPORTED)
7
7
  from secator.output_types import Exploit
8
8
  from secator.runners import Command
9
9
  from secator.serializers import JSONSerializer
@@ -16,7 +16,9 @@ SEARCHSPLOIT_TITLE_REGEX = re.compile(r'^((?:[a-zA-Z\-_!\.()]+\d?\s?)+)\.?\s*(.*
16
16
  class searchsploit(Command):
17
17
  """Exploit searcher based on ExploitDB."""
18
18
  cmd = 'searchsploit'
19
+ tags = ['exploit', 'recon']
19
20
  input_flag = None
21
+ input_types = [TECHNOLOGY]
20
22
  json_flag = '--json'
21
23
  version_flag = OPT_NOT_SUPPORTED
22
24
  opts = {
@@ -41,9 +43,10 @@ class searchsploit(Command):
41
43
  install_pre = {
42
44
  'apk': ['ncurses']
43
45
  }
46
+ install_version = '2025-04-23'
44
47
  install_cmd = (
45
- f'git clone --depth 1 --single-branch https://gitlab.com/exploit-database/exploitdb.git {CONFIG.dirs.share}/exploitdb || true && ' # noqa: E501
46
- f'ln -sf $HOME/.local/share/exploitdb/searchsploit {CONFIG.dirs.bin}/searchsploit'
48
+ f'git clone --depth 1 --single-branch -b [install_version] https://gitlab.com/exploit-database/exploitdb.git {CONFIG.dirs.share}/exploitdb_[install_version] || true && ' # noqa: E501
49
+ f'ln -sf $HOME/.local/share/exploitdb_[install_version]/searchsploit {CONFIG.dirs.bin}/searchsploit'
47
50
  )
48
51
  proxychains = False
49
52
  proxy_socks5 = False
@@ -83,6 +86,8 @@ class searchsploit(Command):
83
86
 
84
87
  @staticmethod
85
88
  def on_item(self, item):
89
+ if not isinstance(item, Exploit):
90
+ return item
86
91
  match = SEARCHSPLOIT_TITLE_REGEX.match(item.name)
87
92
  # if not match:
88
93
  # self._print(f'[bold red]{item.name} ({item.reference}) did not match SEARCHSPLOIT_TITLE_REGEX. Please report this issue.[/]') # noqa: E501
@@ -1,5 +1,5 @@
1
1
  from secator.decorators import task
2
- from secator.definitions import (DELAY, DOMAIN, OPT_NOT_SUPPORTED, PROXY,
2
+ from secator.definitions import (DELAY, DOMAIN, HOST, OPT_NOT_SUPPORTED, PROXY,
3
3
  RATE_LIMIT, RETRIES, THREADS, TIMEOUT)
4
4
  from secator.output_types import Subdomain
5
5
  from secator.serializers import JSONSerializer
@@ -9,7 +9,9 @@ from secator.tasks._categories import ReconDns
9
9
  @task()
10
10
  class subfinder(ReconDns):
11
11
  """Fast passive subdomain enumeration tool."""
12
- cmd = 'subfinder -silent -cs'
12
+ cmd = 'subfinder -cs'
13
+ tags = ['dns', 'recon']
14
+ input_types = [HOST]
13
15
  file_flag = '-dL'
14
16
  input_flag = '-d'
15
17
  json_flag = '-json'
@@ -31,7 +33,8 @@ class subfinder(ReconDns):
31
33
  }
32
34
  }
33
35
  output_types = [Subdomain]
34
- install_cmd = 'go install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest'
36
+ install_version = 'v2.7.0'
37
+ install_cmd = 'go install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@[install_version]'
35
38
  install_github_handle = 'projectdiscovery/subfinder'
36
39
  proxychains = False
37
40
  proxy_http = True
@@ -0,0 +1,276 @@
1
+ import json
2
+ import os
3
+ from datetime import datetime
4
+
5
+ from secator.config import CONFIG
6
+ from secator.decorators import task
7
+ from secator.output_types import Vulnerability, Certificate, Error, Info, Ip, Tag
8
+ from secator.definitions import (PROXY, HOST, USER_AGENT, HEADER, OUTPUT_PATH,
9
+ CERTIFICATE_STATUS_UNKNOWN, CERTIFICATE_STATUS_TRUSTED, CERTIFICATE_STATUS_REVOKED,
10
+ TIMEOUT)
11
+ from secator.tasks._categories import Command, OPTS
12
+
13
+
14
+ @task()
15
+ class testssl(Command):
16
+ """SSL/TLS security scanner, including ciphers, protocols and cryptographic flaws."""
17
+ cmd = 'testssl.sh'
18
+ tags = ['dns', 'recon', 'tls']
19
+ input_types = [HOST]
20
+ input_flag = None
21
+ file_flag = '-iL'
22
+ file_eof_newline = True
23
+ version_flag = ''
24
+ opt_prefix = '--'
25
+ opts = {
26
+ 'verbose': {'is_flag': True, 'default': False, 'internal': True, 'display': True, 'help': 'Record all SSL/TLS info, not only critical info'}, # noqa: E501
27
+ 'parallel': {'is_flag': True, 'default': False, 'help': 'Test multiple hosts in parallel'},
28
+ 'warnings': {'type': str, 'default': None, 'help': 'Set to "batch" to stop on errors, and "off" to skip errors and continue'}, # noqa: E501
29
+ 'ids_friendly': {'is_flag': True, 'default': False, 'help': 'Avoid IDS blocking by skipping a few vulnerability checks'}, # noqa: E501
30
+ 'hints': {'is_flag': True, 'default': False, 'help': 'Additional hints to findings'},
31
+ 'server_defaults': {'is_flag': True, 'default': False, 'help': 'Displays the server default picks and certificate info'}, # noqa: E501
32
+ }
33
+ meta_opts = {
34
+ PROXY: OPTS[PROXY],
35
+ USER_AGENT: OPTS[USER_AGENT],
36
+ HEADER: OPTS[HEADER],
37
+ TIMEOUT: OPTS[TIMEOUT],
38
+ }
39
+ opt_key_map = {
40
+ PROXY: 'proxy',
41
+ USER_AGENT: 'user-agent',
42
+ HEADER: 'reqheader',
43
+ TIMEOUT: 'connect-timeout',
44
+ 'ipv6': '-6',
45
+ }
46
+ output_types = [Certificate, Vulnerability, Ip, Tag]
47
+ proxy_http = True
48
+ proxychains = False
49
+ proxy_socks5 = False
50
+ profile = 'io'
51
+ install_pre = {
52
+ 'apk': ['hexdump', 'coreutils', 'procps'],
53
+ 'pacman': ['util-linux'],
54
+ '*': ['bsdmainutils']
55
+ }
56
+ install_version = 'v3.2.0'
57
+ install_cmd = (
58
+ f'git clone --depth 1 --single-branch -b [install_version] https://github.com/drwetter/testssl.sh.git {CONFIG.dirs.share}/testssl.sh_[install_version] || true && ' # noqa: E501
59
+ f'ln -sf {CONFIG.dirs.share}/testssl.sh_[install_version]/testssl.sh {CONFIG.dirs.bin}'
60
+ )
61
+
62
+ @staticmethod
63
+ def on_cmd(self):
64
+ output_path = self.get_opt_value(OUTPUT_PATH)
65
+ if not output_path:
66
+ output_path = f'{self.reports_folder}/.outputs/{self.unique_name}.json'
67
+ self.output_path = output_path
68
+ self.cmd += f' --jsonfile {self.output_path}'
69
+
70
+ # Hack because target needs to be the last argument in testssl.sh
71
+ if len(self.inputs) == 1:
72
+ target = self.inputs[0]
73
+ self.cmd = self.cmd.replace(f' {target}', '')
74
+ self.cmd += f' {target}'
75
+
76
+ @staticmethod
77
+ def on_cmd_done(self):
78
+ if not os.path.exists(self.output_path):
79
+ yield Error(message=f'Could not find JSON results in {self.output_path}')
80
+ return
81
+ yield Info(message=f'JSON results saved to {self.output_path}')
82
+
83
+ verbose = self.get_opt_value('verbose')
84
+ with open(self.output_path, 'r') as f:
85
+ data = json.load(f)
86
+ bad_cyphers = {}
87
+ retrieved_certificates = {}
88
+ ignored_item_ids = ["scanTime", "overall_grade", "DNS_CAArecord"]
89
+ ip_addresses = []
90
+ host_to_ips = {}
91
+
92
+ for item in data:
93
+ host, ip = tuple(item['ip'].split('/'))
94
+ id = item['id']
95
+ # port = item['port']
96
+ finding = item['finding']
97
+ severity = item['severity'].lower()
98
+ cwe = item.get('cwe')
99
+ vuln_tags = ['ssl', 'tls']
100
+ if cwe:
101
+ vuln_tags.append(cwe)
102
+
103
+ # Skip ignored items
104
+ if id.startswith(tuple(ignored_item_ids)):
105
+ continue
106
+
107
+ # Add IP to address pool
108
+ host_to_ips.setdefault(host, []).append(ip)
109
+ if ip not in ip_addresses:
110
+ ip_addresses.append(ip)
111
+ yield Ip(
112
+ host=host,
113
+ ip=ip,
114
+ alive=True
115
+ )
116
+
117
+ # Process errors
118
+ if id.startswith("scanProblem"):
119
+ yield Error(message=finding)
120
+
121
+ # Process bad ciphers
122
+ elif id.startswith('cipher-'):
123
+ splited_item = item["finding"].split(" ")
124
+ concerned_protocol = splited_item[0]
125
+ bad_cypher = splited_item[-1]
126
+ bad_cyphers.setdefault(ip, {}).setdefault(concerned_protocol, []).append(bad_cypher) # noqa: E501
127
+
128
+ # Process certificates
129
+ elif id.startswith('cert_') or id.startswith('cert '):
130
+ retrieved_certificates.setdefault(ip, []).append(item)
131
+
132
+ # Process intermediate certificates
133
+ elif id.startswith('intermediate_cert_'):
134
+ # TODO: implement this
135
+ pass
136
+
137
+ # If info or ok, create a tag only if 'verbose' option is set
138
+ elif severity in ['info', 'ok']:
139
+ if not verbose:
140
+ continue
141
+ yield Tag(
142
+ name=f'SSL/TLS [{id}]',
143
+ match=host,
144
+ extra_data={
145
+ 'type': id,
146
+ 'finding': finding,
147
+ }
148
+ )
149
+
150
+ # Create vulnerability
151
+ else:
152
+ if id in ['TLS1', 'TLS1_1']:
153
+ human_name = f'SSL/TLS deprecated protocol offered: {id}'
154
+ else:
155
+ human_name = f'SSL/TLS {id}: {finding}'
156
+ yield Vulnerability(
157
+ name=human_name,
158
+ matched_at=host,
159
+ ip=ip,
160
+ tags=vuln_tags,
161
+ severity=severity,
162
+ confidence='high',
163
+ extra_data={
164
+ 'id': id,
165
+ 'finding': finding
166
+ }
167
+ )
168
+
169
+ # Creating vulnerability for the deprecated ciphers
170
+ for ip, protocols in bad_cyphers.items():
171
+ for protocol, cyphers in protocols.items():
172
+ yield Vulnerability(
173
+ name=f'SSL/TLS vulnerability ciphers for {protocol} deprecated',
174
+ matched_at=ip,
175
+ ip=ip,
176
+ confidence='high',
177
+ severity='low',
178
+ extra_data={
179
+ 'cyphers': cyphers
180
+ }
181
+ )
182
+
183
+ # Creating certificates for each founded target
184
+ host_to_ips = {k: set(v) for k, v in host_to_ips.items()}
185
+ for ip, certs in retrieved_certificates.items():
186
+ host = [k for k, v in host_to_ips.items() if ip in v][0]
187
+ cert_data = {
188
+ 'host': host,
189
+ 'ip': ip,
190
+ 'fingerprint_sha256': None,
191
+ 'subject_cn': None,
192
+ 'subject_an': None,
193
+ 'not_before': None,
194
+ 'not_after': None,
195
+ 'issuer_cn': None,
196
+ 'self_signed': None,
197
+ 'trusted': None,
198
+ 'status': None,
199
+ 'keysize': None,
200
+ 'serial_number': None,
201
+ }
202
+ for cert in certs:
203
+ host = [k for k, v in host_to_ips.items() if ip in v][0]
204
+ id = cert['id']
205
+ finding = cert['finding']
206
+
207
+ if id.startswith('cert_crlDistributionPoints') and finding != '--':
208
+ # TODO not implemented, need to find a certificate that is revoked by CRL
209
+ cert_data['status'] = CERTIFICATE_STATUS_UNKNOWN
210
+
211
+ if id.startswith('cert_ocspRevoked'):
212
+ if finding.startswith('not revoked'):
213
+ cert_data['status'] = CERTIFICATE_STATUS_TRUSTED
214
+ else:
215
+ cert_data['status'] = CERTIFICATE_STATUS_REVOKED
216
+
217
+ if id.startswith('cert_fingerprintSHA256'):
218
+ cert_data['fingerprint_sha256'] = finding
219
+
220
+ if id.startswith('cert_commonName'):
221
+ cert_data['subject_cn'] = finding
222
+
223
+ if id.startswith('cert_subjectAltName'):
224
+ cert_data['subject_an'] = finding.split(" ")
225
+
226
+ if id.startswith('cert_notBefore'):
227
+ cert_data['not_before'] = datetime.strptime(finding, "%Y-%m-%d %H:%M")
228
+
229
+ if id.startswith('cert_notAfter'):
230
+ cert_data['not_after'] = datetime.strptime(finding, "%Y-%m-%d %H:%M")
231
+
232
+ if id.startswith('cert_caIssuers'):
233
+ cert_data['issuer_cn'] = finding
234
+
235
+ if id.startswith('cert_chain_of_trust'):
236
+ cert_data['self_signed'] = 'self signed' in finding
237
+
238
+ if id.startswith('cert_chain_of_trust'):
239
+ cert_data['trusted'] = finding.startswith('passed')
240
+
241
+ if id.startswith('cert_keySize'):
242
+ cert_data['keysize'] = int(finding.split(" ")[1])
243
+
244
+ if id.startswith('cert_serialNumber'):
245
+ cert_data['serial_number'] = finding
246
+
247
+ if id.startswith('cert ') and finding.startswith('-----BEGIN CERTIFICATE-----'):
248
+ cert_data['raw_value'] = finding
249
+
250
+ # For the following attributes commented, it's because at the time of writting it
251
+ # I did not found the value inside the result of testssl
252
+ cert = Certificate(
253
+ **cert_data
254
+ # issuer_dn='',
255
+ # issuer='',
256
+ # TODO: delete the ciphers attribute from certificate outputType
257
+ # ciphers=None,
258
+ # TODO: need to find a way to retrieve the parent certificate,
259
+ # parent_certificate=None,
260
+ )
261
+ yield cert
262
+ if cert.is_expired():
263
+ yield Vulnerability(
264
+ name='SSL certificate expired',
265
+ provider='testssl',
266
+ description='The SSL certificate is expired. This can easily lead to domain takeovers',
267
+ matched_at=host,
268
+ ip=ip,
269
+ tags=['ssl', 'tls'],
270
+ severity='medium',
271
+ confidence='high',
272
+ extra_data={
273
+ 'id': id,
274
+ 'expiration_date': Certificate.format_date(cert.not_after)
275
+ }
276
+ )