souleyez 2.43.29__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.
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 +22827 -10678
  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.29.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.29.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
  355. souleyez-2.43.29.dist-info/RECORD +0 -379
  356. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
  357. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
  358. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,821 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Nmap handler.
4
+
5
+ Consolidates parsing and display logic for nmap and ARD (which uses nmap) jobs.
6
+ """
7
+ import logging
8
+ import os
9
+ import re
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ import click
13
+
14
+ from souleyez.engine.job_status import STATUS_DONE, STATUS_ERROR, STATUS_NO_RESULTS
15
+ from souleyez.handlers.base import BaseToolHandler
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class NmapHandler(BaseToolHandler):
21
+ """Handler for nmap and nmap-based (ARD) jobs."""
22
+
23
+ tool_name = "nmap"
24
+ display_name = "Nmap"
25
+
26
+ # All handlers enabled
27
+ has_error_handler = True
28
+ has_warning_handler = True
29
+ has_no_results_handler = True
30
+ has_done_handler = True
31
+
32
+ # Risky ports that warrant security concerns
33
+ RISKY_PORTS = {
34
+ 21: ("FTP", "Cleartext file transfer - check for anonymous access"),
35
+ 23: ("Telnet", "Cleartext remote access - highly insecure"),
36
+ 25: ("SMTP", "Mail relay - check for open relay"),
37
+ 69: ("TFTP", "Trivial FTP - no authentication"),
38
+ 111: ("RPC", "Remote procedure call - can expose NFS/services"),
39
+ 135: ("MSRPC", "Windows RPC - often targeted"),
40
+ 139: ("NetBIOS", "Legacy Windows networking"),
41
+ 445: ("SMB", "File sharing - frequent attack target"),
42
+ 512: ("rexec", "Remote execution - cleartext"),
43
+ 513: ("rlogin", "Remote login - cleartext, no auth"),
44
+ 514: ("rsh", "Remote shell - cleartext, no auth"),
45
+ 1433: ("MSSQL", "Database exposed - should not be public"),
46
+ 1521: ("Oracle", "Database exposed - should not be public"),
47
+ 2049: ("NFS", "Network file system - check exports"),
48
+ 3306: ("MySQL", "Database exposed - should not be public"),
49
+ 3389: ("RDP", "Remote desktop - brute forceable"),
50
+ 5432: ("PostgreSQL", "Database exposed - should not be public"),
51
+ 5900: ("VNC", "Remote desktop - often weak auth"),
52
+ 5901: ("VNC", "Remote desktop - often weak auth"),
53
+ 6000: ("X11", "Remote display - unencrypted"),
54
+ 6379: ("Redis", "Database/cache - often no auth"),
55
+ 27017: ("MongoDB", "Database exposed - often no auth"),
56
+ }
57
+
58
+ def parse_job(
59
+ self,
60
+ engagement_id: int,
61
+ log_path: str,
62
+ job: Dict[str, Any],
63
+ host_manager: Optional[Any] = None,
64
+ findings_manager: Optional[Any] = None,
65
+ credentials_manager: Optional[Any] = None,
66
+ ) -> Dict[str, Any]:
67
+ """
68
+ Parse nmap job results.
69
+
70
+ Imports hosts/services into database and creates findings for CVEs.
71
+ """
72
+ try:
73
+ from souleyez.parsers.nmap_parser import parse_nmap_log
74
+ from souleyez.core.cve_matcher import CVEMatcher
75
+ from souleyez.engine.result_handler import detect_tool_error
76
+
77
+ # Import managers if not provided
78
+ if host_manager is None:
79
+ from souleyez.storage.hosts import HostManager
80
+
81
+ host_manager = HostManager()
82
+ if findings_manager is None:
83
+ from souleyez.storage.findings import FindingsManager
84
+
85
+ findings_manager = FindingsManager()
86
+
87
+ # Parse the log file
88
+ parsed = parse_nmap_log(log_path)
89
+
90
+ if "error" in parsed:
91
+ return {"error": parsed["error"]}
92
+
93
+ # Import into database
94
+ result = host_manager.import_nmap_results(engagement_id, parsed)
95
+ logger.info(
96
+ f"Nmap import: {result['hosts_added']} hosts, "
97
+ f"{result['services_added']} services in engagement {engagement_id}"
98
+ )
99
+ logger.debug(
100
+ f"Info scripts to process: {len(parsed.get('info_scripts', []))}"
101
+ )
102
+
103
+ # Check for CVEs and common issues
104
+ cve_matcher = CVEMatcher()
105
+ findings_added = 0
106
+
107
+ # First, store any script-detected vulnerabilities (from --script vuln)
108
+ findings_added += self._store_vulnerabilities(
109
+ parsed, engagement_id, host_manager, findings_manager
110
+ )
111
+
112
+ # Then check for inferred CVEs based on service versions
113
+ findings_added += self._check_service_cves(
114
+ parsed, engagement_id, host_manager, findings_manager, cve_matcher
115
+ )
116
+
117
+ # Store info script findings
118
+ findings_added += self._store_info_scripts(
119
+ parsed, engagement_id, host_manager, findings_manager
120
+ )
121
+
122
+ # Build host details list for summary
123
+ host_details = self._build_host_details(parsed)
124
+
125
+ # Determine scan type based on job args
126
+ args = job.get("args", [])
127
+ is_discovery = "-sn" in args or "--discovery" in args
128
+ is_full_scan = any(x in args for x in ["-sV", "-O", "-A", "-p1-65535"])
129
+
130
+ # Collect all services for tool chaining
131
+ all_services = self._collect_chainable_services(parsed)
132
+
133
+ # Check for nmap errors before determining status
134
+ with open(log_path, "r", encoding="utf-8", errors="replace") as f:
135
+ log_content = f.read()
136
+ nmap_error = detect_tool_error(log_content, "nmap")
137
+
138
+ # Determine status based on results
139
+ hosts_up = len(
140
+ [h for h in parsed.get("hosts", []) if h.get("status") == "up"]
141
+ )
142
+ if nmap_error:
143
+ status = STATUS_ERROR
144
+ elif hosts_up > 0:
145
+ status = STATUS_DONE
146
+ else:
147
+ status = STATUS_NO_RESULTS
148
+
149
+ # Build summary for job queue display
150
+ summary_parts = []
151
+ if hosts_up > 0:
152
+ summary_parts.append(f"{hosts_up} host(s) up")
153
+ if result["services_added"] > 0:
154
+ summary_parts.append(f"{result['services_added']} service(s)")
155
+ if findings_added > 0:
156
+ summary_parts.append(f"{findings_added} finding(s)")
157
+ summary = " | ".join(summary_parts) if summary_parts else "No hosts found"
158
+
159
+ return {
160
+ "tool": "nmap",
161
+ "status": status,
162
+ "summary": summary,
163
+ "hosts_added": result["hosts_added"],
164
+ "services_added": result["services_added"],
165
+ "findings_added": findings_added,
166
+ "host_details": host_details,
167
+ "is_discovery": is_discovery,
168
+ "is_full_scan": is_full_scan,
169
+ "services": all_services,
170
+ "hosts": parsed.get("hosts", []),
171
+ "domains": parsed.get(
172
+ "domains", []
173
+ ), # AD domains from LDAP/SMB banners
174
+ }
175
+
176
+ except Exception as e:
177
+ logger.error(f"Error parsing nmap job: {e}")
178
+ return {"error": str(e)}
179
+
180
+ def _store_vulnerabilities(
181
+ self, parsed: Dict, engagement_id: int, host_manager: Any, findings_manager: Any
182
+ ) -> int:
183
+ """Store script-detected vulnerabilities (from --script vuln)."""
184
+ findings_added = 0
185
+
186
+ for vuln in parsed.get("vulnerabilities", []):
187
+ host_ip = vuln.get("host_ip")
188
+ if not host_ip:
189
+ continue
190
+
191
+ host = host_manager.get_host_by_ip(engagement_id, host_ip)
192
+ if not host:
193
+ continue
194
+
195
+ host_id = host["id"]
196
+
197
+ # Determine severity from CVSS score
198
+ cvss = vuln.get("cvss_score")
199
+ if cvss:
200
+ if cvss >= 9.0:
201
+ severity = "critical"
202
+ elif cvss >= 7.0:
203
+ severity = "high"
204
+ elif cvss >= 4.0:
205
+ severity = "medium"
206
+ else:
207
+ severity = "low"
208
+ else:
209
+ severity = "high" if vuln.get("state") == "VULNERABLE" else "medium"
210
+
211
+ # Build references string from CVE IDs
212
+ cve_ids = vuln.get("cve_ids", [])
213
+ refs = None
214
+ if cve_ids:
215
+ cve_refs = [
216
+ f"https://nvd.nist.gov/vuln/detail/{cve}" for cve in cve_ids[:3]
217
+ ]
218
+ refs = ", ".join(cve_refs)
219
+ elif vuln.get("references"):
220
+ refs = ", ".join(vuln.get("references", [])[:3])
221
+
222
+ # Build description with CVSS and CVE info
223
+ description = vuln.get(
224
+ "description", f"Detected by nmap script: {vuln.get('script')}"
225
+ )
226
+ if cvss:
227
+ description += f"\n\nCVSS Score: {cvss}"
228
+ if cve_ids:
229
+ description += f"\nCVE IDs: {', '.join(cve_ids[:5])}"
230
+
231
+ # Build title - include CVE if available
232
+ title = vuln.get("title", vuln.get("script", "Unknown Vulnerability"))
233
+ if cve_ids and cve_ids[0] not in title:
234
+ title = f"{cve_ids[0]}: {title}"
235
+
236
+ findings_manager.add_finding(
237
+ engagement_id=engagement_id,
238
+ host_id=host_id,
239
+ title=title,
240
+ finding_type="vulnerability",
241
+ severity=severity,
242
+ description=description,
243
+ port=vuln.get("port"),
244
+ tool="nmap",
245
+ refs=refs,
246
+ evidence=f"Host: {vuln.get('host_ip', 'unknown')}:{vuln.get('port', 'N/A')}\nScript: {vuln.get('script', 'nmap')}",
247
+ )
248
+ findings_added += 1
249
+
250
+ return findings_added
251
+
252
+ def _check_service_cves(
253
+ self,
254
+ parsed: Dict,
255
+ engagement_id: int,
256
+ host_manager: Any,
257
+ findings_manager: Any,
258
+ cve_matcher: Any,
259
+ ) -> int:
260
+ """Check for inferred CVEs based on service versions."""
261
+ findings_added = 0
262
+
263
+ for host_data in parsed.get("hosts", []):
264
+ if host_data.get("status") != "up":
265
+ continue
266
+
267
+ host = host_manager.get_host_by_ip(engagement_id, host_data.get("ip"))
268
+ if not host:
269
+ continue
270
+
271
+ host_id = host["id"]
272
+
273
+ for svc in host_data.get("services", []):
274
+ service_info = {
275
+ "service_name": svc.get("service") or "",
276
+ "version": svc.get("version") or "",
277
+ "port": svc.get("port"),
278
+ "protocol": svc.get("protocol") or "tcp",
279
+ }
280
+
281
+ # Also check database for stored version if not in parsed data
282
+ if not service_info["version"]:
283
+ services = host_manager.get_host_services(host_id)
284
+ for stored_svc in services:
285
+ if stored_svc["port"] == svc.get("port"):
286
+ service_info["version"] = stored_svc.get(
287
+ "service_version", ""
288
+ )
289
+ break
290
+
291
+ # Check for CVEs
292
+ cve_findings = cve_matcher.parse_nmap_service(service_info)
293
+ for finding in cve_findings:
294
+ findings_manager.add_finding(
295
+ engagement_id=engagement_id,
296
+ host_id=host_id,
297
+ title=finding["title"],
298
+ finding_type="vulnerability",
299
+ severity=finding["severity"],
300
+ description=finding["description"],
301
+ port=finding.get("port"),
302
+ tool="nmap",
303
+ refs=f"https://nvd.nist.gov/vuln/detail/{finding.get('cve_id')}",
304
+ )
305
+ findings_added += 1
306
+
307
+ # Check for common issues
308
+ issue_findings = cve_matcher.scan_for_common_issues(service_info)
309
+ for finding in issue_findings:
310
+ findings_manager.add_finding(
311
+ engagement_id=engagement_id,
312
+ host_id=host_id,
313
+ title=finding["title"],
314
+ finding_type="misconfiguration",
315
+ severity=finding["severity"],
316
+ description=finding["description"],
317
+ port=finding.get("port"),
318
+ tool="nmap",
319
+ )
320
+ findings_added += 1
321
+
322
+ return findings_added
323
+
324
+ def _store_info_scripts(
325
+ self, parsed: Dict, engagement_id: int, host_manager: Any, findings_manager: Any
326
+ ) -> int:
327
+ """Store info script findings (vnc-info, ssh-hostkey, etc.)."""
328
+ findings_added = 0
329
+
330
+ for info in parsed.get("info_scripts", []):
331
+ host_ip = info.get("host_ip")
332
+ if not host_ip:
333
+ logger.warning(f"Info script missing host_ip: {info.get('script')}")
334
+ continue
335
+
336
+ host = host_manager.get_host_by_ip(engagement_id, host_ip)
337
+ if not host:
338
+ logger.warning(
339
+ f"Host not found for info script: {host_ip} in engagement {engagement_id}"
340
+ )
341
+ continue
342
+
343
+ host_id = host["id"]
344
+
345
+ script_name = info.get("script", "unknown")
346
+ title = info.get("title", script_name)
347
+ description = info.get("description", "")
348
+
349
+ port = info.get("port")
350
+ if port:
351
+ title = f"{title} (port {port})"
352
+
353
+ findings_manager.add_finding(
354
+ engagement_id=engagement_id,
355
+ host_id=host_id,
356
+ title=title,
357
+ finding_type="info",
358
+ severity="info",
359
+ description=description,
360
+ port=port,
361
+ tool="nmap",
362
+ evidence=f"Host: {host_ip}:{port if port else 'N/A'}\nScript: {script_name}",
363
+ )
364
+ findings_added += 1
365
+
366
+ return findings_added
367
+
368
+ def _build_host_details(self, parsed: Dict) -> List[Dict]:
369
+ """Build host details list for summary."""
370
+ host_details = []
371
+
372
+ for host_data in parsed.get("hosts", []):
373
+ if host_data.get("status") == "up":
374
+ services = host_data.get("services", [])
375
+ service_count = len(services)
376
+
377
+ # Get top ports for detailed scans
378
+ top_ports = []
379
+ for svc in services[:5]:
380
+ port = svc.get("port")
381
+ service_name = svc.get("service", "unknown")
382
+ top_ports.append(f"{port}/{service_name}")
383
+
384
+ host_details.append(
385
+ {
386
+ "ip": host_data.get("ip"),
387
+ "hostname": host_data.get("hostname"),
388
+ "os": host_data.get("os"),
389
+ "service_count": service_count,
390
+ "top_ports": top_ports,
391
+ }
392
+ )
393
+
394
+ return host_details
395
+
396
+ def _collect_chainable_services(self, parsed: Dict) -> List[Dict]:
397
+ """Collect all services for tool chaining."""
398
+ chainable_states = {"open", "filtered", "open|filtered"}
399
+ all_services = []
400
+
401
+ for host_data in parsed.get("hosts", []):
402
+ if host_data.get("status") == "up":
403
+ for svc in host_data.get("services", []):
404
+ port_state = svc.get("state", "open").lower()
405
+ if port_state in chainable_states:
406
+ all_services.append(
407
+ {
408
+ "ip": host_data.get("ip"),
409
+ "port": svc.get("port"),
410
+ "protocol": svc.get("protocol", "tcp"),
411
+ "state": port_state,
412
+ "service_name": svc.get("service") or "",
413
+ "version": svc.get("version") or "",
414
+ }
415
+ )
416
+
417
+ return all_services
418
+
419
+ def _identify_security_concerns(self, hosts: List[Dict]) -> List[Dict]:
420
+ """Identify risky services in discovered hosts."""
421
+ security_concerns = []
422
+
423
+ for host in hosts:
424
+ ip = host.get("ip", "unknown")
425
+ hostname = host.get("hostname", "")
426
+
427
+ for svc in host.get("services", []):
428
+ port = svc.get("port")
429
+ state = svc.get("state") or ""
430
+ service_name = svc.get("service") or ""
431
+
432
+ if state != "open":
433
+ continue
434
+
435
+ try:
436
+ port_num = int(port)
437
+ if port_num in self.RISKY_PORTS:
438
+ name, desc = self.RISKY_PORTS[port_num]
439
+ host_display = f"{ip}:{port}"
440
+ if hostname:
441
+ host_display += f" ({hostname})"
442
+ security_concerns.append(
443
+ {
444
+ "host": host_display,
445
+ "port": port_num,
446
+ "service": name,
447
+ "description": desc,
448
+ }
449
+ )
450
+ elif "vnc" in service_name.lower():
451
+ host_display = f"{ip}:{port}"
452
+ security_concerns.append(
453
+ {
454
+ "host": host_display,
455
+ "port": port_num,
456
+ "service": "VNC",
457
+ "description": "Remote desktop - often weak auth",
458
+ }
459
+ )
460
+ except (ValueError, TypeError):
461
+ pass
462
+
463
+ return security_concerns
464
+
465
+ def display_done(
466
+ self,
467
+ job: Dict[str, Any],
468
+ log_path: str,
469
+ show_all: bool = False,
470
+ show_passwords: bool = False,
471
+ ) -> None:
472
+ """Display successful nmap scan results."""
473
+ try:
474
+ from souleyez.parsers.nmap_parser import parse_nmap_output
475
+
476
+ if not log_path or not os.path.exists(log_path):
477
+ return
478
+
479
+ with open(log_path, "r", encoding="utf-8", errors="replace") as f:
480
+ log_content = f.read()
481
+ parsed = parse_nmap_output(log_content, job.get("target", ""))
482
+
483
+ vulnerabilities = parsed.get("vulnerabilities", [])
484
+ hosts = parsed.get("hosts", [])
485
+
486
+ # If vulnerabilities found, show vuln-focused view
487
+ if vulnerabilities:
488
+ self._display_vulnerabilities(vulnerabilities, show_all)
489
+ elif hosts:
490
+ self._display_services(hosts, show_all)
491
+ else:
492
+ self.display_no_results(job, log_path)
493
+
494
+ except Exception as e:
495
+ logger.debug(f"Error in display_done: {e}")
496
+
497
+ def _display_vulnerabilities(
498
+ self, vulnerabilities: List[Dict], show_all: bool
499
+ ) -> None:
500
+ """Display vulnerability-focused view."""
501
+ # Group by severity
502
+ by_severity = {"critical": [], "high": [], "medium": [], "low": []}
503
+ for vuln in vulnerabilities:
504
+ cvss = vuln.get("cvss_score")
505
+ if cvss and cvss >= 9.0:
506
+ sev = "critical"
507
+ elif cvss and cvss >= 7.0:
508
+ sev = "high"
509
+ elif cvss and cvss >= 4.0:
510
+ sev = "medium"
511
+ elif vuln.get("state") == "VULNERABLE":
512
+ sev = "high"
513
+ else:
514
+ sev = "medium"
515
+ by_severity[sev].append(vuln)
516
+
517
+ # Count unique hosts scanned
518
+ unique_hosts = set(
519
+ v.get("host_ip") for v in vulnerabilities if v.get("host_ip")
520
+ )
521
+
522
+ click.echo(click.style("=" * 70, fg="red"))
523
+ click.echo(click.style("VULNERABILITY SCAN RESULTS", bold=True, fg="red"))
524
+ click.echo(click.style("=" * 70, fg="red"))
525
+ click.echo()
526
+
527
+ # Summary line
528
+ crit_count = len(by_severity["critical"])
529
+ high_count = len(by_severity["high"])
530
+ med_count = len(by_severity["medium"])
531
+ low_count = len(by_severity["low"])
532
+
533
+ summary_parts = []
534
+ if crit_count:
535
+ summary_parts.append(
536
+ click.style(f"CRITICAL: {crit_count}", fg="red", bold=True)
537
+ )
538
+ if high_count:
539
+ summary_parts.append(click.style(f"HIGH: {high_count}", fg="red"))
540
+ if med_count:
541
+ summary_parts.append(click.style(f"MEDIUM: {med_count}", fg="yellow"))
542
+ if low_count:
543
+ summary_parts.append(click.style(f"LOW: {low_count}", fg="blue"))
544
+
545
+ click.echo(
546
+ f" Hosts Scanned: {len(unique_hosts)} Total Findings: {len(vulnerabilities)}"
547
+ )
548
+ click.echo(f" {' | '.join(summary_parts)}")
549
+ click.echo()
550
+
551
+ # Display each severity section with interactive pagination
552
+ items_per_page = 15
553
+ done_viewing = False
554
+
555
+ for severity in ["critical", "high", "medium", "low"]:
556
+ if done_viewing:
557
+ break
558
+
559
+ items = by_severity[severity]
560
+ if not items:
561
+ continue
562
+
563
+ # Section header with color
564
+ if severity == "critical":
565
+ header = click.style(
566
+ f"-- CRITICAL ({len(items)}) ", fg="red", bold=True
567
+ )
568
+ elif severity == "high":
569
+ header = click.style(f"-- HIGH ({len(items)}) ", fg="red")
570
+ elif severity == "medium":
571
+ header = click.style(f"-- MEDIUM ({len(items)}) ", fg="yellow")
572
+ else:
573
+ header = click.style(f"-- LOW ({len(items)}) ", fg="blue")
574
+
575
+ click.echo(header + click.style("-" * 50, dim=True))
576
+
577
+ # Paginate through items
578
+ current_idx = 0
579
+ while current_idx < len(items):
580
+ end_idx = min(current_idx + items_per_page, len(items))
581
+ for vuln in items[current_idx:end_idx]:
582
+ title = vuln.get("title", vuln.get("script", "Unknown"))
583
+ host_ip = vuln.get("host_ip", "")
584
+ port = vuln.get("port", "")
585
+
586
+ location = f"{host_ip}:{port}" if port else host_ip
587
+ click.echo(f" {location:<18} {title[:55]}")
588
+
589
+ current_idx = end_idx
590
+ remaining = len(items) - current_idx
591
+
592
+ if remaining > 0 and not show_all:
593
+ try:
594
+ prompt_text = click.style(
595
+ f" ({current_idx}/{len(items)}) [Enter]=more [s]=skip [d]=done: ",
596
+ dim=True,
597
+ )
598
+ choice = (
599
+ click.prompt(prompt_text, default="", show_default=False)
600
+ .lower()
601
+ .strip()
602
+ )
603
+
604
+ if choice == "d":
605
+ done_viewing = True
606
+ break
607
+ elif choice == "s":
608
+ click.echo(
609
+ click.style(
610
+ f" ... {remaining} more not shown", dim=True
611
+ )
612
+ )
613
+ break
614
+ except (KeyboardInterrupt, EOFError):
615
+ done_viewing = True
616
+ break
617
+
618
+ click.echo()
619
+
620
+ click.echo(click.style("=" * 70, fg="red"))
621
+ click.echo()
622
+
623
+ def _display_services(self, hosts: List[Dict], show_all: bool) -> None:
624
+ """Display discovered services view."""
625
+ has_services = any(host.get("services", []) for host in hosts)
626
+
627
+ # Security Concerns Analysis
628
+ security_concerns = self._identify_security_concerns(hosts)
629
+
630
+ # Display security concerns section if any found
631
+ if security_concerns:
632
+ click.echo(click.style("=" * 70, fg="yellow"))
633
+ click.echo(click.style("SECURITY CONCERNS", bold=True, fg="yellow"))
634
+ click.echo(click.style("=" * 70, fg="yellow"))
635
+ click.echo()
636
+
637
+ # Group by service type
638
+ by_service = {}
639
+ for concern in security_concerns:
640
+ svc = concern["service"]
641
+ if svc not in by_service:
642
+ by_service[svc] = []
643
+ by_service[svc].append(concern)
644
+
645
+ for service, concerns in sorted(by_service.items()):
646
+ click.echo(
647
+ click.style(f" {service}", bold=True, fg="yellow")
648
+ + click.style(f" - {concerns[0]['description']}", fg="bright_black")
649
+ )
650
+ for c in concerns:
651
+ click.echo(f" - {c['host']}")
652
+ click.echo()
653
+
654
+ click.echo(click.style("=" * 70, fg="yellow"))
655
+ click.echo()
656
+
657
+ click.echo(click.style("=" * 70, fg="cyan"))
658
+ if has_services:
659
+ click.echo(click.style("DISCOVERED SERVICES", bold=True, fg="cyan"))
660
+ else:
661
+ click.echo(click.style("DISCOVERED HOSTS", bold=True, fg="cyan"))
662
+ click.echo(click.style("=" * 70, fg="cyan"))
663
+ click.echo()
664
+
665
+ for host in hosts:
666
+ ip = host.get("ip", "unknown")
667
+ hostname = host.get("hostname")
668
+ status = host.get("status", "unknown")
669
+ services = host.get("services", [])
670
+
671
+ if services:
672
+ # Show host header
673
+ click.echo(click.style(f"Host: {ip}", bold=True))
674
+ if hostname:
675
+ click.echo(f" Hostname: {hostname}")
676
+ # Show each service
677
+ for svc in services:
678
+ port = svc.get("port", "?")
679
+ protocol = svc.get("protocol", "tcp")
680
+ state = svc.get("state", "unknown")
681
+ service = svc.get("service", "unknown")
682
+ version = svc.get("version", "")
683
+
684
+ state_color = (
685
+ "green"
686
+ if state == "open"
687
+ else ("yellow" if state == "filtered" else None)
688
+ )
689
+ state_display = (
690
+ click.style(state, fg=state_color) if state_color else state
691
+ )
692
+ version_str = f" ({version})" if version else ""
693
+ click.echo(
694
+ f" {port}/{protocol} {state_display} {service}{version_str}"
695
+ )
696
+ click.echo()
697
+ elif status == "up":
698
+ host_display = f" {ip}"
699
+ if hostname:
700
+ host_display += f" ({hostname})"
701
+ click.echo(host_display)
702
+
703
+ click.echo(click.style("=" * 70, fg="cyan"))
704
+ click.echo()
705
+
706
+ def display_warning(
707
+ self,
708
+ job: Dict[str, Any],
709
+ log_path: str,
710
+ log_content: Optional[str] = None,
711
+ ) -> None:
712
+ """Display warning status for nmap scan."""
713
+ click.echo(click.style("=" * 70, fg="yellow"))
714
+ click.echo(click.style("[WARNING] NMAP SCAN", bold=True, fg="yellow"))
715
+ click.echo(click.style("=" * 70, fg="yellow"))
716
+ click.echo()
717
+ click.echo(" Scan completed with warnings. Check raw logs for details.")
718
+ click.echo(" Press [r] to view raw logs.")
719
+ click.echo()
720
+ click.echo(click.style("=" * 70, fg="yellow"))
721
+ click.echo()
722
+
723
+ def display_error(
724
+ self,
725
+ job: Dict[str, Any],
726
+ log_path: str,
727
+ log_content: Optional[str] = None,
728
+ ) -> None:
729
+ """Display error status for nmap scan."""
730
+ # Read log if not provided
731
+ if log_content is None and log_path and os.path.exists(log_path):
732
+ try:
733
+ with open(log_path, "r", encoding="utf-8", errors="replace") as f:
734
+ log_content = f.read()
735
+ except Exception:
736
+ log_content = ""
737
+
738
+ click.echo(click.style("=" * 70, fg="red"))
739
+ click.echo(click.style("[ERROR] NMAP SCAN FAILED", bold=True, fg="red"))
740
+ click.echo(click.style("=" * 70, fg="red"))
741
+ click.echo()
742
+
743
+ # Check for common nmap errors
744
+ error_msg = None
745
+ if log_content:
746
+ if "Failed to resolve" in log_content or "Failed to open" in log_content:
747
+ error_msg = "Failed to resolve target hostname"
748
+ elif "No targets were specified" in log_content:
749
+ error_msg = "No valid targets specified"
750
+ elif (
751
+ "requires root privileges" in log_content
752
+ or "Operation not permitted" in log_content
753
+ ):
754
+ error_msg = "Scan type requires root privileges (try sudo)"
755
+ elif "Host seems down" in log_content:
756
+ error_msg = "Host appears to be down or blocking probes"
757
+ elif "timed out" in log_content.lower() or "timeout" in log_content.lower():
758
+ error_msg = "Scan timed out - target may be slow or filtering"
759
+ elif "Connection refused" in log_content:
760
+ error_msg = "Connection refused - no services on target ports"
761
+
762
+ if error_msg:
763
+ click.echo(f" {error_msg}")
764
+ else:
765
+ click.echo(" Scan failed - see raw logs for details (press 'r')")
766
+
767
+ click.echo()
768
+ click.echo(click.style("=" * 70, fg="red"))
769
+ click.echo()
770
+
771
+ def display_no_results(
772
+ self,
773
+ job: Dict[str, Any],
774
+ log_path: str,
775
+ ) -> None:
776
+ """Display no_results status for nmap scan."""
777
+ # Try to read log for additional context
778
+ log_text = ""
779
+ if log_path and os.path.exists(log_path):
780
+ try:
781
+ with open(log_path, "r", encoding="utf-8", errors="replace") as f:
782
+ log_text = f.read()
783
+ except Exception:
784
+ pass
785
+
786
+ click.echo(click.style("=" * 70, fg="cyan"))
787
+ click.echo(click.style("NMAP SCAN RESULTS", bold=True, fg="cyan"))
788
+ click.echo(click.style("=" * 70, fg="cyan"))
789
+ click.echo()
790
+ click.echo(" No open ports or services discovered.")
791
+ click.echo()
792
+
793
+ # Check for additional context
794
+ if "Host seems down" in log_text:
795
+ click.echo(
796
+ click.style(
797
+ " Note: Host appears to be down or blocking probes", fg="yellow"
798
+ )
799
+ )
800
+ elif "filtered" in log_text.lower():
801
+ click.echo(
802
+ click.style(" Note: Ports may be filtered by firewall", fg="yellow")
803
+ )
804
+
805
+ click.echo()
806
+ click.echo(click.style(" This could mean:", fg="bright_black"))
807
+ click.echo(
808
+ click.style(" - All ports are closed or filtered", fg="bright_black")
809
+ )
810
+ click.echo(click.style(" - Host is behind a firewall", fg="bright_black"))
811
+ click.echo(
812
+ click.style(
813
+ " - Try different scan types (-sS, -sT, -sU)", fg="bright_black"
814
+ )
815
+ )
816
+ click.echo(
817
+ click.style(" - Try scanning more ports (-p-)", fg="bright_black")
818
+ )
819
+ click.echo()
820
+ click.echo(click.style("=" * 70, fg="cyan"))
821
+ click.echo()