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.
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
@@ -0,0 +1,815 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Ffuf handler.
4
+
5
+ Consolidates parsing and display logic for ffuf fuzzing jobs.
6
+ """
7
+ import logging
8
+ import os
9
+ import re
10
+ from typing import Any, Dict, List, Optional
11
+ from urllib.parse import urlparse
12
+
13
+ import click
14
+
15
+ from souleyez.engine.job_status import (
16
+ STATUS_DONE,
17
+ STATUS_ERROR,
18
+ STATUS_NO_RESULTS,
19
+ STATUS_WARNING,
20
+ )
21
+ from souleyez.handlers.base import BaseToolHandler
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class FfufHandler(BaseToolHandler):
27
+ """Handler for ffuf fuzzing jobs."""
28
+
29
+ tool_name = "ffuf"
30
+ display_name = "Ffuf"
31
+
32
+ # All handlers enabled
33
+ has_error_handler = True
34
+ has_warning_handler = True
35
+ has_no_results_handler = True
36
+ has_done_handler = True
37
+
38
+ # Security concern patterns (shared with gobuster)
39
+ SECURITY_CONCERN_PATTERNS = {
40
+ "lfi_php_wrapper": {
41
+ "patterns": [r"php://filter", r"php://input", r"data://", r"expect://"],
42
+ "label": "LFI via PHP wrapper (SOURCE CODE EXPOSURE)",
43
+ "severity": "critical",
44
+ },
45
+ "lfi_etc_passwd": {
46
+ "patterns": [r"/etc/passwd", r"/etc/shadow", r"/etc/group"],
47
+ "label": "LFI - System file readable",
48
+ "severity": "critical",
49
+ },
50
+ "lfi_traversal": {
51
+ "patterns": [
52
+ r"\.\./.*passwd",
53
+ r"\.\./.*shadow",
54
+ r"\.\./.*config",
55
+ r"\.\.[\\/]",
56
+ ],
57
+ "label": "LFI via directory traversal",
58
+ "severity": "critical",
59
+ },
60
+ "lfi_log_files": {
61
+ "patterns": [
62
+ r"/var/log/",
63
+ r"/var/mail/",
64
+ r"/proc/self/",
65
+ r"access\.log",
66
+ r"error\.log",
67
+ r"auth\.log",
68
+ ],
69
+ "label": "LFI - Log/proc file readable",
70
+ "severity": "high",
71
+ },
72
+ "lfi_ssh_keys": {
73
+ "patterns": [r"\.ssh/id_rsa", r"\.ssh/authorized_keys", r"\.bash_history"],
74
+ "label": "LFI - SSH keys/history exposed",
75
+ "severity": "critical",
76
+ },
77
+ "database_files": {
78
+ "patterns": [
79
+ r"\.sql$",
80
+ r"\.db$",
81
+ r"\.mdb$",
82
+ r"\.sqlite",
83
+ r"/db\.",
84
+ r"/database\.",
85
+ r"\.bak\.sql",
86
+ r"database\.yml",
87
+ ],
88
+ "label": "Database file exposed",
89
+ "severity": "high",
90
+ },
91
+ "backup_files": {
92
+ "patterns": [
93
+ r"\.bak$",
94
+ r"\.old$",
95
+ r"\.backup$",
96
+ r"\.orig$",
97
+ r"\.save$",
98
+ r"\.swp$",
99
+ r"~$",
100
+ r"\.zip$",
101
+ r"\.tar",
102
+ r"\.gz$",
103
+ r"\.rar$",
104
+ ],
105
+ "label": "Backup/archive file",
106
+ "severity": "high",
107
+ },
108
+ "config_files": {
109
+ "patterns": [
110
+ r"web\.config",
111
+ r"\.htaccess",
112
+ r"\.htpasswd",
113
+ r"\.env$",
114
+ r"config\.php",
115
+ r"config\.inc",
116
+ r"settings\.py",
117
+ r"\.ini$",
118
+ r"\.conf$",
119
+ r"\.cfg$",
120
+ r"wp-config\.php",
121
+ ],
122
+ "label": "Configuration file exposed",
123
+ "severity": "high",
124
+ },
125
+ "source_files": {
126
+ "patterns": [
127
+ r"\.git(/|$)",
128
+ r"\.svn(/|$)",
129
+ r"\.DS_Store",
130
+ r"\.vscode(/|$)",
131
+ r"\.idea(/|$)",
132
+ r"Thumbs\.db",
133
+ r"\.log$",
134
+ r"debug\.",
135
+ r"test\.php",
136
+ r"phpinfo",
137
+ ],
138
+ "label": "Development/debug file",
139
+ "severity": "medium",
140
+ },
141
+ "legacy_dirs": {
142
+ "patterns": [
143
+ r"_vti_",
144
+ r"/cgi-bin(/|$)",
145
+ r"/cgi(/|$)",
146
+ r"/fcgi(/|$)",
147
+ r"/admin(/|$)",
148
+ r"/administrator(/|$)",
149
+ r"/phpmyadmin(/|$)",
150
+ r"/pma(/|$)",
151
+ r"/myadmin(/|$)",
152
+ ],
153
+ "label": "Legacy/admin directory",
154
+ "severity": "medium",
155
+ },
156
+ "sensitive_endpoints": {
157
+ "patterns": [
158
+ r"/upload(/|$)",
159
+ r"/uploads(/|$)",
160
+ r"/file(/|$)",
161
+ r"/files(/|$)",
162
+ r"/tmp(/|$)",
163
+ r"/temp(/|$)",
164
+ r"/private(/|$)",
165
+ r"/internal(/|$)",
166
+ r"/api(/|$)",
167
+ r"/bank(/|$)",
168
+ ],
169
+ "label": "Potentially sensitive directory",
170
+ "severity": "low",
171
+ },
172
+ }
173
+
174
+ def parse_job(
175
+ self,
176
+ engagement_id: int,
177
+ log_path: str,
178
+ job: Dict[str, Any],
179
+ host_manager: Optional[Any] = None,
180
+ findings_manager: Optional[Any] = None,
181
+ credentials_manager: Optional[Any] = None,
182
+ ) -> Dict[str, Any]:
183
+ """
184
+ Parse ffuf job results.
185
+
186
+ Extracts discovered paths and stores them in the database.
187
+ """
188
+ try:
189
+ from souleyez.parsers.ffuf_parser import parse_ffuf
190
+ from souleyez.engine.result_handler import detect_tool_error
191
+
192
+ # Import managers if not provided
193
+ if host_manager is None:
194
+ from souleyez.storage.hosts import HostManager
195
+
196
+ host_manager = HostManager()
197
+ if findings_manager is None:
198
+ from souleyez.storage.findings import FindingsManager
199
+
200
+ findings_manager = FindingsManager()
201
+
202
+ target = job.get("target", "")
203
+ parsed = parse_ffuf(log_path, target)
204
+
205
+ if "error" in parsed:
206
+ return parsed
207
+
208
+ # Extract base target for host tracking
209
+ parsed_url = urlparse(target.replace("FUZZ", ""))
210
+ target_host = parsed_url.hostname or target
211
+
212
+ host_id = None
213
+ if target_host:
214
+ result = host_manager.add_or_update_host(
215
+ engagement_id, {"ip": target_host, "status": "up"}
216
+ )
217
+ if isinstance(result, dict):
218
+ host_id = result.get("id")
219
+ else:
220
+ host_id = result
221
+
222
+ # Store web paths
223
+ paths_added = 0
224
+ created_findings = []
225
+
226
+ if host_id and parsed.get("paths"):
227
+ try:
228
+ from souleyez.storage.web_paths import WebPathsManager
229
+
230
+ wpm = WebPathsManager()
231
+ paths_added = wpm.bulk_add_web_paths(host_id, parsed["paths"])
232
+ except Exception as e:
233
+ logger.warning(f"Failed to store web paths: {e}")
234
+
235
+ # Create findings for sensitive paths
236
+ created_findings = self._create_findings_for_sensitive_paths(
237
+ engagement_id, host_id, parsed["paths"], findings_manager
238
+ )
239
+
240
+ # Auto-extract credentials from PHP filter LFI results
241
+ if credentials_manager is None:
242
+ from souleyez.storage.credentials import CredentialsManager
243
+
244
+ credentials_manager = CredentialsManager()
245
+
246
+ extracted_creds = self._extract_lfi_credentials(
247
+ engagement_id,
248
+ host_id,
249
+ parsed["paths"],
250
+ credentials_manager,
251
+ findings_manager,
252
+ )
253
+ if extracted_creds:
254
+ logger.info(
255
+ f"LFI auto-extraction: Found {len(extracted_creds)} credential(s)"
256
+ )
257
+
258
+ # Check for ffuf errors
259
+ with open(log_path, "r", encoding="utf-8", errors="replace") as f:
260
+ log_content = f.read()
261
+ ffuf_error = detect_tool_error(log_content, "ffuf")
262
+
263
+ # Determine status
264
+ if ffuf_error:
265
+ status = STATUS_ERROR
266
+ elif parsed.get("results_found", 0) > 0:
267
+ status = STATUS_DONE
268
+ else:
269
+ status = STATUS_NO_RESULTS
270
+
271
+ # Build summary for job queue display
272
+ summary_parts = []
273
+ results_found = parsed.get("results_found", 0)
274
+ if results_found > 0:
275
+ summary_parts.append(f"{results_found} result(s)")
276
+ if len(created_findings) > 0:
277
+ summary_parts.append(f"{len(created_findings)} finding(s)")
278
+ summary = " | ".join(summary_parts) if summary_parts else "No results"
279
+
280
+ return {
281
+ "tool": "ffuf",
282
+ "status": status,
283
+ "summary": summary,
284
+ "target": target,
285
+ "results_found": parsed.get("results_found", 0),
286
+ "paths_added": paths_added,
287
+ "findings_added": len(created_findings),
288
+ "method": parsed.get("method"),
289
+ "parameters_found": parsed.get("paths", []),
290
+ }
291
+
292
+ except Exception as e:
293
+ logger.error(f"Error parsing ffuf job: {e}")
294
+ return {"error": str(e)}
295
+
296
+ def _create_findings_for_sensitive_paths(
297
+ self, engagement_id: int, host_id: int, paths: List[Dict], findings_manager: Any
298
+ ) -> List[Dict]:
299
+ """Create findings for sensitive/interesting paths discovered."""
300
+ created_findings = []
301
+
302
+ for path_entry in paths:
303
+ url = path_entry.get("url", "").lower()
304
+ for concern_type, concern_info in self.SECURITY_CONCERN_PATTERNS.items():
305
+ for pattern in concern_info["patterns"]:
306
+ if re.search(pattern, url, re.IGNORECASE):
307
+ try:
308
+ findings_manager.add_finding(
309
+ engagement_id=engagement_id,
310
+ host_id=host_id,
311
+ title=f"{concern_info['label']}: {url.split('/')[-1]}",
312
+ finding_type="web_path",
313
+ severity=concern_info["severity"],
314
+ description=f"Ffuf discovered a potentially sensitive path: {path_entry.get('url', '')}\n"
315
+ f"Status code: {path_entry.get('status_code', 'unknown')}\n"
316
+ f"Category: {concern_info['label']}",
317
+ tool="ffuf",
318
+ )
319
+ created_findings.append(
320
+ {
321
+ "url": path_entry.get("url"),
322
+ "type": concern_type,
323
+ "severity": concern_info["severity"],
324
+ }
325
+ )
326
+ except Exception as e:
327
+ logger.warning(f"Failed to create finding: {e}")
328
+ break
329
+
330
+ return created_findings
331
+
332
+ def _extract_lfi_credentials(
333
+ self,
334
+ engagement_id: int,
335
+ host_id: int,
336
+ paths: List[Dict],
337
+ credentials_manager: Any,
338
+ findings_manager: Any,
339
+ ) -> List[Dict]:
340
+ """
341
+ Auto-extract credentials from successful PHP filter LFI results.
342
+
343
+ When php://filter/convert.base64-encode URLs are found, fetch them,
344
+ decode the base64, and parse for credentials.
345
+ """
346
+ import base64
347
+ import requests
348
+
349
+ extracted_creds = []
350
+
351
+ # Find PHP filter URLs (config files are most valuable)
352
+ php_filter_urls = []
353
+ for path_entry in paths:
354
+ url = path_entry.get("url", "")
355
+ if "php://filter" in url and "base64-encode" in url:
356
+ # Prioritize config files
357
+ if any(
358
+ kw in url.lower()
359
+ for kw in ["config", "database", "settings", "db", "connect"]
360
+ ):
361
+ php_filter_urls.insert(0, url) # High priority
362
+ else:
363
+ php_filter_urls.append(url)
364
+
365
+ if not php_filter_urls:
366
+ return []
367
+
368
+ logger.info(
369
+ f"LFI auto-extraction: Found {len(php_filter_urls)} PHP filter URL(s)"
370
+ )
371
+
372
+ # Credential patterns to search for in decoded PHP
373
+ cred_patterns = [
374
+ # PHP variable assignments
375
+ (r'\$(?:password|passwd|pass|pwd)\s*=\s*["\']([^"\']+)["\']', "password"),
376
+ (r'\$(?:username|user|usr|login)\s*=\s*["\']([^"\']+)["\']', "username"),
377
+ (r'\$(?:database|db|dbname|db_name)\s*=\s*["\']([^"\']+)["\']', "database"),
378
+ (r'\$(?:server|host|hostname|db_host)\s*=\s*["\']([^"\']+)["\']', "host"),
379
+ # Array-style configs
380
+ (r'["\']password["\']\s*=>\s*["\']([^"\']+)["\']', "password"),
381
+ (r'["\']username["\']\s*=>\s*["\']([^"\']+)["\']', "username"),
382
+ (r'["\']database["\']\s*=>\s*["\']([^"\']+)["\']', "database"),
383
+ (r'["\']host["\']\s*=>\s*["\']([^"\']+)["\']', "host"),
384
+ # Define constants
385
+ (
386
+ r"define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*['\"]([^'\"]+)['\"]\s*\)",
387
+ "password",
388
+ ),
389
+ (
390
+ r"define\s*\(\s*['\"]DB_USER['\"]\s*,\s*['\"]([^'\"]+)['\"]\s*\)",
391
+ "username",
392
+ ),
393
+ (
394
+ r"define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*['\"]([^'\"]+)['\"]\s*\)",
395
+ "database",
396
+ ),
397
+ (r"define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*['\"]([^'\"]+)['\"]\s*\)", "host"),
398
+ ]
399
+
400
+ # Try to fetch and parse each URL (limit to prevent abuse)
401
+ max_fetch = 5
402
+ for url in php_filter_urls[:max_fetch]:
403
+ try:
404
+ logger.info(f" Fetching: {url}")
405
+ resp = requests.get(
406
+ url, timeout=10, verify=False
407
+ ) # nosec B501 - pentesting tool
408
+
409
+ if resp.status_code != 200:
410
+ continue
411
+
412
+ # Extract base64 from response (usually in HTML body)
413
+ html = resp.text
414
+
415
+ # Look for base64 content - usually a long string of alphanumeric + /+=
416
+ base64_pattern = r"([A-Za-z0-9+/]{50,}={0,2})"
417
+ matches = re.findall(base64_pattern, html)
418
+
419
+ for b64_match in matches:
420
+ try:
421
+ decoded = base64.b64decode(b64_match).decode(
422
+ "utf-8", errors="ignore"
423
+ )
424
+
425
+ # Skip if it doesn't look like PHP
426
+ if "<?php" not in decoded and "$" not in decoded:
427
+ continue
428
+
429
+ logger.info(f" Decoded PHP source ({len(decoded)} bytes)")
430
+
431
+ # Extract credentials
432
+ creds = {"source": url, "source_file": "config.php"}
433
+ for pattern, field in cred_patterns:
434
+ match = re.search(pattern, decoded, re.IGNORECASE)
435
+ if match:
436
+ creds[field] = match.group(1)
437
+
438
+ # If we found at least username and password, store it
439
+ if creds.get("username") and creds.get("password"):
440
+ logger.info(
441
+ f" Found credentials: {creds.get('username')}:***"
442
+ )
443
+
444
+ # Store in credentials manager
445
+ try:
446
+ credentials_manager.add_credential(
447
+ engagement_id=engagement_id,
448
+ host_id=host_id,
449
+ username=creds.get("username"),
450
+ password=creds.get("password"),
451
+ credential_type="database",
452
+ service=creds.get("database", "mysql"),
453
+ source="LFI auto-extraction",
454
+ notes=f"Extracted from {url}\nDatabase: {creds.get('database', 'unknown')}\nHost: {creds.get('host', 'localhost')}",
455
+ )
456
+ extracted_creds.append(creds)
457
+ except Exception as e:
458
+ logger.warning(f"Failed to store credential: {e}")
459
+
460
+ # Also create a critical finding
461
+ try:
462
+ findings_manager.add_finding(
463
+ engagement_id=engagement_id,
464
+ host_id=host_id,
465
+ title=f"LFI Credential Extraction: {creds.get('username')}@{creds.get('database', 'database')}",
466
+ finding_type="credential",
467
+ severity="critical",
468
+ description=f"Credentials automatically extracted via LFI:\n\n"
469
+ f"Username: {creds.get('username')}\n"
470
+ f"Database: {creds.get('database', 'unknown')}\n"
471
+ f"Host: {creds.get('host', 'localhost')}\n\n"
472
+ f"Source URL: {url}\n\n"
473
+ f"This indicates a critical LFI vulnerability that exposes database credentials.",
474
+ tool="ffuf",
475
+ )
476
+ except Exception as e:
477
+ logger.warning(f"Failed to create finding: {e}")
478
+
479
+ break # Found creds in this URL, move to next
480
+
481
+ except Exception as e:
482
+ logger.debug(f"Failed to decode base64: {e}")
483
+ continue
484
+
485
+ except requests.RequestException as e:
486
+ logger.warning(f"Failed to fetch {url}: {e}")
487
+ continue
488
+ except Exception as e:
489
+ logger.warning(f"Error processing {url}: {e}")
490
+ continue
491
+
492
+ return extracted_creds
493
+
494
+ def _identify_security_concerns(self, paths: List[Dict]) -> List[Dict]:
495
+ """Identify security concerns in discovered paths."""
496
+ concerns = []
497
+
498
+ for path_entry in paths:
499
+ url = path_entry.get("url", "").lower()
500
+ for concern_type, concern_info in self.SECURITY_CONCERN_PATTERNS.items():
501
+ for pattern in concern_info["patterns"]:
502
+ if re.search(pattern, url, re.IGNORECASE):
503
+ concerns.append(
504
+ {
505
+ "url": path_entry.get("url", ""),
506
+ "type": concern_type,
507
+ "label": concern_info["label"],
508
+ "severity": concern_info["severity"],
509
+ "status_code": path_entry.get("status_code", "unknown"),
510
+ }
511
+ )
512
+ break
513
+
514
+ return concerns
515
+
516
+ def display_done(
517
+ self,
518
+ job: Dict[str, Any],
519
+ log_path: str,
520
+ show_all: bool = False,
521
+ show_passwords: bool = False,
522
+ ) -> None:
523
+ """Display successful ffuf scan results."""
524
+ try:
525
+ from souleyez.parsers.ffuf_parser import parse_ffuf
526
+
527
+ if not log_path or not os.path.exists(log_path):
528
+ return
529
+
530
+ parsed = parse_ffuf(log_path, job.get("target", ""))
531
+ paths = parsed.get("paths", [])
532
+
533
+ if not paths:
534
+ self.display_no_results(job, log_path)
535
+ return
536
+
537
+ # Identify security concerns
538
+ security_concerns = self._identify_security_concerns(paths)
539
+
540
+ # Display security concerns if found
541
+ if security_concerns:
542
+ click.echo(click.style("=" * 70, fg="red"))
543
+ click.echo(click.style("SECURITY CONCERNS", bold=True, fg="red"))
544
+ click.echo(click.style("=" * 70, fg="red"))
545
+ click.echo()
546
+
547
+ critical_concerns = [
548
+ c for c in security_concerns if c["severity"] == "critical"
549
+ ]
550
+ high_concerns = [
551
+ c for c in security_concerns if c["severity"] == "high"
552
+ ]
553
+ medium_concerns = [
554
+ c for c in security_concerns if c["severity"] == "medium"
555
+ ]
556
+ low_concerns = [c for c in security_concerns if c["severity"] == "low"]
557
+
558
+ if critical_concerns:
559
+ click.echo(
560
+ click.style(
561
+ "[CRITICAL] LFI VULNERABILITY CONFIRMED:",
562
+ fg="red",
563
+ bold=True,
564
+ blink=True,
565
+ )
566
+ )
567
+ by_label = {}
568
+ for c in critical_concerns:
569
+ if c["label"] not in by_label:
570
+ by_label[c["label"]] = []
571
+ by_label[c["label"]].append(c["url"])
572
+ for label, urls in by_label.items():
573
+ click.echo(click.style(f" - {label}:", fg="red"))
574
+ for url in urls[:5]:
575
+ click.echo(f" {url}")
576
+ if len(urls) > 5:
577
+ click.echo(f" ... and {len(urls) - 5} more")
578
+ click.echo()
579
+
580
+ if high_concerns:
581
+ click.echo(
582
+ click.style("[HIGH] Critical findings:", fg="red", bold=True)
583
+ )
584
+ by_label = {}
585
+ for c in high_concerns:
586
+ if c["label"] not in by_label:
587
+ by_label[c["label"]] = []
588
+ by_label[c["label"]].append(c["url"])
589
+ for label, urls in by_label.items():
590
+ click.echo(click.style(f" - {label}:", fg="red"))
591
+ for url in urls[:5]:
592
+ click.echo(f" {url}")
593
+ if len(urls) > 5:
594
+ click.echo(f" ... and {len(urls) - 5} more")
595
+ click.echo()
596
+
597
+ if medium_concerns:
598
+ click.echo(
599
+ click.style(
600
+ "[MEDIUM] Notable findings:", fg="yellow", bold=True
601
+ )
602
+ )
603
+ by_label = {}
604
+ for c in medium_concerns:
605
+ if c["label"] not in by_label:
606
+ by_label[c["label"]] = []
607
+ by_label[c["label"]].append(c["url"])
608
+ for label, urls in by_label.items():
609
+ click.echo(click.style(f" - {label}:", fg="yellow"))
610
+ for url in urls[:5]:
611
+ click.echo(f" {url}")
612
+ if len(urls) > 5:
613
+ click.echo(f" ... and {len(urls) - 5} more")
614
+ click.echo()
615
+
616
+ if low_concerns:
617
+ click.echo(
618
+ click.style("[LOW] Worth investigating:", fg="cyan", bold=True)
619
+ )
620
+ by_label = {}
621
+ for c in low_concerns:
622
+ if c["label"] not in by_label:
623
+ by_label[c["label"]] = []
624
+ by_label[c["label"]].append(c["url"])
625
+ for label, urls in by_label.items():
626
+ click.echo(f" - {label}: {len(urls)} path(s)")
627
+ click.echo()
628
+
629
+ # Display discovered paths
630
+ click.echo(click.style("=" * 70, fg="cyan"))
631
+ click.echo(click.style("FFUF DISCOVERED PATHS", bold=True, fg="cyan"))
632
+ click.echo(click.style("=" * 70, fg="cyan"))
633
+ click.echo()
634
+ click.echo(f"Total found: {len(paths)}")
635
+ if parsed.get("method"):
636
+ click.echo(f"Method: {parsed.get('method')}")
637
+ click.echo()
638
+
639
+ # Group by status code
640
+ status_groups = {}
641
+ for path in paths:
642
+ status = path.get("status_code", "unknown")
643
+ if status not in status_groups:
644
+ status_groups[status] = []
645
+ status_groups[status].append(path)
646
+
647
+ # Display by status code
648
+ for status in sorted(status_groups.keys()):
649
+ status_color = (
650
+ "green"
651
+ if status == 200
652
+ else "cyan" if status in [301, 302] else "yellow"
653
+ )
654
+ click.echo(
655
+ click.style(
656
+ f"[{status}] ({len(status_groups[status])} paths)",
657
+ bold=True,
658
+ fg=status_color,
659
+ )
660
+ )
661
+
662
+ paths_to_show = (
663
+ status_groups[status] if show_all else status_groups[status][:10]
664
+ )
665
+
666
+ for path in paths_to_show:
667
+ url = path.get("url", "")
668
+ size = path.get("size", "")
669
+ redirect = path.get("redirect", "")
670
+
671
+ if redirect:
672
+ click.echo(f" {url} -> {redirect}")
673
+ elif size:
674
+ click.echo(f" {url} ({size} bytes)")
675
+ else:
676
+ click.echo(f" {url}")
677
+
678
+ if not show_all and len(status_groups[status]) > 10:
679
+ click.echo(f" ... and {len(status_groups[status]) - 10} more")
680
+ click.echo()
681
+
682
+ click.echo(click.style("=" * 70, fg="cyan"))
683
+ click.echo()
684
+
685
+ except Exception as e:
686
+ logger.debug(f"Error in display_done: {e}")
687
+
688
+ def display_warning(
689
+ self,
690
+ job: Dict[str, Any],
691
+ log_path: str,
692
+ log_content: Optional[str] = None,
693
+ ) -> None:
694
+ """Display warning status for ffuf scan."""
695
+ click.echo(click.style("=" * 70, fg="yellow"))
696
+ click.echo(click.style("[WARNING] FFUF SCAN", bold=True, fg="yellow"))
697
+ click.echo(click.style("=" * 70, fg="yellow"))
698
+ click.echo()
699
+ click.echo(" Scan completed with warnings. Check raw logs for details.")
700
+ click.echo(" Press [r] to view raw logs.")
701
+ click.echo()
702
+ click.echo(click.style("=" * 70, fg="yellow"))
703
+ click.echo()
704
+
705
+ def display_error(
706
+ self,
707
+ job: Dict[str, Any],
708
+ log_path: str,
709
+ log_content: Optional[str] = None,
710
+ ) -> None:
711
+ """Display error status for ffuf scan."""
712
+ # Read log if not provided
713
+ if log_content is None and log_path and os.path.exists(log_path):
714
+ try:
715
+ with open(log_path, "r", encoding="utf-8", errors="replace") as f:
716
+ log_content = f.read()
717
+ except Exception:
718
+ log_content = ""
719
+
720
+ click.echo(click.style("=" * 70, fg="red"))
721
+ click.echo(click.style("[ERROR] FFUF SCAN FAILED", bold=True, fg="red"))
722
+ click.echo(click.style("=" * 70, fg="red"))
723
+ click.echo()
724
+
725
+ # Check if it was a timeout
726
+ if log_content and (
727
+ "timed out" in log_content.lower() or "Command timed out" in log_content
728
+ ):
729
+ click.echo(" Scan reached timeout before completing.")
730
+ click.echo()
731
+ click.echo(click.style(" Possible causes:", fg="bright_black"))
732
+ click.echo(
733
+ click.style(" - Target is rate limiting requests", fg="bright_black")
734
+ )
735
+ click.echo(
736
+ click.style(
737
+ " - Wordlist too large for timeout window", fg="bright_black"
738
+ )
739
+ )
740
+ click.echo(click.style(" - Network latency issues", fg="bright_black"))
741
+ click.echo()
742
+ click.echo(click.style(" Suggestions:", fg="bright_black"))
743
+ click.echo(click.style(" - Try smaller wordlist", fg="bright_black"))
744
+ click.echo(
745
+ click.style(
746
+ " - Increase -p (delay) between requests", fg="bright_black"
747
+ )
748
+ )
749
+ click.echo(click.style(" - Reduce -t (threads)", fg="bright_black"))
750
+ else:
751
+ error_msg = None
752
+ if log_content and "ERROR:" in log_content:
753
+ match = re.search(r"ERROR:\s*(.+?)(?:\n|$)", log_content)
754
+ if match:
755
+ error_msg = match.group(1).strip()
756
+
757
+ if error_msg:
758
+ click.echo(f" Error: {error_msg}")
759
+ else:
760
+ click.echo(" Scan failed - see raw logs for details.")
761
+ click.echo(" Press [r] to view raw logs.")
762
+
763
+ click.echo()
764
+ click.echo(click.style("=" * 70, fg="red"))
765
+ click.echo()
766
+
767
+ def display_no_results(
768
+ self,
769
+ job: Dict[str, Any],
770
+ log_path: str,
771
+ ) -> None:
772
+ """Display no_results status for ffuf scan."""
773
+ click.echo(click.style("=" * 70, fg="cyan"))
774
+ click.echo(click.style("FFUF SCAN RESULTS", bold=True, fg="cyan"))
775
+ click.echo(click.style("=" * 70, fg="cyan"))
776
+ click.echo()
777
+ click.echo(" No paths discovered.")
778
+ click.echo()
779
+
780
+ # Try to get config info from parsed results
781
+ if log_path and os.path.exists(log_path):
782
+ try:
783
+ from souleyez.parsers.ffuf_parser import parse_ffuf
784
+
785
+ parsed = parse_ffuf(log_path, job.get("target", ""))
786
+ if parsed.get("wordlist"):
787
+ click.echo(f" Wordlist: {os.path.basename(parsed['wordlist'])}")
788
+ if parsed.get("method"):
789
+ click.echo(f" Method: {parsed['method']}")
790
+ except Exception:
791
+ pass
792
+
793
+ click.echo()
794
+ click.echo(click.style(" This could mean:", fg="bright_black"))
795
+ click.echo(
796
+ click.style(
797
+ " - Target has good security (no exposed paths)", fg="bright_black"
798
+ )
799
+ )
800
+ click.echo(
801
+ click.style(" - Try a different/larger wordlist", fg="bright_black")
802
+ )
803
+ click.echo(
804
+ click.style(
805
+ " - Check filter settings (-fc, -fs, -fw)", fg="bright_black"
806
+ )
807
+ )
808
+ click.echo(
809
+ click.style(
810
+ " - Target may be blocking automated requests", fg="bright_black"
811
+ )
812
+ )
813
+ click.echo()
814
+ click.echo(click.style("=" * 70, fg="cyan"))
815
+ click.echo()