souleyez 2.43.26__py3-none-any.whl → 2.43.34__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 souleyez might be problematic. Click here for more details.

Files changed (358) hide show
  1. souleyez/__init__.py +1 -2
  2. souleyez/ai/__init__.py +21 -15
  3. souleyez/ai/action_mapper.py +249 -150
  4. souleyez/ai/chain_advisor.py +116 -100
  5. souleyez/ai/claude_provider.py +29 -28
  6. souleyez/ai/context_builder.py +80 -62
  7. souleyez/ai/executor.py +158 -117
  8. souleyez/ai/feedback_handler.py +136 -121
  9. souleyez/ai/llm_factory.py +27 -20
  10. souleyez/ai/llm_provider.py +4 -2
  11. souleyez/ai/ollama_provider.py +6 -9
  12. souleyez/ai/ollama_service.py +44 -37
  13. souleyez/ai/path_scorer.py +91 -76
  14. souleyez/ai/recommender.py +176 -144
  15. souleyez/ai/report_context.py +74 -73
  16. souleyez/ai/report_service.py +84 -66
  17. souleyez/ai/result_parser.py +222 -229
  18. souleyez/ai/safety.py +67 -44
  19. souleyez/auth/__init__.py +23 -22
  20. souleyez/auth/audit.py +36 -26
  21. souleyez/auth/engagement_access.py +65 -48
  22. souleyez/auth/permissions.py +14 -3
  23. souleyez/auth/session_manager.py +54 -37
  24. souleyez/auth/user_manager.py +109 -64
  25. souleyez/commands/audit.py +40 -43
  26. souleyez/commands/auth.py +35 -15
  27. souleyez/commands/deliverables.py +55 -50
  28. souleyez/commands/engagement.py +47 -28
  29. souleyez/commands/license.py +32 -23
  30. souleyez/commands/screenshots.py +36 -32
  31. souleyez/commands/user.py +82 -36
  32. souleyez/config.py +52 -44
  33. souleyez/core/credential_tester.py +87 -81
  34. souleyez/core/cve_mappings.py +179 -192
  35. souleyez/core/cve_matcher.py +162 -148
  36. souleyez/core/msf_auto_mapper.py +100 -83
  37. souleyez/core/msf_chain_engine.py +294 -256
  38. souleyez/core/msf_database.py +153 -70
  39. souleyez/core/msf_integration.py +679 -673
  40. souleyez/core/msf_rpc_client.py +40 -42
  41. souleyez/core/msf_rpc_manager.py +77 -79
  42. souleyez/core/msf_sync_manager.py +241 -181
  43. souleyez/core/network_utils.py +22 -15
  44. souleyez/core/parser_handler.py +34 -25
  45. souleyez/core/pending_chains.py +114 -63
  46. souleyez/core/templates.py +158 -107
  47. souleyez/core/tool_chaining.py +9526 -2879
  48. souleyez/core/version_utils.py +79 -94
  49. souleyez/core/vuln_correlation.py +136 -89
  50. souleyez/core/web_utils.py +33 -32
  51. souleyez/data/wordlists/ad_users.txt +378 -0
  52. souleyez/data/wordlists/api_endpoints_large.txt +769 -0
  53. souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
  54. souleyez/data/wordlists/lfi_payloads.txt +82 -0
  55. souleyez/data/wordlists/passwords_brute.txt +1548 -0
  56. souleyez/data/wordlists/passwords_crack.txt +2479 -0
  57. souleyez/data/wordlists/passwords_spray.txt +386 -0
  58. souleyez/data/wordlists/subdomains_large.txt +5057 -0
  59. souleyez/data/wordlists/usernames_common.txt +694 -0
  60. souleyez/data/wordlists/web_dirs_large.txt +4769 -0
  61. souleyez/detection/__init__.py +1 -1
  62. souleyez/detection/attack_signatures.py +12 -17
  63. souleyez/detection/mitre_mappings.py +61 -55
  64. souleyez/detection/validator.py +97 -86
  65. souleyez/devtools.py +23 -10
  66. souleyez/docs/README.md +4 -4
  67. souleyez/docs/api-reference/cli-commands.md +2 -2
  68. souleyez/docs/developer-guide/adding-new-tools.md +562 -0
  69. souleyez/docs/user-guide/auto-chaining.md +30 -8
  70. souleyez/docs/user-guide/getting-started.md +1 -1
  71. souleyez/docs/user-guide/installation.md +26 -3
  72. souleyez/docs/user-guide/metasploit-integration.md +2 -2
  73. souleyez/docs/user-guide/rbac.md +1 -1
  74. souleyez/docs/user-guide/scope-management.md +1 -1
  75. souleyez/docs/user-guide/siem-integration.md +1 -1
  76. souleyez/docs/user-guide/tools-reference.md +1 -8
  77. souleyez/docs/user-guide/worker-management.md +1 -1
  78. souleyez/engine/background.py +1239 -535
  79. souleyez/engine/base.py +4 -1
  80. souleyez/engine/job_status.py +17 -49
  81. souleyez/engine/log_sanitizer.py +103 -77
  82. souleyez/engine/manager.py +38 -7
  83. souleyez/engine/result_handler.py +2200 -1550
  84. souleyez/engine/worker_manager.py +50 -41
  85. souleyez/export/evidence_bundle.py +72 -62
  86. souleyez/feature_flags/features.py +16 -20
  87. souleyez/feature_flags.py +5 -9
  88. souleyez/handlers/__init__.py +11 -0
  89. souleyez/handlers/base.py +188 -0
  90. souleyez/handlers/bash_handler.py +277 -0
  91. souleyez/handlers/bloodhound_handler.py +243 -0
  92. souleyez/handlers/certipy_handler.py +311 -0
  93. souleyez/handlers/crackmapexec_handler.py +486 -0
  94. souleyez/handlers/dnsrecon_handler.py +344 -0
  95. souleyez/handlers/enum4linux_handler.py +400 -0
  96. souleyez/handlers/evil_winrm_handler.py +493 -0
  97. souleyez/handlers/ffuf_handler.py +815 -0
  98. souleyez/handlers/gobuster_handler.py +1114 -0
  99. souleyez/handlers/gpp_extract_handler.py +334 -0
  100. souleyez/handlers/hashcat_handler.py +444 -0
  101. souleyez/handlers/hydra_handler.py +563 -0
  102. souleyez/handlers/impacket_getuserspns_handler.py +343 -0
  103. souleyez/handlers/impacket_psexec_handler.py +222 -0
  104. souleyez/handlers/impacket_secretsdump_handler.py +426 -0
  105. souleyez/handlers/john_handler.py +286 -0
  106. souleyez/handlers/katana_handler.py +425 -0
  107. souleyez/handlers/kerbrute_handler.py +298 -0
  108. souleyez/handlers/ldapsearch_handler.py +636 -0
  109. souleyez/handlers/lfi_extract_handler.py +464 -0
  110. souleyez/handlers/msf_auxiliary_handler.py +408 -0
  111. souleyez/handlers/msf_exploit_handler.py +380 -0
  112. souleyez/handlers/nikto_handler.py +413 -0
  113. souleyez/handlers/nmap_handler.py +821 -0
  114. souleyez/handlers/nuclei_handler.py +359 -0
  115. souleyez/handlers/nxc_handler.py +371 -0
  116. souleyez/handlers/rdp_sec_check_handler.py +353 -0
  117. souleyez/handlers/registry.py +292 -0
  118. souleyez/handlers/responder_handler.py +232 -0
  119. souleyez/handlers/service_explorer_handler.py +434 -0
  120. souleyez/handlers/smbclient_handler.py +344 -0
  121. souleyez/handlers/smbmap_handler.py +510 -0
  122. souleyez/handlers/smbpasswd_handler.py +296 -0
  123. souleyez/handlers/sqlmap_handler.py +1116 -0
  124. souleyez/handlers/theharvester_handler.py +601 -0
  125. souleyez/handlers/web_login_test_handler.py +327 -0
  126. souleyez/handlers/whois_handler.py +277 -0
  127. souleyez/handlers/wpscan_handler.py +554 -0
  128. souleyez/history.py +32 -16
  129. souleyez/importers/msf_importer.py +106 -75
  130. souleyez/importers/smart_importer.py +208 -147
  131. souleyez/integrations/siem/__init__.py +10 -10
  132. souleyez/integrations/siem/base.py +17 -18
  133. souleyez/integrations/siem/elastic.py +108 -122
  134. souleyez/integrations/siem/factory.py +207 -80
  135. souleyez/integrations/siem/googlesecops.py +146 -154
  136. souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
  137. souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
  138. souleyez/integrations/siem/sentinel.py +107 -109
  139. souleyez/integrations/siem/splunk.py +246 -212
  140. souleyez/integrations/siem/wazuh.py +65 -71
  141. souleyez/integrations/wazuh/__init__.py +5 -5
  142. souleyez/integrations/wazuh/client.py +70 -93
  143. souleyez/integrations/wazuh/config.py +85 -57
  144. souleyez/integrations/wazuh/host_mapper.py +28 -36
  145. souleyez/integrations/wazuh/sync.py +78 -68
  146. souleyez/intelligence/__init__.py +4 -5
  147. souleyez/intelligence/correlation_analyzer.py +309 -295
  148. souleyez/intelligence/exploit_knowledge.py +661 -623
  149. souleyez/intelligence/exploit_suggestions.py +159 -139
  150. souleyez/intelligence/gap_analyzer.py +132 -97
  151. souleyez/intelligence/gap_detector.py +251 -214
  152. souleyez/intelligence/sensitive_tables.py +266 -129
  153. souleyez/intelligence/service_parser.py +137 -123
  154. souleyez/intelligence/surface_analyzer.py +407 -268
  155. souleyez/intelligence/target_parser.py +159 -162
  156. souleyez/licensing/__init__.py +6 -6
  157. souleyez/licensing/validator.py +17 -19
  158. souleyez/log_config.py +79 -54
  159. souleyez/main.py +1505 -687
  160. souleyez/migrations/fix_job_counter.py +16 -14
  161. souleyez/parsers/bloodhound_parser.py +41 -39
  162. souleyez/parsers/crackmapexec_parser.py +178 -111
  163. souleyez/parsers/dalfox_parser.py +72 -77
  164. souleyez/parsers/dnsrecon_parser.py +103 -91
  165. souleyez/parsers/enum4linux_parser.py +183 -153
  166. souleyez/parsers/ffuf_parser.py +29 -25
  167. souleyez/parsers/gobuster_parser.py +301 -41
  168. souleyez/parsers/hashcat_parser.py +324 -79
  169. souleyez/parsers/http_fingerprint_parser.py +350 -103
  170. souleyez/parsers/hydra_parser.py +131 -111
  171. souleyez/parsers/impacket_parser.py +231 -178
  172. souleyez/parsers/john_parser.py +98 -86
  173. souleyez/parsers/katana_parser.py +316 -0
  174. souleyez/parsers/msf_parser.py +943 -498
  175. souleyez/parsers/nikto_parser.py +346 -65
  176. souleyez/parsers/nmap_parser.py +262 -174
  177. souleyez/parsers/nuclei_parser.py +40 -44
  178. souleyez/parsers/responder_parser.py +26 -26
  179. souleyez/parsers/searchsploit_parser.py +74 -74
  180. souleyez/parsers/service_explorer_parser.py +279 -0
  181. souleyez/parsers/smbmap_parser.py +180 -124
  182. souleyez/parsers/sqlmap_parser.py +434 -308
  183. souleyez/parsers/theharvester_parser.py +75 -57
  184. souleyez/parsers/whois_parser.py +135 -94
  185. souleyez/parsers/wpscan_parser.py +278 -190
  186. souleyez/plugins/afp.py +44 -36
  187. souleyez/plugins/afp_brute.py +114 -46
  188. souleyez/plugins/ard.py +48 -37
  189. souleyez/plugins/bloodhound.py +95 -61
  190. souleyez/plugins/certipy.py +303 -0
  191. souleyez/plugins/crackmapexec.py +186 -85
  192. souleyez/plugins/dalfox.py +120 -59
  193. souleyez/plugins/dns_hijack.py +146 -41
  194. souleyez/plugins/dnsrecon.py +97 -61
  195. souleyez/plugins/enum4linux.py +91 -66
  196. souleyez/plugins/evil_winrm.py +291 -0
  197. souleyez/plugins/ffuf.py +166 -90
  198. souleyez/plugins/firmware_extract.py +133 -29
  199. souleyez/plugins/gobuster.py +387 -190
  200. souleyez/plugins/gpp_extract.py +393 -0
  201. souleyez/plugins/hashcat.py +100 -73
  202. souleyez/plugins/http_fingerprint.py +854 -267
  203. souleyez/plugins/hydra.py +566 -200
  204. souleyez/plugins/impacket_getnpusers.py +117 -69
  205. souleyez/plugins/impacket_psexec.py +84 -64
  206. souleyez/plugins/impacket_secretsdump.py +103 -69
  207. souleyez/plugins/impacket_smbclient.py +89 -75
  208. souleyez/plugins/john.py +86 -69
  209. souleyez/plugins/katana.py +313 -0
  210. souleyez/plugins/kerbrute.py +237 -0
  211. souleyez/plugins/lfi_extract.py +541 -0
  212. souleyez/plugins/macos_ssh.py +117 -48
  213. souleyez/plugins/mdns.py +35 -30
  214. souleyez/plugins/msf_auxiliary.py +253 -130
  215. souleyez/plugins/msf_exploit.py +239 -161
  216. souleyez/plugins/nikto.py +134 -78
  217. souleyez/plugins/nmap.py +275 -91
  218. souleyez/plugins/nuclei.py +180 -89
  219. souleyez/plugins/nxc.py +285 -0
  220. souleyez/plugins/plugin_base.py +35 -36
  221. souleyez/plugins/plugin_template.py +13 -5
  222. souleyez/plugins/rdp_sec_check.py +130 -0
  223. souleyez/plugins/responder.py +112 -71
  224. souleyez/plugins/router_http_brute.py +76 -65
  225. souleyez/plugins/router_ssh_brute.py +118 -41
  226. souleyez/plugins/router_telnet_brute.py +124 -42
  227. souleyez/plugins/routersploit.py +91 -59
  228. souleyez/plugins/routersploit_exploit.py +77 -55
  229. souleyez/plugins/searchsploit.py +91 -77
  230. souleyez/plugins/service_explorer.py +1160 -0
  231. souleyez/plugins/smbmap.py +122 -72
  232. souleyez/plugins/smbpasswd.py +215 -0
  233. souleyez/plugins/sqlmap.py +301 -113
  234. souleyez/plugins/theharvester.py +127 -75
  235. souleyez/plugins/tr069.py +79 -57
  236. souleyez/plugins/upnp.py +65 -47
  237. souleyez/plugins/upnp_abuse.py +73 -55
  238. souleyez/plugins/vnc_access.py +129 -42
  239. souleyez/plugins/vnc_brute.py +109 -38
  240. souleyez/plugins/web_login_test.py +417 -0
  241. souleyez/plugins/whois.py +77 -58
  242. souleyez/plugins/wpscan.py +173 -69
  243. souleyez/reporting/__init__.py +2 -1
  244. souleyez/reporting/attack_chain.py +411 -346
  245. souleyez/reporting/charts.py +436 -501
  246. souleyez/reporting/compliance_mappings.py +334 -201
  247. souleyez/reporting/detection_report.py +126 -125
  248. souleyez/reporting/formatters.py +828 -591
  249. souleyez/reporting/generator.py +386 -302
  250. souleyez/reporting/metrics.py +72 -75
  251. souleyez/scanner.py +35 -29
  252. souleyez/security/__init__.py +37 -11
  253. souleyez/security/scope_validator.py +175 -106
  254. souleyez/security/validation.py +223 -149
  255. souleyez/security.py +22 -6
  256. souleyez/storage/credentials.py +247 -186
  257. souleyez/storage/crypto.py +296 -129
  258. souleyez/storage/database.py +73 -50
  259. souleyez/storage/db.py +58 -36
  260. souleyez/storage/deliverable_evidence.py +177 -128
  261. souleyez/storage/deliverable_exporter.py +282 -246
  262. souleyez/storage/deliverable_templates.py +134 -116
  263. souleyez/storage/deliverables.py +135 -130
  264. souleyez/storage/engagements.py +109 -56
  265. souleyez/storage/evidence.py +181 -152
  266. souleyez/storage/execution_log.py +31 -17
  267. souleyez/storage/exploit_attempts.py +93 -57
  268. souleyez/storage/exploits.py +67 -36
  269. souleyez/storage/findings.py +48 -61
  270. souleyez/storage/hosts.py +176 -144
  271. souleyez/storage/migrate_to_engagements.py +43 -19
  272. souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
  273. souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
  274. souleyez/storage/migrations/_003_add_execution_log.py +14 -8
  275. souleyez/storage/migrations/_005_screenshots.py +13 -5
  276. souleyez/storage/migrations/_006_deliverables.py +13 -5
  277. souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
  278. souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
  279. souleyez/storage/migrations/_010_evidence_linking.py +17 -10
  280. souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
  281. souleyez/storage/migrations/_012_team_collaboration.py +34 -21
  282. souleyez/storage/migrations/_013_add_host_tags.py +12 -6
  283. souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
  284. souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
  285. souleyez/storage/migrations/_016_add_domain_field.py +10 -4
  286. souleyez/storage/migrations/_017_msf_sessions.py +16 -8
  287. souleyez/storage/migrations/_018_add_osint_target.py +10 -6
  288. souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
  289. souleyez/storage/migrations/_020_add_rbac.py +36 -15
  290. souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
  291. souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
  292. souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
  293. souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
  294. souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
  295. souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
  296. souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
  297. souleyez/storage/migrations/__init__.py +26 -26
  298. souleyez/storage/migrations/migration_manager.py +19 -19
  299. souleyez/storage/msf_sessions.py +100 -65
  300. souleyez/storage/osint.py +17 -24
  301. souleyez/storage/recommendation_engine.py +269 -235
  302. souleyez/storage/screenshots.py +33 -32
  303. souleyez/storage/smb_shares.py +136 -92
  304. souleyez/storage/sqlmap_data.py +183 -128
  305. souleyez/storage/team_collaboration.py +135 -141
  306. souleyez/storage/timeline_tracker.py +122 -94
  307. souleyez/storage/wazuh_vulns.py +64 -66
  308. souleyez/storage/web_paths.py +33 -37
  309. souleyez/testing/credential_tester.py +221 -205
  310. souleyez/ui/__init__.py +1 -1
  311. souleyez/ui/ai_quotes.py +12 -12
  312. souleyez/ui/attack_surface.py +2439 -1516
  313. souleyez/ui/chain_rules_view.py +914 -382
  314. souleyez/ui/correlation_view.py +312 -230
  315. souleyez/ui/dashboard.py +2382 -1130
  316. souleyez/ui/deliverables_view.py +148 -62
  317. souleyez/ui/design_system.py +13 -13
  318. souleyez/ui/errors.py +49 -49
  319. souleyez/ui/evidence_linking_view.py +284 -179
  320. souleyez/ui/evidence_vault.py +393 -285
  321. souleyez/ui/exploit_suggestions_view.py +555 -349
  322. souleyez/ui/export_view.py +100 -66
  323. souleyez/ui/gap_analysis_view.py +315 -171
  324. souleyez/ui/help_system.py +105 -97
  325. souleyez/ui/intelligence_view.py +436 -293
  326. souleyez/ui/interactive.py +23434 -10286
  327. souleyez/ui/interactive_selector.py +75 -68
  328. souleyez/ui/log_formatter.py +47 -39
  329. souleyez/ui/menu_components.py +22 -13
  330. souleyez/ui/msf_auxiliary_menu.py +184 -133
  331. souleyez/ui/pending_chains_view.py +336 -172
  332. souleyez/ui/progress_indicators.py +5 -3
  333. souleyez/ui/recommendations_view.py +195 -137
  334. souleyez/ui/rule_builder.py +343 -225
  335. souleyez/ui/setup_wizard.py +678 -284
  336. souleyez/ui/shortcuts.py +217 -165
  337. souleyez/ui/splunk_gap_analysis_view.py +452 -270
  338. souleyez/ui/splunk_vulns_view.py +139 -86
  339. souleyez/ui/team_dashboard.py +498 -335
  340. souleyez/ui/template_selector.py +196 -105
  341. souleyez/ui/terminal.py +6 -6
  342. souleyez/ui/timeline_view.py +198 -127
  343. souleyez/ui/tool_setup.py +264 -164
  344. souleyez/ui/tutorial.py +202 -72
  345. souleyez/ui/tutorial_state.py +40 -40
  346. souleyez/ui/wazuh_vulns_view.py +235 -141
  347. souleyez/ui/wordlist_browser.py +260 -107
  348. souleyez/ui.py +464 -312
  349. souleyez/utils/tool_checker.py +427 -367
  350. souleyez/utils.py +33 -29
  351. souleyez/wordlists.py +134 -167
  352. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/METADATA +1 -1
  353. souleyez-2.43.34.dist-info/RECORD +443 -0
  354. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
  355. souleyez-2.43.26.dist-info/RECORD +0 -379
  356. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
  357. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
  358. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/top_level.txt +0 -0
@@ -12,7 +12,7 @@ logger = logging.getLogger(__name__)
12
12
  class ActionMapper:
13
13
  """
14
14
  Maps AI recommendations to executable commands.
15
-
15
+
16
16
  Handles translation of natural language actions into
17
17
  concrete shell commands.
18
18
  """
@@ -20,12 +20,15 @@ class ActionMapper:
20
20
  def __init__(self):
21
21
  """Initialize action mapper."""
22
22
  from ..storage.credentials import CredentialsManager
23
+
23
24
  self.creds_mgr = CredentialsManager()
24
25
 
25
- def map_to_command(self, recommendation: Dict[str, Any], engagement_id: Optional[int] = None) -> Optional[str]:
26
+ def map_to_command(
27
+ self, recommendation: Dict[str, Any], engagement_id: Optional[int] = None
28
+ ) -> Optional[str]:
26
29
  """
27
30
  Map AI recommendation to executable command.
28
-
31
+
29
32
  Args:
30
33
  recommendation: Dict with keys:
31
34
  - action: Human-readable action
@@ -34,150 +37,203 @@ class ActionMapper:
34
37
  - expected_outcome: Expected result
35
38
  - risk_level: Risk level
36
39
  engagement_id: Optional engagement ID for credential lookup
37
-
40
+
38
41
  Returns:
39
42
  Shell command string or None if can't map
40
43
  """
41
- action = recommendation.get('action', '').lower()
42
- target = recommendation.get('target', '')
43
- rationale = recommendation.get('rationale', '').lower()
44
-
44
+ action = recommendation.get("action", "").lower()
45
+ target = recommendation.get("target", "")
46
+ rationale = recommendation.get("rationale", "").lower()
47
+
45
48
  # Extract IP and port from target
46
- ip_match = re.search(r'\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b', target)
47
- port_match = re.search(r':(\d+)', target)
48
-
49
+ ip_match = re.search(r"\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b", target)
50
+ port_match = re.search(r":(\d+)", target)
51
+
49
52
  ip = ip_match.group(1) if ip_match else None
50
53
  port = port_match.group(1) if port_match else None
51
-
54
+
52
55
  if not ip:
53
56
  logger.warning(f"Could not extract IP from target: {target}")
54
57
  return None
55
-
58
+
56
59
  # PostgreSQL credential testing (check before MySQL)
57
- if ('postgres' in action or 'postgresql' in action or 'psql' in action) and \
58
- ('test' in action or 'login' in action or 'credential' in action):
59
- return self._map_postgresql_test(ip, port or '5432', recommendation, engagement_id)
60
-
60
+ if ("postgres" in action or "postgresql" in action or "psql" in action) and (
61
+ "test" in action or "login" in action or "credential" in action
62
+ ):
63
+ return self._map_postgresql_test(
64
+ ip, port or "5432", recommendation, engagement_id
65
+ )
66
+
61
67
  # MySQL credential testing (check before SSH)
62
- if 'mysql' in action and ('test' in action or 'login' in action or 'credential' in action):
63
- return self._map_mysql_test(ip, port or '3306', recommendation, engagement_id)
64
-
68
+ if "mysql" in action and (
69
+ "test" in action or "login" in action or "credential" in action
70
+ ):
71
+ return self._map_mysql_test(
72
+ ip, port or "3306", recommendation, engagement_id
73
+ )
74
+
65
75
  # MySQL enumeration (expanded patterns - check before SSH)
66
- if 'mysql' in action and ('enum' in action or 'database' in action or 'data' in action):
67
- return self._map_mysql_enum(ip, port or '3306', recommendation, engagement_id)
68
-
76
+ if "mysql" in action and (
77
+ "enum" in action or "database" in action or "data" in action
78
+ ):
79
+ return self._map_mysql_enum(
80
+ ip, port or "3306", recommendation, engagement_id
81
+ )
82
+
69
83
  # SMB credential testing
70
- if ('smb' in action or 'cifs' in action or 'smb' in target.lower() or
71
- ('authenticate' in action or 'login' in action or 'credential' in action) and
72
- ('445' in target or '139' in target or 'smb' in rationale)):
73
- return self._map_smb_test(ip, port or '445', recommendation, engagement_id)
74
-
84
+ if (
85
+ "smb" in action
86
+ or "cifs" in action
87
+ or "smb" in target.lower()
88
+ or ("authenticate" in action or "login" in action or "credential" in action)
89
+ and ("445" in target or "139" in target or "smb" in rationale)
90
+ ):
91
+ return self._map_smb_test(ip, port or "445", recommendation, engagement_id)
92
+
75
93
  # RDP credential testing
76
- if ('rdp' in action or 'rdp' in target.lower() or 'remote desktop' in action or
77
- ('authenticate' in action or 'login' in action or 'credential' in action) and
78
- ('3389' in target or 'rdp' in rationale)):
79
- return self._map_rdp_test(ip, port or '3389', recommendation, engagement_id)
80
-
94
+ if (
95
+ "rdp" in action
96
+ or "rdp" in target.lower()
97
+ or "remote desktop" in action
98
+ or ("authenticate" in action or "login" in action or "credential" in action)
99
+ and ("3389" in target or "rdp" in rationale)
100
+ ):
101
+ return self._map_rdp_test(ip, port or "3389", recommendation, engagement_id)
102
+
81
103
  # FTP credential testing
82
- if ('ftp' in action or 'ftp' in target.lower() or
83
- ('authenticate' in action or 'login' in action or 'credential' in action) and
84
- ('21' in target or 'ftp' in rationale)):
85
- return self._map_ftp_test(ip, port or '21', recommendation, engagement_id)
86
-
104
+ if (
105
+ "ftp" in action
106
+ or "ftp" in target.lower()
107
+ or ("authenticate" in action or "login" in action or "credential" in action)
108
+ and ("21" in target or "ftp" in rationale)
109
+ ):
110
+ return self._map_ftp_test(ip, port or "21", recommendation, engagement_id)
111
+
87
112
  # SSH credential testing (expanded patterns)
88
- if ('ssh' in action or 'ssh' in target.lower() or
89
- ('authenticate' in action or 'login' in action or 'credential' in action) and
90
- ('22' in target or 'ssh' in rationale or not port)):
91
- return self._map_ssh_test(ip, port or '22', recommendation, engagement_id)
92
-
113
+ if (
114
+ "ssh" in action
115
+ or "ssh" in target.lower()
116
+ or ("authenticate" in action or "login" in action or "credential" in action)
117
+ and ("22" in target or "ssh" in rationale or not port)
118
+ ):
119
+ return self._map_ssh_test(ip, port or "22", recommendation, engagement_id)
120
+
93
121
  # HTTP/Web enumeration (BEFORE generic nmap scan)
94
- if ('http' in action or 'web' in action or 'directory' in action or 'path' in action or
95
- 'http' in target.lower() or 'web' in target.lower() or
96
- ('enumerate' in action or 'enum' in action) and
97
- ('http' in rationale or 'web' in rationale or port in ['80', '443', '8080', '8443'])):
122
+ if (
123
+ "http" in action
124
+ or "web" in action
125
+ or "directory" in action
126
+ or "path" in action
127
+ or "http" in target.lower()
128
+ or "web" in target.lower()
129
+ or ("enumerate" in action or "enum" in action)
130
+ and (
131
+ "http" in rationale
132
+ or "web" in rationale
133
+ or port in ["80", "443", "8080", "8443"]
134
+ )
135
+ ):
98
136
  return self._map_http_enum(ip, port, recommendation)
99
-
137
+
100
138
  # Nmap port scan (keep as fallback, but more specific)
101
- if ('nmap' in action or 'port scan' in action or
102
- ('scan' in action and 'port' in action) or
103
- ('discover' in action and 'port' in action)):
139
+ if (
140
+ "nmap" in action
141
+ or "port scan" in action
142
+ or ("scan" in action and "port" in action)
143
+ or ("discover" in action and "port" in action)
144
+ ):
104
145
  return self._map_nmap_scan(ip, recommendation)
105
-
146
+
106
147
  # Service enumeration
107
- if 'enumerate' in action and 'service' in action:
148
+ if "enumerate" in action and "service" in action:
108
149
  return self._map_service_enum(ip, port, recommendation)
109
-
150
+
110
151
  logger.warning(f"No command mapping found for action: {action}")
111
152
  return None
112
153
 
113
- def _find_credentials(self, engagement_id: Optional[int], service: str, ip: Optional[str] = None) -> Optional[tuple]:
154
+ def _find_credentials(
155
+ self, engagement_id: Optional[int], service: str, ip: Optional[str] = None
156
+ ) -> Optional[tuple]:
114
157
  """
115
158
  Find valid credentials from database.
116
-
159
+
117
160
  Args:
118
161
  engagement_id: Engagement ID
119
162
  service: Service name (ssh, mysql, etc.)
120
163
  ip: Optional IP to filter by
121
-
164
+
122
165
  Returns:
123
166
  Tuple of (username, password) or None
124
167
  """
125
168
  if not engagement_id:
126
169
  return None
127
-
170
+
128
171
  try:
129
172
  creds = self.creds_mgr.list_credentials(engagement_id)
130
-
173
+
131
174
  # Look for valid credentials for this service
132
175
  for cred in creds:
133
- cred_service = cred.get('service', '').lower()
134
- cred_status = cred.get('status', 'untested')
135
-
176
+ cred_service = cred.get("service", "").lower()
177
+ cred_status = cred.get("status", "untested")
178
+
136
179
  if service.lower() in cred_service or cred_service in service.lower():
137
180
  # Prefer valid credentials, but fall back to untested
138
- if cred_status == 'valid':
139
- return (cred.get('username'), cred.get('password'))
140
-
181
+ if cred_status == "valid":
182
+ return (cred.get("username"), cred.get("password"))
183
+
141
184
  # If no valid creds, try untested for same service
142
185
  for cred in creds:
143
- cred_service = cred.get('service', '').lower()
144
- cred_status = cred.get('status', 'untested')
145
-
146
- if (service.lower() in cred_service or cred_service in service.lower()) and cred_status == 'untested':
147
- return (cred.get('username'), cred.get('password'))
148
-
186
+ cred_service = cred.get("service", "").lower()
187
+ cred_status = cred.get("status", "untested")
188
+
189
+ if (
190
+ service.lower() in cred_service or cred_service in service.lower()
191
+ ) and cred_status == "untested":
192
+ return (cred.get("username"), cred.get("password"))
193
+
149
194
  # If still nothing and service is mysql, try SSH credentials (common on same host)
150
- if service.lower() == 'mysql':
195
+ if service.lower() == "mysql":
151
196
  for cred in creds:
152
- if cred.get('service', '').lower() == 'ssh' and cred.get('status') == 'valid':
153
- logger.info("Using SSH credentials for MySQL (common username on same host)")
154
- return (cred.get('username'), cred.get('password'))
155
-
197
+ if (
198
+ cred.get("service", "").lower() == "ssh"
199
+ and cred.get("status") == "valid"
200
+ ):
201
+ logger.info(
202
+ "Using SSH credentials for MySQL (common username on same host)"
203
+ )
204
+ return (cred.get("username"), cred.get("password"))
205
+
156
206
  except Exception as e:
157
207
  logger.error(f"Failed to lookup credentials: {e}")
158
-
208
+
159
209
  return None
160
210
 
161
- def _map_ssh_test(self, ip: str, port: str, rec: Dict[str, Any], engagement_id: Optional[int] = None) -> Optional[str]:
211
+ def _map_ssh_test(
212
+ self,
213
+ ip: str,
214
+ port: str,
215
+ rec: Dict[str, Any],
216
+ engagement_id: Optional[int] = None,
217
+ ) -> Optional[str]:
162
218
  """Map SSH credential testing to command."""
163
219
  # Try to extract credentials from action or rationale
164
- action = rec.get('action', '')
165
- rationale = rec.get('rationale', '')
220
+ action = rec.get("action", "")
221
+ rationale = rec.get("rationale", "")
166
222
  text = f"{action} {rationale}".lower()
167
-
223
+
168
224
  # Look for credential patterns
169
- cred_match = re.search(r'(\w+):(\w+)', text)
225
+ cred_match = re.search(r"(\w+):(\w+)", text)
170
226
  if cred_match:
171
227
  username = cred_match.group(1)
172
228
  password = cred_match.group(2)
173
229
  else:
174
230
  # Try to find credentials from database
175
- creds = self._find_credentials(engagement_id, 'ssh', ip)
231
+ creds = self._find_credentials(engagement_id, "ssh", ip)
176
232
  if not creds:
177
233
  logger.warning("SSH test requested but no credentials found")
178
234
  return None
179
235
  username, password = creds
180
-
236
+
181
237
  return (
182
238
  f"sshpass -p '{password}' ssh -o ConnectTimeout=5 "
183
239
  f"-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "
@@ -185,20 +241,26 @@ class ActionMapper:
185
241
  f"-p {port} {username}@{ip} 'echo SSH_SUCCESS && whoami && id'"
186
242
  )
187
243
 
188
- def _map_ftp_test(self, ip: str, port: str, rec: Dict[str, Any], engagement_id: Optional[int] = None) -> Optional[str]:
244
+ def _map_ftp_test(
245
+ self,
246
+ ip: str,
247
+ port: str,
248
+ rec: Dict[str, Any],
249
+ engagement_id: Optional[int] = None,
250
+ ) -> Optional[str]:
189
251
  """Map FTP credential testing to command."""
190
- action = rec.get('action', '')
191
- rationale = rec.get('rationale', '')
252
+ action = rec.get("action", "")
253
+ rationale = rec.get("rationale", "")
192
254
  text = f"{action} {rationale}".lower()
193
255
 
194
256
  # Look for credential patterns
195
- cred_match = re.search(r'(\w+):(\w+)', text)
257
+ cred_match = re.search(r"(\w+):(\w+)", text)
196
258
  if cred_match:
197
259
  username = cred_match.group(1)
198
260
  password = cred_match.group(2)
199
261
  else:
200
262
  # Try to find credentials from database
201
- creds = self._find_credentials(engagement_id, 'ftp', ip)
263
+ creds = self._find_credentials(engagement_id, "ftp", ip)
202
264
  if not creds:
203
265
  logger.warning("FTP test requested but no credentials found")
204
266
  return None
@@ -210,20 +272,26 @@ class ActionMapper:
210
272
  f"echo 'FTP_FAILED'"
211
273
  )
212
274
 
213
- def _map_smb_test(self, ip: str, port: str, rec: Dict[str, Any], engagement_id: Optional[int] = None) -> Optional[str]:
275
+ def _map_smb_test(
276
+ self,
277
+ ip: str,
278
+ port: str,
279
+ rec: Dict[str, Any],
280
+ engagement_id: Optional[int] = None,
281
+ ) -> Optional[str]:
214
282
  """Map SMB credential testing to command."""
215
- action = rec.get('action', '')
216
- rationale = rec.get('rationale', '')
283
+ action = rec.get("action", "")
284
+ rationale = rec.get("rationale", "")
217
285
  text = f"{action} {rationale}".lower()
218
286
 
219
287
  # Look for credential patterns
220
- cred_match = re.search(r'(\w+):(\w+)', text)
288
+ cred_match = re.search(r"(\w+):(\w+)", text)
221
289
  if cred_match:
222
290
  username = cred_match.group(1)
223
291
  password = cred_match.group(2)
224
292
  else:
225
293
  # Try to find credentials from database
226
- creds = self._find_credentials(engagement_id, 'smb', ip)
294
+ creds = self._find_credentials(engagement_id, "smb", ip)
227
295
  if not creds:
228
296
  logger.warning("SMB test requested but no credentials found")
229
297
  return None
@@ -235,20 +303,26 @@ class ActionMapper:
235
303
  f"-N 2>&1 || echo 'SMB_FAILED'"
236
304
  )
237
305
 
238
- def _map_rdp_test(self, ip: str, port: str, rec: Dict[str, Any], engagement_id: Optional[int] = None) -> Optional[str]:
306
+ def _map_rdp_test(
307
+ self,
308
+ ip: str,
309
+ port: str,
310
+ rec: Dict[str, Any],
311
+ engagement_id: Optional[int] = None,
312
+ ) -> Optional[str]:
239
313
  """Map RDP credential testing to command."""
240
- action = rec.get('action', '')
241
- rationale = rec.get('rationale', '')
314
+ action = rec.get("action", "")
315
+ rationale = rec.get("rationale", "")
242
316
  text = f"{action} {rationale}".lower()
243
317
 
244
318
  # Look for credential patterns
245
- cred_match = re.search(r'(\w+):(\w+)', text)
319
+ cred_match = re.search(r"(\w+):(\w+)", text)
246
320
  if cred_match:
247
321
  username = cred_match.group(1)
248
322
  password = cred_match.group(2)
249
323
  else:
250
324
  # Try to find credentials from database
251
- creds = self._find_credentials(engagement_id, 'rdp', ip)
325
+ creds = self._find_credentials(engagement_id, "rdp", ip)
252
326
  if not creds:
253
327
  logger.warning("RDP test requested but no credentials found")
254
328
  return None
@@ -260,73 +334,91 @@ class ActionMapper:
260
334
  f"/cert-ignore +auth-only /sec:nla 2>&1 || echo 'RDP_FAILED'"
261
335
  )
262
336
 
263
- def _map_mysql_test(self, ip: str, port: str, rec: Dict[str, Any], engagement_id: Optional[int] = None) -> Optional[str]:
337
+ def _map_mysql_test(
338
+ self,
339
+ ip: str,
340
+ port: str,
341
+ rec: Dict[str, Any],
342
+ engagement_id: Optional[int] = None,
343
+ ) -> Optional[str]:
264
344
  """Map MySQL credential testing to command."""
265
- action = rec.get('action', '')
266
- rationale = rec.get('rationale', '')
345
+ action = rec.get("action", "")
346
+ rationale = rec.get("rationale", "")
267
347
  text = f"{action} {rationale}".lower()
268
-
348
+
269
349
  # Look for credential patterns
270
- cred_match = re.search(r'(\w+):(\w+)', text)
350
+ cred_match = re.search(r"(\w+):(\w+)", text)
271
351
  if cred_match:
272
352
  username = cred_match.group(1)
273
353
  password = cred_match.group(2)
274
354
  else:
275
355
  # Try to find credentials from database
276
- creds = self._find_credentials(engagement_id, 'mysql', ip)
356
+ creds = self._find_credentials(engagement_id, "mysql", ip)
277
357
  if not creds:
278
358
  logger.warning("MySQL test requested but no credentials found")
279
359
  return None
280
360
  username, password = creds
281
-
361
+
282
362
  return (
283
363
  f"mysql -h {ip} -P {port} -u {username} -p'{password}' "
284
364
  f"--skip-ssl "
285
365
  f"-e 'SELECT version();' 2>&1"
286
366
  )
287
367
 
288
- def _map_mysql_enum(self, ip: str, port: str, rec: Dict[str, Any], engagement_id: Optional[int] = None) -> Optional[str]:
368
+ def _map_mysql_enum(
369
+ self,
370
+ ip: str,
371
+ port: str,
372
+ rec: Dict[str, Any],
373
+ engagement_id: Optional[int] = None,
374
+ ) -> Optional[str]:
289
375
  """Map MySQL enumeration to command."""
290
- action = rec.get('action', '')
291
- rationale = rec.get('rationale', '')
376
+ action = rec.get("action", "")
377
+ rationale = rec.get("rationale", "")
292
378
  text = f"{action} {rationale}".lower()
293
-
379
+
294
380
  # Look for credentials
295
- cred_match = re.search(r'(\w+):(\w+)', text)
381
+ cred_match = re.search(r"(\w+):(\w+)", text)
296
382
  if cred_match:
297
383
  username = cred_match.group(1)
298
384
  password = cred_match.group(2)
299
385
  else:
300
386
  # Try to find credentials from database
301
- creds = self._find_credentials(engagement_id, 'mysql', ip)
387
+ creds = self._find_credentials(engagement_id, "mysql", ip)
302
388
  if not creds:
303
389
  logger.warning("MySQL enumeration requested but no credentials found")
304
390
  return None
305
391
  username, password = creds
306
-
392
+
307
393
  return (
308
394
  f"mysql -h {ip} -P {port} -u {username} -p'{password}' "
309
395
  f"--skip-ssl "
310
396
  f"-e 'SHOW DATABASES; SELECT user,host FROM mysql.user;' 2>&1"
311
397
  )
312
398
 
313
- def _map_postgresql_test(self, ip: str, port: str, rec: Dict[str, Any], engagement_id: Optional[int] = None) -> Optional[str]:
399
+ def _map_postgresql_test(
400
+ self,
401
+ ip: str,
402
+ port: str,
403
+ rec: Dict[str, Any],
404
+ engagement_id: Optional[int] = None,
405
+ ) -> Optional[str]:
314
406
  """Map PostgreSQL credential testing to command."""
315
- action = rec.get('action', '')
316
- rationale = rec.get('rationale', '')
407
+ action = rec.get("action", "")
408
+ rationale = rec.get("rationale", "")
317
409
  text = f"{action} {rationale}".lower()
318
410
 
319
411
  # Look for credential patterns
320
- cred_match = re.search(r'(\w+):(\w+)', text)
412
+ cred_match = re.search(r"(\w+):(\w+)", text)
321
413
  if cred_match:
322
414
  username = cred_match.group(1)
323
415
  password = cred_match.group(2)
324
416
  else:
325
417
  # Try to find credentials from database
326
- creds = self._find_credentials(engagement_id, 'postgresql', ip)
418
+ creds = self._find_credentials(engagement_id, "postgresql", ip)
327
419
  if not creds:
328
420
  # Also try 'postgres' service name
329
- creds = self._find_credentials(engagement_id, 'postgres', ip)
421
+ creds = self._find_credentials(engagement_id, "postgres", ip)
330
422
  if not creds:
331
423
  logger.warning("PostgreSQL test requested but no credentials found")
332
424
  return None
@@ -339,12 +431,12 @@ class ActionMapper:
339
431
 
340
432
  def _map_nmap_scan(self, ip: str, rec: Dict[str, Any]) -> str:
341
433
  """Map nmap scanning to command."""
342
- action = rec.get('action', '').lower()
343
-
434
+ action = rec.get("action", "").lower()
435
+
344
436
  # Quick scan by default
345
- if 'full' in action or 'all' in action:
437
+ if "full" in action or "all" in action:
346
438
  return f"nmap -sV -p- {ip}"
347
- elif 'quick' in action or 'fast' in action:
439
+ elif "quick" in action or "fast" in action:
348
440
  return f"nmap -F {ip}"
349
441
  else:
350
442
  # Default: top 1000 ports with version detection
@@ -353,51 +445,58 @@ class ActionMapper:
353
445
  def _map_http_enum(self, ip: str, port: Optional[str], rec: Dict[str, Any]) -> str:
354
446
  """
355
447
  Map HTTP/web enumeration to appropriate tools.
356
-
448
+
357
449
  Uses gobuster for web content discovery and vulnerability identification.
358
450
  """
359
- action = rec.get('action', '').lower()
360
- target_info = rec.get('target', '').lower()
361
-
451
+ action = rec.get("action", "").lower()
452
+ target_info = rec.get("target", "").lower()
453
+
362
454
  # Determine port
363
455
  if not port:
364
456
  # Try to extract from target or default based on protocol
365
- if 'https' in target_info or '443' in target_info:
366
- port = '443'
457
+ if "https" in target_info or "443" in target_info:
458
+ port = "443"
367
459
  else:
368
- port = '80'
369
-
460
+ port = "80"
461
+
370
462
  # Build URL
371
- protocol = 'https' if port in ['443', '8443'] else 'http'
463
+ protocol = "https" if port in ["443", "8443"] else "http"
372
464
  url = f"{protocol}://{ip}:{port}"
373
-
465
+
374
466
  # Prefer gobuster for directory/path enumeration
375
- if 'directory' in action or 'path' in action or 'dir' in action or 'file' in action:
376
- ssl_flag = ' -k' if protocol == 'https' else ''
377
- return f"gobuster dir -u {url} -w /usr/share/wordlists/dirb/common.txt -t 10{ssl_flag}"
378
-
467
+ if (
468
+ "directory" in action
469
+ or "path" in action
470
+ or "dir" in action
471
+ or "file" in action
472
+ ):
473
+ ssl_flag = " -k" if protocol == "https" else ""
474
+ return f"gobuster dir -u {url} -w data/wordlists/web_dirs_common.txt -t 10{ssl_flag}"
475
+
379
476
  # Use gobuster for web vulnerability and content discovery
380
- elif 'vulnerab' in action or 'vuln' in action or 'scan' in action:
381
- ssl_flag = ' -k' if protocol == 'https' else ''
382
- return f"gobuster dir -u {url} -w /usr/share/wordlists/dirb/common.txt -t 10{ssl_flag}"
383
-
477
+ elif "vulnerab" in action or "vuln" in action or "scan" in action:
478
+ ssl_flag = " -k" if protocol == "https" else ""
479
+ return f"gobuster dir -u {url} -w data/wordlists/web_dirs_common.txt -t 10{ssl_flag}"
480
+
384
481
  # Default to gobuster for general HTTP enumeration
385
482
  else:
386
- ssl_flag = ' -k' if protocol == 'https' else ''
387
- return f"gobuster dir -u {url} -w /usr/share/wordlists/dirb/common.txt -t 10{ssl_flag}"
483
+ ssl_flag = " -k" if protocol == "https" else ""
484
+ return f"gobuster dir -u {url} -w data/wordlists/web_dirs_common.txt -t 10{ssl_flag}"
388
485
 
389
- def _map_service_enum(self, ip: str, port: Optional[str], rec: Dict[str, Any]) -> Optional[str]:
486
+ def _map_service_enum(
487
+ self, ip: str, port: Optional[str], rec: Dict[str, Any]
488
+ ) -> Optional[str]:
390
489
  """Map service enumeration to command."""
391
490
  if not port:
392
491
  return f"nmap -sV {ip}"
393
-
394
- service = rec.get('target', '').lower()
395
-
396
- if 'http' in service or port in ['80', '443', '8080']:
492
+
493
+ service = rec.get("target", "").lower()
494
+
495
+ if "http" in service or port in ["80", "443", "8080"]:
397
496
  return f"curl -I http://{ip}:{port}/ 2>&1"
398
- elif 'ssh' in service or port == '22':
497
+ elif "ssh" in service or port == "22":
399
498
  return f"ssh -V {ip} 2>&1 | head -1"
400
- elif 'mysql' in service or port == '3306':
499
+ elif "mysql" in service or port == "3306":
401
500
  return f"nmap -sV -p {port} {ip}"
402
501
  else:
403
502
  return f"nc -zv {ip} {port} 2>&1"