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.
- souleyez/__init__.py +1 -2
- souleyez/ai/__init__.py +21 -15
- souleyez/ai/action_mapper.py +249 -150
- souleyez/ai/chain_advisor.py +116 -100
- souleyez/ai/claude_provider.py +29 -28
- souleyez/ai/context_builder.py +80 -62
- souleyez/ai/executor.py +158 -117
- souleyez/ai/feedback_handler.py +136 -121
- souleyez/ai/llm_factory.py +27 -20
- souleyez/ai/llm_provider.py +4 -2
- souleyez/ai/ollama_provider.py +6 -9
- souleyez/ai/ollama_service.py +44 -37
- souleyez/ai/path_scorer.py +91 -76
- souleyez/ai/recommender.py +176 -144
- souleyez/ai/report_context.py +74 -73
- souleyez/ai/report_service.py +84 -66
- souleyez/ai/result_parser.py +222 -229
- souleyez/ai/safety.py +67 -44
- souleyez/auth/__init__.py +23 -22
- souleyez/auth/audit.py +36 -26
- souleyez/auth/engagement_access.py +65 -48
- souleyez/auth/permissions.py +14 -3
- souleyez/auth/session_manager.py +54 -37
- souleyez/auth/user_manager.py +109 -64
- souleyez/commands/audit.py +40 -43
- souleyez/commands/auth.py +35 -15
- souleyez/commands/deliverables.py +55 -50
- souleyez/commands/engagement.py +47 -28
- souleyez/commands/license.py +32 -23
- souleyez/commands/screenshots.py +36 -32
- souleyez/commands/user.py +82 -36
- souleyez/config.py +52 -44
- souleyez/core/credential_tester.py +87 -81
- souleyez/core/cve_mappings.py +179 -192
- souleyez/core/cve_matcher.py +162 -148
- souleyez/core/msf_auto_mapper.py +100 -83
- souleyez/core/msf_chain_engine.py +294 -256
- souleyez/core/msf_database.py +153 -70
- souleyez/core/msf_integration.py +679 -673
- souleyez/core/msf_rpc_client.py +40 -42
- souleyez/core/msf_rpc_manager.py +77 -79
- souleyez/core/msf_sync_manager.py +241 -181
- souleyez/core/network_utils.py +22 -15
- souleyez/core/parser_handler.py +34 -25
- souleyez/core/pending_chains.py +114 -63
- souleyez/core/templates.py +158 -107
- souleyez/core/tool_chaining.py +9526 -2879
- souleyez/core/version_utils.py +79 -94
- souleyez/core/vuln_correlation.py +136 -89
- souleyez/core/web_utils.py +33 -32
- souleyez/data/wordlists/ad_users.txt +378 -0
- souleyez/data/wordlists/api_endpoints_large.txt +769 -0
- souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
- souleyez/data/wordlists/lfi_payloads.txt +82 -0
- souleyez/data/wordlists/passwords_brute.txt +1548 -0
- souleyez/data/wordlists/passwords_crack.txt +2479 -0
- souleyez/data/wordlists/passwords_spray.txt +386 -0
- souleyez/data/wordlists/subdomains_large.txt +5057 -0
- souleyez/data/wordlists/usernames_common.txt +694 -0
- souleyez/data/wordlists/web_dirs_large.txt +4769 -0
- souleyez/detection/__init__.py +1 -1
- souleyez/detection/attack_signatures.py +12 -17
- souleyez/detection/mitre_mappings.py +61 -55
- souleyez/detection/validator.py +97 -86
- souleyez/devtools.py +23 -10
- souleyez/docs/README.md +4 -4
- souleyez/docs/api-reference/cli-commands.md +2 -2
- souleyez/docs/developer-guide/adding-new-tools.md +562 -0
- souleyez/docs/user-guide/auto-chaining.md +30 -8
- souleyez/docs/user-guide/getting-started.md +1 -1
- souleyez/docs/user-guide/installation.md +26 -3
- souleyez/docs/user-guide/metasploit-integration.md +2 -2
- souleyez/docs/user-guide/rbac.md +1 -1
- souleyez/docs/user-guide/scope-management.md +1 -1
- souleyez/docs/user-guide/siem-integration.md +1 -1
- souleyez/docs/user-guide/tools-reference.md +1 -8
- souleyez/docs/user-guide/worker-management.md +1 -1
- souleyez/engine/background.py +1239 -535
- souleyez/engine/base.py +4 -1
- souleyez/engine/job_status.py +17 -49
- souleyez/engine/log_sanitizer.py +103 -77
- souleyez/engine/manager.py +38 -7
- souleyez/engine/result_handler.py +2200 -1550
- souleyez/engine/worker_manager.py +50 -41
- souleyez/export/evidence_bundle.py +72 -62
- souleyez/feature_flags/features.py +16 -20
- souleyez/feature_flags.py +5 -9
- souleyez/handlers/__init__.py +11 -0
- souleyez/handlers/base.py +188 -0
- souleyez/handlers/bash_handler.py +277 -0
- souleyez/handlers/bloodhound_handler.py +243 -0
- souleyez/handlers/certipy_handler.py +311 -0
- souleyez/handlers/crackmapexec_handler.py +486 -0
- souleyez/handlers/dnsrecon_handler.py +344 -0
- souleyez/handlers/enum4linux_handler.py +400 -0
- souleyez/handlers/evil_winrm_handler.py +493 -0
- souleyez/handlers/ffuf_handler.py +815 -0
- souleyez/handlers/gobuster_handler.py +1114 -0
- souleyez/handlers/gpp_extract_handler.py +334 -0
- souleyez/handlers/hashcat_handler.py +444 -0
- souleyez/handlers/hydra_handler.py +563 -0
- souleyez/handlers/impacket_getuserspns_handler.py +343 -0
- souleyez/handlers/impacket_psexec_handler.py +222 -0
- souleyez/handlers/impacket_secretsdump_handler.py +426 -0
- souleyez/handlers/john_handler.py +286 -0
- souleyez/handlers/katana_handler.py +425 -0
- souleyez/handlers/kerbrute_handler.py +298 -0
- souleyez/handlers/ldapsearch_handler.py +636 -0
- souleyez/handlers/lfi_extract_handler.py +464 -0
- souleyez/handlers/msf_auxiliary_handler.py +408 -0
- souleyez/handlers/msf_exploit_handler.py +380 -0
- souleyez/handlers/nikto_handler.py +413 -0
- souleyez/handlers/nmap_handler.py +821 -0
- souleyez/handlers/nuclei_handler.py +359 -0
- souleyez/handlers/nxc_handler.py +371 -0
- souleyez/handlers/rdp_sec_check_handler.py +353 -0
- souleyez/handlers/registry.py +292 -0
- souleyez/handlers/responder_handler.py +232 -0
- souleyez/handlers/service_explorer_handler.py +434 -0
- souleyez/handlers/smbclient_handler.py +344 -0
- souleyez/handlers/smbmap_handler.py +510 -0
- souleyez/handlers/smbpasswd_handler.py +296 -0
- souleyez/handlers/sqlmap_handler.py +1116 -0
- souleyez/handlers/theharvester_handler.py +601 -0
- souleyez/handlers/web_login_test_handler.py +327 -0
- souleyez/handlers/whois_handler.py +277 -0
- souleyez/handlers/wpscan_handler.py +554 -0
- souleyez/history.py +32 -16
- souleyez/importers/msf_importer.py +106 -75
- souleyez/importers/smart_importer.py +208 -147
- souleyez/integrations/siem/__init__.py +10 -10
- souleyez/integrations/siem/base.py +17 -18
- souleyez/integrations/siem/elastic.py +108 -122
- souleyez/integrations/siem/factory.py +207 -80
- souleyez/integrations/siem/googlesecops.py +146 -154
- souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
- souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
- souleyez/integrations/siem/sentinel.py +107 -109
- souleyez/integrations/siem/splunk.py +246 -212
- souleyez/integrations/siem/wazuh.py +65 -71
- souleyez/integrations/wazuh/__init__.py +5 -5
- souleyez/integrations/wazuh/client.py +70 -93
- souleyez/integrations/wazuh/config.py +85 -57
- souleyez/integrations/wazuh/host_mapper.py +28 -36
- souleyez/integrations/wazuh/sync.py +78 -68
- souleyez/intelligence/__init__.py +4 -5
- souleyez/intelligence/correlation_analyzer.py +309 -295
- souleyez/intelligence/exploit_knowledge.py +661 -623
- souleyez/intelligence/exploit_suggestions.py +159 -139
- souleyez/intelligence/gap_analyzer.py +132 -97
- souleyez/intelligence/gap_detector.py +251 -214
- souleyez/intelligence/sensitive_tables.py +266 -129
- souleyez/intelligence/service_parser.py +137 -123
- souleyez/intelligence/surface_analyzer.py +407 -268
- souleyez/intelligence/target_parser.py +159 -162
- souleyez/licensing/__init__.py +6 -6
- souleyez/licensing/validator.py +17 -19
- souleyez/log_config.py +79 -54
- souleyez/main.py +1505 -687
- souleyez/migrations/fix_job_counter.py +16 -14
- souleyez/parsers/bloodhound_parser.py +41 -39
- souleyez/parsers/crackmapexec_parser.py +178 -111
- souleyez/parsers/dalfox_parser.py +72 -77
- souleyez/parsers/dnsrecon_parser.py +103 -91
- souleyez/parsers/enum4linux_parser.py +183 -153
- souleyez/parsers/ffuf_parser.py +29 -25
- souleyez/parsers/gobuster_parser.py +301 -41
- souleyez/parsers/hashcat_parser.py +324 -79
- souleyez/parsers/http_fingerprint_parser.py +350 -103
- souleyez/parsers/hydra_parser.py +131 -111
- souleyez/parsers/impacket_parser.py +231 -178
- souleyez/parsers/john_parser.py +98 -86
- souleyez/parsers/katana_parser.py +316 -0
- souleyez/parsers/msf_parser.py +943 -498
- souleyez/parsers/nikto_parser.py +346 -65
- souleyez/parsers/nmap_parser.py +262 -174
- souleyez/parsers/nuclei_parser.py +40 -44
- souleyez/parsers/responder_parser.py +26 -26
- souleyez/parsers/searchsploit_parser.py +74 -74
- souleyez/parsers/service_explorer_parser.py +279 -0
- souleyez/parsers/smbmap_parser.py +180 -124
- souleyez/parsers/sqlmap_parser.py +434 -308
- souleyez/parsers/theharvester_parser.py +75 -57
- souleyez/parsers/whois_parser.py +135 -94
- souleyez/parsers/wpscan_parser.py +278 -190
- souleyez/plugins/afp.py +44 -36
- souleyez/plugins/afp_brute.py +114 -46
- souleyez/plugins/ard.py +48 -37
- souleyez/plugins/bloodhound.py +95 -61
- souleyez/plugins/certipy.py +303 -0
- souleyez/plugins/crackmapexec.py +186 -85
- souleyez/plugins/dalfox.py +120 -59
- souleyez/plugins/dns_hijack.py +146 -41
- souleyez/plugins/dnsrecon.py +97 -61
- souleyez/plugins/enum4linux.py +91 -66
- souleyez/plugins/evil_winrm.py +291 -0
- souleyez/plugins/ffuf.py +166 -90
- souleyez/plugins/firmware_extract.py +133 -29
- souleyez/plugins/gobuster.py +387 -190
- souleyez/plugins/gpp_extract.py +393 -0
- souleyez/plugins/hashcat.py +100 -73
- souleyez/plugins/http_fingerprint.py +854 -267
- souleyez/plugins/hydra.py +566 -200
- souleyez/plugins/impacket_getnpusers.py +117 -69
- souleyez/plugins/impacket_psexec.py +84 -64
- souleyez/plugins/impacket_secretsdump.py +103 -69
- souleyez/plugins/impacket_smbclient.py +89 -75
- souleyez/plugins/john.py +86 -69
- souleyez/plugins/katana.py +313 -0
- souleyez/plugins/kerbrute.py +237 -0
- souleyez/plugins/lfi_extract.py +541 -0
- souleyez/plugins/macos_ssh.py +117 -48
- souleyez/plugins/mdns.py +35 -30
- souleyez/plugins/msf_auxiliary.py +253 -130
- souleyez/plugins/msf_exploit.py +239 -161
- souleyez/plugins/nikto.py +134 -78
- souleyez/plugins/nmap.py +275 -91
- souleyez/plugins/nuclei.py +180 -89
- souleyez/plugins/nxc.py +285 -0
- souleyez/plugins/plugin_base.py +35 -36
- souleyez/plugins/plugin_template.py +13 -5
- souleyez/plugins/rdp_sec_check.py +130 -0
- souleyez/plugins/responder.py +112 -71
- souleyez/plugins/router_http_brute.py +76 -65
- souleyez/plugins/router_ssh_brute.py +118 -41
- souleyez/plugins/router_telnet_brute.py +124 -42
- souleyez/plugins/routersploit.py +91 -59
- souleyez/plugins/routersploit_exploit.py +77 -55
- souleyez/plugins/searchsploit.py +91 -77
- souleyez/plugins/service_explorer.py +1160 -0
- souleyez/plugins/smbmap.py +122 -72
- souleyez/plugins/smbpasswd.py +215 -0
- souleyez/plugins/sqlmap.py +301 -113
- souleyez/plugins/theharvester.py +127 -75
- souleyez/plugins/tr069.py +79 -57
- souleyez/plugins/upnp.py +65 -47
- souleyez/plugins/upnp_abuse.py +73 -55
- souleyez/plugins/vnc_access.py +129 -42
- souleyez/plugins/vnc_brute.py +109 -38
- souleyez/plugins/web_login_test.py +417 -0
- souleyez/plugins/whois.py +77 -58
- souleyez/plugins/wpscan.py +173 -69
- souleyez/reporting/__init__.py +2 -1
- souleyez/reporting/attack_chain.py +411 -346
- souleyez/reporting/charts.py +436 -501
- souleyez/reporting/compliance_mappings.py +334 -201
- souleyez/reporting/detection_report.py +126 -125
- souleyez/reporting/formatters.py +828 -591
- souleyez/reporting/generator.py +386 -302
- souleyez/reporting/metrics.py +72 -75
- souleyez/scanner.py +35 -29
- souleyez/security/__init__.py +37 -11
- souleyez/security/scope_validator.py +175 -106
- souleyez/security/validation.py +223 -149
- souleyez/security.py +22 -6
- souleyez/storage/credentials.py +247 -186
- souleyez/storage/crypto.py +296 -129
- souleyez/storage/database.py +73 -50
- souleyez/storage/db.py +58 -36
- souleyez/storage/deliverable_evidence.py +177 -128
- souleyez/storage/deliverable_exporter.py +282 -246
- souleyez/storage/deliverable_templates.py +134 -116
- souleyez/storage/deliverables.py +135 -130
- souleyez/storage/engagements.py +109 -56
- souleyez/storage/evidence.py +181 -152
- souleyez/storage/execution_log.py +31 -17
- souleyez/storage/exploit_attempts.py +93 -57
- souleyez/storage/exploits.py +67 -36
- souleyez/storage/findings.py +48 -61
- souleyez/storage/hosts.py +176 -144
- souleyez/storage/migrate_to_engagements.py +43 -19
- souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
- souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
- souleyez/storage/migrations/_003_add_execution_log.py +14 -8
- souleyez/storage/migrations/_005_screenshots.py +13 -5
- souleyez/storage/migrations/_006_deliverables.py +13 -5
- souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
- souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
- souleyez/storage/migrations/_010_evidence_linking.py +17 -10
- souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
- souleyez/storage/migrations/_012_team_collaboration.py +34 -21
- souleyez/storage/migrations/_013_add_host_tags.py +12 -6
- souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
- souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
- souleyez/storage/migrations/_016_add_domain_field.py +10 -4
- souleyez/storage/migrations/_017_msf_sessions.py +16 -8
- souleyez/storage/migrations/_018_add_osint_target.py +10 -6
- souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
- souleyez/storage/migrations/_020_add_rbac.py +36 -15
- souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
- souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
- souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
- souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
- souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
- souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
- souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
- souleyez/storage/migrations/__init__.py +26 -26
- souleyez/storage/migrations/migration_manager.py +19 -19
- souleyez/storage/msf_sessions.py +100 -65
- souleyez/storage/osint.py +17 -24
- souleyez/storage/recommendation_engine.py +269 -235
- souleyez/storage/screenshots.py +33 -32
- souleyez/storage/smb_shares.py +136 -92
- souleyez/storage/sqlmap_data.py +183 -128
- souleyez/storage/team_collaboration.py +135 -141
- souleyez/storage/timeline_tracker.py +122 -94
- souleyez/storage/wazuh_vulns.py +64 -66
- souleyez/storage/web_paths.py +33 -37
- souleyez/testing/credential_tester.py +221 -205
- souleyez/ui/__init__.py +1 -1
- souleyez/ui/ai_quotes.py +12 -12
- souleyez/ui/attack_surface.py +2439 -1516
- souleyez/ui/chain_rules_view.py +914 -382
- souleyez/ui/correlation_view.py +312 -230
- souleyez/ui/dashboard.py +2382 -1130
- souleyez/ui/deliverables_view.py +148 -62
- souleyez/ui/design_system.py +13 -13
- souleyez/ui/errors.py +49 -49
- souleyez/ui/evidence_linking_view.py +284 -179
- souleyez/ui/evidence_vault.py +393 -285
- souleyez/ui/exploit_suggestions_view.py +555 -349
- souleyez/ui/export_view.py +100 -66
- souleyez/ui/gap_analysis_view.py +315 -171
- souleyez/ui/help_system.py +105 -97
- souleyez/ui/intelligence_view.py +436 -293
- souleyez/ui/interactive.py +22827 -10678
- souleyez/ui/interactive_selector.py +75 -68
- souleyez/ui/log_formatter.py +47 -39
- souleyez/ui/menu_components.py +22 -13
- souleyez/ui/msf_auxiliary_menu.py +184 -133
- souleyez/ui/pending_chains_view.py +336 -172
- souleyez/ui/progress_indicators.py +5 -3
- souleyez/ui/recommendations_view.py +195 -137
- souleyez/ui/rule_builder.py +343 -225
- souleyez/ui/setup_wizard.py +678 -284
- souleyez/ui/shortcuts.py +217 -165
- souleyez/ui/splunk_gap_analysis_view.py +452 -270
- souleyez/ui/splunk_vulns_view.py +139 -86
- souleyez/ui/team_dashboard.py +498 -335
- souleyez/ui/template_selector.py +196 -105
- souleyez/ui/terminal.py +6 -6
- souleyez/ui/timeline_view.py +198 -127
- souleyez/ui/tool_setup.py +264 -164
- souleyez/ui/tutorial.py +202 -72
- souleyez/ui/tutorial_state.py +40 -40
- souleyez/ui/wazuh_vulns_view.py +235 -141
- souleyez/ui/wordlist_browser.py +260 -107
- souleyez/ui.py +464 -312
- souleyez/utils/tool_checker.py +427 -367
- souleyez/utils.py +33 -29
- souleyez/wordlists.py +134 -167
- {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/METADATA +1 -1
- souleyez-2.43.34.dist-info/RECORD +443 -0
- {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
- souleyez-2.43.29.dist-info/RECORD +0 -379
- {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
- {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/top_level.txt +0 -0
souleyez/plugins/gobuster.py
CHANGED
|
@@ -31,12 +31,12 @@ HELP = {
|
|
|
31
31
|
"- Respect rate limits and the target's rules of engagement — brute forcing can trigger alerts.\n"
|
|
32
32
|
"- Save findings (responses, status codes, and URLs) to the job log so you can convert them into Findings or follow-up tasks.\n"
|
|
33
33
|
),
|
|
34
|
-
"usage":
|
|
34
|
+
"usage": 'souleyez jobs enqueue gobuster <target> --args "dir -u <url> -w <wordlist> -t <threads>"',
|
|
35
35
|
"examples": [
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
'souleyez jobs enqueue gobuster http://example.com --args "dir -u http://example.com -w data/wordlists/web_dirs_common.txt -t 10"',
|
|
37
|
+
'souleyez jobs enqueue gobuster http://example.com --args "dir -u http://example.com -w data/wordlists/web_dirs_common.txt -x php,txt,html -t 20"',
|
|
38
|
+
'souleyez jobs enqueue gobuster example.com --args "dns --domain example.com -w data/wordlists/subdomains_common.txt -t 15 --timeout 3s"',
|
|
39
|
+
'souleyez jobs enqueue gobuster http://example.com --args "vhost -u http://example.com -w data/wordlists/subdomains_common.txt -t 50"',
|
|
40
40
|
],
|
|
41
41
|
"flags": [
|
|
42
42
|
["dir", "Directory/file enumeration mode"],
|
|
@@ -46,7 +46,10 @@ HELP = {
|
|
|
46
46
|
["--domain <domain>", "Target domain (dns mode)"],
|
|
47
47
|
["-w <wordlist>", "Wordlist path"],
|
|
48
48
|
["-t <threads>", "Number of threads"],
|
|
49
|
-
[
|
|
49
|
+
[
|
|
50
|
+
"--timeout <duration>",
|
|
51
|
+
"DNS timeout per request (e.g., 3s, 5s) - useful to avoid DNS timeouts",
|
|
52
|
+
],
|
|
50
53
|
["-x <extensions>", "File extensions to check (comma-separated)"],
|
|
51
54
|
["-b <codes>", "Status codes to blacklist"],
|
|
52
55
|
["--wildcard", "Force continued operation when wildcard found"],
|
|
@@ -55,96 +58,248 @@ HELP = {
|
|
|
55
58
|
"directory_enum": [
|
|
56
59
|
{
|
|
57
60
|
"name": "Quick Scan",
|
|
58
|
-
"args": [
|
|
59
|
-
|
|
61
|
+
"args": [
|
|
62
|
+
"dir",
|
|
63
|
+
"-u",
|
|
64
|
+
"<target>",
|
|
65
|
+
"-w",
|
|
66
|
+
"data/wordlists/web_dirs_common.txt",
|
|
67
|
+
"-t",
|
|
68
|
+
"10",
|
|
69
|
+
],
|
|
70
|
+
"desc": "Common wordlist (87 entries)",
|
|
60
71
|
},
|
|
61
72
|
{
|
|
62
73
|
"name": "Standard Scan",
|
|
63
|
-
"args": [
|
|
64
|
-
|
|
74
|
+
"args": [
|
|
75
|
+
"dir",
|
|
76
|
+
"-u",
|
|
77
|
+
"<target>",
|
|
78
|
+
"-w",
|
|
79
|
+
"data/wordlists/web_dirs_extended.txt",
|
|
80
|
+
"-t",
|
|
81
|
+
"20",
|
|
82
|
+
],
|
|
83
|
+
"desc": "Extended wordlist (more coverage)",
|
|
65
84
|
},
|
|
66
85
|
{
|
|
67
86
|
"name": "Common Files (HTML/PHP/TXT)",
|
|
68
|
-
"args": [
|
|
69
|
-
|
|
87
|
+
"args": [
|
|
88
|
+
"dir",
|
|
89
|
+
"-u",
|
|
90
|
+
"<target>",
|
|
91
|
+
"-w",
|
|
92
|
+
"data/wordlists/web_dirs_common.txt",
|
|
93
|
+
"-x",
|
|
94
|
+
"html,htm,php,txt,xml,json",
|
|
95
|
+
"-t",
|
|
96
|
+
"15",
|
|
97
|
+
],
|
|
98
|
+
"desc": "Common paths + web file extensions",
|
|
70
99
|
},
|
|
71
100
|
{
|
|
72
101
|
"name": "PHP Extensions",
|
|
73
|
-
"args": [
|
|
74
|
-
|
|
75
|
-
|
|
102
|
+
"args": [
|
|
103
|
+
"dir",
|
|
104
|
+
"-u",
|
|
105
|
+
"<target>",
|
|
106
|
+
"-w",
|
|
107
|
+
"data/wordlists/web_dirs_common.txt",
|
|
108
|
+
"-x",
|
|
109
|
+
"php,phps,php3,php4,php5,phtml",
|
|
110
|
+
"-t",
|
|
111
|
+
"15",
|
|
112
|
+
],
|
|
113
|
+
"desc": "Common paths + PHP variants",
|
|
114
|
+
},
|
|
76
115
|
],
|
|
77
116
|
"subdomain_enum": [
|
|
78
117
|
{
|
|
79
118
|
"name": "Subdomain Scan (manual domain)",
|
|
80
|
-
"args": [
|
|
81
|
-
|
|
119
|
+
"args": [
|
|
120
|
+
"dns",
|
|
121
|
+
"--domain",
|
|
122
|
+
"example.com",
|
|
123
|
+
"-w",
|
|
124
|
+
"data/wordlists/subdomains_common.txt",
|
|
125
|
+
"-t",
|
|
126
|
+
"15",
|
|
127
|
+
"--timeout",
|
|
128
|
+
"3s",
|
|
129
|
+
],
|
|
130
|
+
"desc": "Subdomain enumeration - EDIT example.com to your domain",
|
|
82
131
|
}
|
|
83
132
|
],
|
|
84
133
|
"vhost_discovery": [
|
|
85
134
|
{
|
|
86
135
|
"name": "Virtual Hosts",
|
|
87
|
-
"args": [
|
|
88
|
-
|
|
136
|
+
"args": [
|
|
137
|
+
"vhost",
|
|
138
|
+
"-u",
|
|
139
|
+
"<target>",
|
|
140
|
+
"-w",
|
|
141
|
+
"data/wordlists/subdomains_common.txt",
|
|
142
|
+
"-t",
|
|
143
|
+
"50",
|
|
144
|
+
],
|
|
145
|
+
"desc": "Virtual host discovery",
|
|
89
146
|
}
|
|
90
|
-
]
|
|
147
|
+
],
|
|
91
148
|
},
|
|
92
149
|
"presets": [
|
|
93
150
|
# Flattened list for backward compatibility
|
|
94
|
-
{
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
151
|
+
{
|
|
152
|
+
"name": "Quick Scan",
|
|
153
|
+
"args": [
|
|
154
|
+
"dir",
|
|
155
|
+
"-u",
|
|
156
|
+
"<target>",
|
|
157
|
+
"-w",
|
|
158
|
+
"data/wordlists/web_dirs_common.txt",
|
|
159
|
+
"-t",
|
|
160
|
+
"10",
|
|
161
|
+
],
|
|
162
|
+
"desc": "Common wordlist (87 entries)",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"name": "Standard Scan",
|
|
166
|
+
"args": [
|
|
167
|
+
"dir",
|
|
168
|
+
"-u",
|
|
169
|
+
"<target>",
|
|
170
|
+
"-w",
|
|
171
|
+
"data/wordlists/web_dirs_extended.txt",
|
|
172
|
+
"-t",
|
|
173
|
+
"20",
|
|
174
|
+
],
|
|
175
|
+
"desc": "Extended wordlist (more coverage)",
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"name": "Common Files (HTML/PHP/TXT)",
|
|
179
|
+
"args": [
|
|
180
|
+
"dir",
|
|
181
|
+
"-u",
|
|
182
|
+
"<target>",
|
|
183
|
+
"-w",
|
|
184
|
+
"data/wordlists/web_dirs_common.txt",
|
|
185
|
+
"-x",
|
|
186
|
+
"html,htm,php,txt,xml,json",
|
|
187
|
+
"-t",
|
|
188
|
+
"15",
|
|
189
|
+
],
|
|
190
|
+
"desc": "Common paths + web file extensions",
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"name": "PHP Extensions",
|
|
194
|
+
"args": [
|
|
195
|
+
"dir",
|
|
196
|
+
"-u",
|
|
197
|
+
"<target>",
|
|
198
|
+
"-w",
|
|
199
|
+
"data/wordlists/web_dirs_common.txt",
|
|
200
|
+
"-x",
|
|
201
|
+
"php,phps,php3,php4,php5,phtml",
|
|
202
|
+
"-t",
|
|
203
|
+
"15",
|
|
204
|
+
],
|
|
205
|
+
"desc": "Common paths + PHP variants",
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"name": "Subdomain Scan (manual domain)",
|
|
209
|
+
"args": [
|
|
210
|
+
"dns",
|
|
211
|
+
"--domain",
|
|
212
|
+
"example.com",
|
|
213
|
+
"-w",
|
|
214
|
+
"data/wordlists/subdomains_common.txt",
|
|
215
|
+
"-t",
|
|
216
|
+
"15",
|
|
217
|
+
"--timeout",
|
|
218
|
+
"3s",
|
|
219
|
+
],
|
|
220
|
+
"desc": "Subdomain enumeration - EDIT example.com to your domain",
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"name": "Virtual Hosts",
|
|
224
|
+
"args": [
|
|
225
|
+
"vhost",
|
|
226
|
+
"-u",
|
|
227
|
+
"<target>",
|
|
228
|
+
"-w",
|
|
229
|
+
"data/wordlists/subdomains_common.txt",
|
|
230
|
+
"-t",
|
|
231
|
+
"50",
|
|
232
|
+
],
|
|
233
|
+
"desc": "Virtual host discovery",
|
|
234
|
+
},
|
|
100
235
|
],
|
|
101
236
|
"help_sections": [
|
|
102
237
|
{
|
|
103
238
|
"title": "What is Gobuster?",
|
|
104
239
|
"color": "cyan",
|
|
105
240
|
"content": [
|
|
106
|
-
{
|
|
107
|
-
|
|
108
|
-
"
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
"
|
|
112
|
-
|
|
113
|
-
|
|
241
|
+
{
|
|
242
|
+
"title": "Overview",
|
|
243
|
+
"desc": "Gobuster is a fast directory, file, and DNS/vhost brute-forcing tool perfect for discovering hidden web content and subdomains.",
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"title": "Use Cases",
|
|
247
|
+
"desc": "Essential for web application reconnaissance",
|
|
248
|
+
"tips": [
|
|
249
|
+
"Find hidden directories and files (admin panels, config files)",
|
|
250
|
+
"Discover DNS subdomains",
|
|
251
|
+
"Enumerate virtual hosts",
|
|
252
|
+
"Identify forgotten endpoints and backups",
|
|
253
|
+
],
|
|
254
|
+
},
|
|
255
|
+
],
|
|
114
256
|
},
|
|
115
257
|
{
|
|
116
258
|
"title": "How to Use",
|
|
117
259
|
"color": "green",
|
|
118
260
|
"content": [
|
|
119
|
-
{
|
|
120
|
-
|
|
121
|
-
"
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
"
|
|
125
|
-
|
|
126
|
-
|
|
261
|
+
{
|
|
262
|
+
"title": "Basic Workflow",
|
|
263
|
+
"desc": "1. Start with common wordlist for quick wins\n 2. Use file extensions (-x) for targeted discovery\n 3. Expand to larger wordlists if needed\n 4. Follow up discovered paths with manual testing",
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
"title": "Modes & Features",
|
|
267
|
+
"desc": "Three main scanning modes",
|
|
268
|
+
"tips": [
|
|
269
|
+
"dir: Directory/file enumeration on web servers",
|
|
270
|
+
"dns: Subdomain brute-forcing",
|
|
271
|
+
"vhost: Virtual host discovery",
|
|
272
|
+
"Use -b to blacklist status codes (403, 404)",
|
|
273
|
+
],
|
|
274
|
+
},
|
|
275
|
+
],
|
|
127
276
|
},
|
|
128
277
|
{
|
|
129
278
|
"title": "Tips & Best Practices",
|
|
130
279
|
"color": "yellow",
|
|
131
280
|
"content": [
|
|
132
|
-
(
|
|
133
|
-
"
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
"
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
281
|
+
(
|
|
282
|
+
"Best Practices:",
|
|
283
|
+
[
|
|
284
|
+
"Start with small wordlists, expand as needed",
|
|
285
|
+
"Use -x for file extensions (php,txt,html)",
|
|
286
|
+
"Respect rate limits to avoid overwhelming targets",
|
|
287
|
+
"Save results and convert findings to job log",
|
|
288
|
+
"Combine with Nmap service scans for context",
|
|
289
|
+
],
|
|
290
|
+
),
|
|
291
|
+
(
|
|
292
|
+
"Common Issues:",
|
|
293
|
+
[
|
|
294
|
+
"Wildcard responses: Use -b to filter status codes",
|
|
295
|
+
"Too many results: Filter by size (-fs) or words (-fw)",
|
|
296
|
+
"DNS timeouts: Add --timeout 3s for DNS mode",
|
|
297
|
+
"No results: Try different wordlists or extensions",
|
|
298
|
+
],
|
|
299
|
+
),
|
|
300
|
+
],
|
|
301
|
+
},
|
|
302
|
+
],
|
|
148
303
|
}
|
|
149
304
|
|
|
150
305
|
|
|
@@ -167,18 +322,17 @@ class GobusterPlugin(PluginBase):
|
|
|
167
322
|
try:
|
|
168
323
|
# Use -v flag (not 'version' subcommand) - works on v3.x
|
|
169
324
|
result = subprocess.run(
|
|
170
|
-
["gobuster", "-v"],
|
|
171
|
-
capture_output=True,
|
|
172
|
-
text=True,
|
|
173
|
-
timeout=10
|
|
325
|
+
["gobuster", "-v"], capture_output=True, text=True, timeout=10
|
|
174
326
|
)
|
|
175
327
|
output = result.stdout + result.stderr
|
|
176
328
|
|
|
177
329
|
# Parse version from output like "gobuster version 3.8.2"
|
|
178
|
-
version_match = re.search(
|
|
330
|
+
version_match = re.search(
|
|
331
|
+
r"version\s+(\d+\.\d+\.\d+)", output, re.IGNORECASE
|
|
332
|
+
)
|
|
179
333
|
if version_match:
|
|
180
334
|
version = version_match.group(1)
|
|
181
|
-
major = int(version.split(
|
|
335
|
+
major = int(version.split(".")[0])
|
|
182
336
|
if major >= 3:
|
|
183
337
|
return (True, version, None)
|
|
184
338
|
else:
|
|
@@ -186,16 +340,15 @@ class GobusterPlugin(PluginBase):
|
|
|
186
340
|
|
|
187
341
|
# Also try --version flag as fallback
|
|
188
342
|
result2 = subprocess.run(
|
|
189
|
-
["gobuster", "--version"],
|
|
190
|
-
capture_output=True,
|
|
191
|
-
text=True,
|
|
192
|
-
timeout=10
|
|
343
|
+
["gobuster", "--version"], capture_output=True, text=True, timeout=10
|
|
193
344
|
)
|
|
194
345
|
output2 = result2.stdout + result2.stderr
|
|
195
|
-
version_match2 = re.search(
|
|
346
|
+
version_match2 = re.search(
|
|
347
|
+
r"version\s+(\d+\.\d+\.\d+)", output2, re.IGNORECASE
|
|
348
|
+
)
|
|
196
349
|
if version_match2:
|
|
197
350
|
version = version_match2.group(1)
|
|
198
|
-
major = int(version.split(
|
|
351
|
+
major = int(version.split(".")[0])
|
|
199
352
|
if major >= 3:
|
|
200
353
|
return (True, version, None)
|
|
201
354
|
else:
|
|
@@ -210,7 +363,11 @@ class GobusterPlugin(PluginBase):
|
|
|
210
363
|
return (True, "3.x", None)
|
|
211
364
|
|
|
212
365
|
except FileNotFoundError:
|
|
213
|
-
return (
|
|
366
|
+
return (
|
|
367
|
+
False,
|
|
368
|
+
None,
|
|
369
|
+
"ERROR: gobuster not found. Install with: sudo apt install gobuster",
|
|
370
|
+
)
|
|
214
371
|
except subprocess.TimeoutExpired:
|
|
215
372
|
return (True, "unknown", None) # Assume it works
|
|
216
373
|
except Exception as e:
|
|
@@ -231,7 +388,9 @@ class GobusterPlugin(PluginBase):
|
|
|
231
388
|
f"After upgrading, verify with: gobuster version\n"
|
|
232
389
|
)
|
|
233
390
|
|
|
234
|
-
def _preflight_check(
|
|
391
|
+
def _preflight_check(
|
|
392
|
+
self, base_url: str, timeout: float = 5.0, log_path: str = None
|
|
393
|
+
) -> Dict[str, Optional[str]]:
|
|
235
394
|
"""
|
|
236
395
|
Probe target with random UUID path to detect false positive responses.
|
|
237
396
|
|
|
@@ -248,7 +407,12 @@ class GobusterPlugin(PluginBase):
|
|
|
248
407
|
- reason: str or None (explanation)
|
|
249
408
|
- redirect_host: str or None (suggested target if host redirect detected)
|
|
250
409
|
"""
|
|
251
|
-
result = {
|
|
410
|
+
result = {
|
|
411
|
+
"exclude_length": None,
|
|
412
|
+
"exclude_status": None,
|
|
413
|
+
"reason": None,
|
|
414
|
+
"redirect_host": None,
|
|
415
|
+
}
|
|
252
416
|
|
|
253
417
|
# Generate random UUID path that definitely doesn't exist
|
|
254
418
|
test_path = str(uuid.uuid4())
|
|
@@ -263,7 +427,7 @@ class GobusterPlugin(PluginBase):
|
|
|
263
427
|
test_url,
|
|
264
428
|
timeout=timeout,
|
|
265
429
|
allow_redirects=False,
|
|
266
|
-
headers={
|
|
430
|
+
headers={"User-Agent": "gobuster/3.6"}, # Match gobuster's UA
|
|
267
431
|
)
|
|
268
432
|
|
|
269
433
|
# 404 is expected for non-existent paths - no action needed
|
|
@@ -272,7 +436,7 @@ class GobusterPlugin(PluginBase):
|
|
|
272
436
|
|
|
273
437
|
# Check for host-level redirects (301/302/307/308)
|
|
274
438
|
if resp.status_code in [301, 302, 307, 308]:
|
|
275
|
-
location = resp.headers.get(
|
|
439
|
+
location = resp.headers.get("Location", "")
|
|
276
440
|
if location:
|
|
277
441
|
# Parse the redirect location
|
|
278
442
|
redirect_parsed = urlparse(location)
|
|
@@ -282,24 +446,30 @@ class GobusterPlugin(PluginBase):
|
|
|
282
446
|
if redirect_host and redirect_host != original_host:
|
|
283
447
|
# This is a host-level redirect (e.g., non-www to www)
|
|
284
448
|
suggested_url = f"{redirect_parsed.scheme or original_parsed.scheme}://{redirect_host}"
|
|
285
|
-
result[
|
|
286
|
-
result[
|
|
287
|
-
result[
|
|
449
|
+
result["redirect_host"] = suggested_url
|
|
450
|
+
result["exclude_status"] = str(resp.status_code)
|
|
451
|
+
result["reason"] = (
|
|
288
452
|
f"Host redirect detected: {original_host} → {redirect_host}"
|
|
289
453
|
)
|
|
290
454
|
|
|
291
455
|
if log_path:
|
|
292
|
-
with open(log_path,
|
|
456
|
+
with open(log_path, "a") as f:
|
|
293
457
|
f.write(f"\n{'=' * 70}\n")
|
|
294
458
|
f.write("⚠️ HOST-LEVEL REDIRECT DETECTED\n")
|
|
295
459
|
f.write(f"{'=' * 70}\n")
|
|
296
460
|
f.write(f"Target: {base_url}\n")
|
|
297
461
|
f.write(f"Redirects to: {redirect_host}\n")
|
|
298
462
|
f.write(f"Status: {resp.status_code}\n\n")
|
|
299
|
-
f.write(
|
|
463
|
+
f.write(
|
|
464
|
+
"The server redirects ALL requests to a different host.\n"
|
|
465
|
+
)
|
|
300
466
|
f.write("This causes unreliable results because:\n")
|
|
301
|
-
f.write(
|
|
302
|
-
|
|
467
|
+
f.write(
|
|
468
|
+
" - Response sizes vary based on path length in redirect URL\n"
|
|
469
|
+
)
|
|
470
|
+
f.write(
|
|
471
|
+
" - Gobuster may report false positives or miss real paths\n\n"
|
|
472
|
+
)
|
|
303
473
|
# Parseable marker for auto-retry
|
|
304
474
|
f.write(f"HOST_REDIRECT_TARGET: {suggested_url}\n\n")
|
|
305
475
|
f.write("Auto-retrying with corrected target...\n")
|
|
@@ -307,27 +477,29 @@ class GobusterPlugin(PluginBase):
|
|
|
307
477
|
|
|
308
478
|
# Still try to exclude the response length for this scan
|
|
309
479
|
content_length = len(resp.content)
|
|
310
|
-
result[
|
|
480
|
+
result["exclude_length"] = str(content_length)
|
|
311
481
|
return result
|
|
312
482
|
|
|
313
483
|
# Any other status for a random UUID = false positive indicator
|
|
314
484
|
# Common: 403 (blocked), 401 (auth required), 200 (catch-all), 500 (error page)
|
|
315
485
|
if resp.status_code in [200, 301, 302, 400, 401, 403, 500, 503]:
|
|
316
486
|
content_length = len(resp.content)
|
|
317
|
-
result[
|
|
318
|
-
result[
|
|
319
|
-
result[
|
|
487
|
+
result["exclude_length"] = str(content_length)
|
|
488
|
+
result["exclude_status"] = str(resp.status_code)
|
|
489
|
+
result["reason"] = (
|
|
320
490
|
f"Pre-flight: Random path returned {resp.status_code} "
|
|
321
491
|
f"({content_length} bytes) - auto-excluding"
|
|
322
492
|
)
|
|
323
493
|
|
|
324
494
|
if log_path:
|
|
325
|
-
with open(log_path,
|
|
495
|
+
with open(log_path, "a") as f:
|
|
326
496
|
f.write(f"\n{'=' * 60}\n")
|
|
327
497
|
f.write("PRE-FLIGHT CHECK\n")
|
|
328
498
|
f.write(f"{'=' * 60}\n")
|
|
329
499
|
f.write(f"Tested: {test_url}\n")
|
|
330
|
-
f.write(
|
|
500
|
+
f.write(
|
|
501
|
+
f"Result: {resp.status_code} ({content_length} bytes)\n"
|
|
502
|
+
)
|
|
331
503
|
f.write(f"Action: Adding --exclude-length {content_length}\n")
|
|
332
504
|
f.write(f"{'=' * 60}\n\n")
|
|
333
505
|
|
|
@@ -336,7 +508,7 @@ class GobusterPlugin(PluginBase):
|
|
|
336
508
|
except requests.exceptions.Timeout:
|
|
337
509
|
# Target too slow, skip preflight
|
|
338
510
|
if log_path:
|
|
339
|
-
with open(log_path,
|
|
511
|
+
with open(log_path, "a") as f:
|
|
340
512
|
f.write("Pre-flight: Target timeout, skipping check\n")
|
|
341
513
|
return result
|
|
342
514
|
except requests.exceptions.ConnectionError:
|
|
@@ -346,7 +518,9 @@ class GobusterPlugin(PluginBase):
|
|
|
346
518
|
# Any other error, continue without exclusions
|
|
347
519
|
return result
|
|
348
520
|
|
|
349
|
-
def build_command(
|
|
521
|
+
def build_command(
|
|
522
|
+
self, target: str, args: List[str] = None, label: str = "", log_path: str = None
|
|
523
|
+
):
|
|
350
524
|
"""Build gobuster command for background execution with PID tracking."""
|
|
351
525
|
args = args or []
|
|
352
526
|
|
|
@@ -354,55 +528,57 @@ class GobusterPlugin(PluginBase):
|
|
|
354
528
|
meets_req, version, error_msg = self._check_version()
|
|
355
529
|
if not meets_req:
|
|
356
530
|
if log_path:
|
|
357
|
-
with open(log_path,
|
|
531
|
+
with open(log_path, "w") as f:
|
|
358
532
|
f.write(error_msg)
|
|
359
533
|
return None
|
|
360
534
|
|
|
361
535
|
# Detect the mode from args
|
|
362
536
|
mode = None
|
|
363
|
-
if
|
|
364
|
-
mode =
|
|
365
|
-
elif
|
|
366
|
-
mode =
|
|
367
|
-
elif
|
|
368
|
-
mode =
|
|
369
|
-
|
|
537
|
+
if "dir" in args:
|
|
538
|
+
mode = "dir"
|
|
539
|
+
elif "dns" in args:
|
|
540
|
+
mode = "dns"
|
|
541
|
+
elif "vhost" in args:
|
|
542
|
+
mode = "vhost"
|
|
543
|
+
|
|
370
544
|
# Validate target and mode compatibility (same validation as run())
|
|
371
|
-
if mode ==
|
|
372
|
-
if target.startswith((
|
|
545
|
+
if mode == "dns":
|
|
546
|
+
if target.startswith(("http://", "https://")):
|
|
373
547
|
if log_path:
|
|
374
|
-
with open(log_path,
|
|
548
|
+
with open(log_path, "w") as f:
|
|
375
549
|
f.write("ERROR: DNS mode requires a domain name, not a URL\n")
|
|
376
550
|
return None
|
|
377
|
-
|
|
378
|
-
ip_pattern = re.compile(r
|
|
551
|
+
|
|
552
|
+
ip_pattern = re.compile(r"^(\d{1,3}\.){3}\d{1,3}$")
|
|
379
553
|
if ip_pattern.match(target):
|
|
380
554
|
if log_path:
|
|
381
|
-
with open(log_path,
|
|
382
|
-
f.write(
|
|
555
|
+
with open(log_path, "w") as f:
|
|
556
|
+
f.write(
|
|
557
|
+
"ERROR: DNS mode requires a domain name, not an IP address\n"
|
|
558
|
+
)
|
|
383
559
|
return None
|
|
384
|
-
|
|
385
|
-
elif mode in [
|
|
386
|
-
if not target.startswith((
|
|
560
|
+
|
|
561
|
+
elif mode in ["dir", "vhost"]:
|
|
562
|
+
if not target.startswith(("http://", "https://")):
|
|
387
563
|
target = f"http://{target}"
|
|
388
|
-
|
|
564
|
+
|
|
389
565
|
try:
|
|
390
566
|
target = validate_url(target)
|
|
391
567
|
except ValidationError as e:
|
|
392
568
|
if log_path:
|
|
393
|
-
with open(log_path,
|
|
569
|
+
with open(log_path, "w") as f:
|
|
394
570
|
f.write(f"ERROR: Invalid URL: {e}\n")
|
|
395
571
|
return None
|
|
396
|
-
|
|
572
|
+
|
|
397
573
|
else:
|
|
398
|
-
if not target.startswith((
|
|
574
|
+
if not target.startswith(("http://", "https://")):
|
|
399
575
|
target = f"http://{target}"
|
|
400
|
-
|
|
576
|
+
|
|
401
577
|
try:
|
|
402
578
|
target = validate_url(target)
|
|
403
579
|
except ValidationError as e:
|
|
404
580
|
if log_path:
|
|
405
|
-
with open(log_path,
|
|
581
|
+
with open(log_path, "w") as f:
|
|
406
582
|
f.write(f"ERROR: Invalid URL: {e}\n")
|
|
407
583
|
return None
|
|
408
584
|
|
|
@@ -410,11 +586,11 @@ class GobusterPlugin(PluginBase):
|
|
|
410
586
|
|
|
411
587
|
# Pre-flight check for dir/vhost modes: detect false positive responses
|
|
412
588
|
# This prevents gobuster from failing on servers that return 403/401/200 for all paths
|
|
413
|
-
if mode in [
|
|
589
|
+
if mode in ["dir", "vhost", None]: # None = default to dir behavior
|
|
414
590
|
# Extract base URL from -u argument or use target
|
|
415
591
|
base_url = target
|
|
416
592
|
for i, arg in enumerate(processed_args):
|
|
417
|
-
if arg ==
|
|
593
|
+
if arg == "-u" and i + 1 < len(processed_args):
|
|
418
594
|
base_url = processed_args[i + 1]
|
|
419
595
|
break
|
|
420
596
|
|
|
@@ -423,49 +599,54 @@ class GobusterPlugin(PluginBase):
|
|
|
423
599
|
|
|
424
600
|
# If host redirect detected, abort scan immediately
|
|
425
601
|
# Don't waste time running on wrong target - result_handler will spawn retry
|
|
426
|
-
if preflight.get(
|
|
602
|
+
if preflight.get("redirect_host"):
|
|
427
603
|
if log_path:
|
|
428
|
-
with open(log_path,
|
|
604
|
+
with open(log_path, "a") as f:
|
|
429
605
|
f.write("\n=== SCAN ABORTED ===\n")
|
|
430
|
-
f.write(
|
|
431
|
-
|
|
432
|
-
|
|
606
|
+
f.write(
|
|
607
|
+
"Host redirect detected. Aborting to avoid wasted scan time.\n"
|
|
608
|
+
)
|
|
609
|
+
f.write(
|
|
610
|
+
"A retry job will be auto-queued with the correct target.\n"
|
|
611
|
+
)
|
|
612
|
+
f.write(
|
|
613
|
+
f"=== Completed: {time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime())} ===\n"
|
|
614
|
+
)
|
|
433
615
|
return None # Abort - background.py will check log for HOST_REDIRECT_TARGET
|
|
434
616
|
|
|
435
|
-
if preflight[
|
|
617
|
+
if preflight["exclude_length"]:
|
|
436
618
|
# Collect existing exclusions
|
|
437
619
|
existing_excludes = set()
|
|
438
620
|
exclude_idx = None
|
|
439
621
|
for i, arg in enumerate(processed_args):
|
|
440
|
-
if arg ==
|
|
622
|
+
if arg == "--exclude-length" and i + 1 < len(processed_args):
|
|
441
623
|
exclude_idx = i
|
|
442
|
-
existing_excludes.update(processed_args[i + 1].split(
|
|
624
|
+
existing_excludes.update(processed_args[i + 1].split(","))
|
|
443
625
|
|
|
444
626
|
# Add detected length if not already excluded
|
|
445
|
-
if preflight[
|
|
446
|
-
existing_excludes.add(preflight[
|
|
447
|
-
merged =
|
|
627
|
+
if preflight["exclude_length"] not in existing_excludes:
|
|
628
|
+
existing_excludes.add(preflight["exclude_length"])
|
|
629
|
+
merged = ",".join(sorted(existing_excludes))
|
|
448
630
|
|
|
449
631
|
if exclude_idx is not None:
|
|
450
632
|
# Update existing --exclude-length value
|
|
451
633
|
processed_args[exclude_idx + 1] = merged
|
|
452
634
|
else:
|
|
453
635
|
# Add new --exclude-length
|
|
454
|
-
processed_args.extend([
|
|
636
|
+
processed_args.extend(["--exclude-length", merged])
|
|
455
637
|
|
|
456
638
|
# Add --no-progress to suppress verbose progress output (gobuster v3.6+)
|
|
457
639
|
# This prevents thousands of "Progress: X / Y" lines in output
|
|
458
|
-
if
|
|
459
|
-
processed_args.append(
|
|
640
|
+
if "--no-progress" not in processed_args:
|
|
641
|
+
processed_args.append("--no-progress")
|
|
460
642
|
|
|
461
643
|
cmd = ["gobuster"] + processed_args
|
|
462
644
|
|
|
463
|
-
return {
|
|
464
|
-
'cmd': cmd,
|
|
465
|
-
'timeout': 1800 # 30 minutes
|
|
466
|
-
}
|
|
645
|
+
return {"cmd": cmd, "timeout": 1800} # 30 minutes
|
|
467
646
|
|
|
468
|
-
def run(
|
|
647
|
+
def run(
|
|
648
|
+
self, target: str, args: List[str] = None, label: str = "", log_path: str = None
|
|
649
|
+
) -> int:
|
|
469
650
|
"""Execute gobuster scan and write output to log_path."""
|
|
470
651
|
|
|
471
652
|
args = args or []
|
|
@@ -474,23 +655,23 @@ class GobusterPlugin(PluginBase):
|
|
|
474
655
|
meets_req, version, error_msg = self._check_version()
|
|
475
656
|
if not meets_req:
|
|
476
657
|
if log_path:
|
|
477
|
-
with open(log_path,
|
|
658
|
+
with open(log_path, "w") as f:
|
|
478
659
|
f.write(error_msg)
|
|
479
660
|
return 1
|
|
480
661
|
|
|
481
662
|
# Detect the mode from args
|
|
482
663
|
mode = None
|
|
483
|
-
if
|
|
484
|
-
mode =
|
|
485
|
-
elif
|
|
486
|
-
mode =
|
|
487
|
-
elif
|
|
488
|
-
mode =
|
|
489
|
-
|
|
664
|
+
if "dir" in args:
|
|
665
|
+
mode = "dir"
|
|
666
|
+
elif "dns" in args:
|
|
667
|
+
mode = "dns"
|
|
668
|
+
elif "vhost" in args:
|
|
669
|
+
mode = "vhost"
|
|
670
|
+
|
|
490
671
|
# Validate target and mode compatibility
|
|
491
|
-
if mode ==
|
|
672
|
+
if mode == "dns":
|
|
492
673
|
# DNS mode requires a domain name, not an IP or URL
|
|
493
|
-
if target.startswith((
|
|
674
|
+
if target.startswith(("http://", "https://")):
|
|
494
675
|
error_msg = (
|
|
495
676
|
"ERROR: DNS mode requires a domain name, not a URL.\n\n"
|
|
496
677
|
f"You provided: {target}\n"
|
|
@@ -498,19 +679,19 @@ class GobusterPlugin(PluginBase):
|
|
|
498
679
|
"FIXES:\n"
|
|
499
680
|
"1. For DNS enumeration:\n"
|
|
500
681
|
" - Use: souleyez jobs enqueue gobuster <domain>\n"
|
|
501
|
-
|
|
682
|
+
' - Example: souleyez jobs enqueue gobuster vulnweb.com --args "dns --domain vulnweb.com -w <wordlist>"\n\n'
|
|
502
683
|
"2. For directory enumeration of an IP/URL:\n"
|
|
503
|
-
|
|
504
|
-
|
|
684
|
+
' - Use: souleyez jobs enqueue gobuster <ip-or-url> --args "dir -u http://<ip> -w <wordlist>"\n'
|
|
685
|
+
' - Example: souleyez jobs enqueue gobuster 44.228.249.3 --args "dir -u http://44.228.249.3 -w <wordlist>"\n'
|
|
505
686
|
)
|
|
506
687
|
if log_path:
|
|
507
|
-
with open(log_path,
|
|
688
|
+
with open(log_path, "w") as f:
|
|
508
689
|
f.write(error_msg)
|
|
509
690
|
return 1
|
|
510
691
|
raise ValueError(error_msg)
|
|
511
|
-
|
|
692
|
+
|
|
512
693
|
# Check if target is an IP address
|
|
513
|
-
ip_pattern = re.compile(r
|
|
694
|
+
ip_pattern = re.compile(r"^(\d{1,3}\.){3}\d{1,3}$")
|
|
514
695
|
if ip_pattern.match(target):
|
|
515
696
|
error_msg = (
|
|
516
697
|
f"ERROR: DNS mode requires a domain name, not an IP address.\n\n"
|
|
@@ -519,41 +700,41 @@ class GobusterPlugin(PluginBase):
|
|
|
519
700
|
"IP addresses don't have subdomains.\n\n"
|
|
520
701
|
"FIXES:\n"
|
|
521
702
|
"1. For directory enumeration of the IP:\n"
|
|
522
|
-
f
|
|
703
|
+
f' souleyez jobs enqueue gobuster {target} --args "dir -u http://{target} -w <wordlist>"\n\n'
|
|
523
704
|
"2. For DNS enumeration (if you have a domain):\n"
|
|
524
|
-
|
|
705
|
+
' souleyez jobs enqueue gobuster example.com --args "dns --domain example.com -w <wordlist>"\n'
|
|
525
706
|
)
|
|
526
707
|
if log_path:
|
|
527
|
-
with open(log_path,
|
|
708
|
+
with open(log_path, "w") as f:
|
|
528
709
|
f.write(error_msg)
|
|
529
710
|
return 1
|
|
530
711
|
raise ValueError(error_msg)
|
|
531
|
-
|
|
532
|
-
elif mode in [
|
|
712
|
+
|
|
713
|
+
elif mode in ["dir", "vhost"]:
|
|
533
714
|
# Dir/vhost modes need a URL with protocol
|
|
534
|
-
if not target.startswith((
|
|
715
|
+
if not target.startswith(("http://", "https://")):
|
|
535
716
|
target = f"http://{target}"
|
|
536
|
-
|
|
717
|
+
|
|
537
718
|
# Validate URL
|
|
538
719
|
try:
|
|
539
720
|
target = validate_url(target)
|
|
540
721
|
except ValidationError as e:
|
|
541
722
|
if log_path:
|
|
542
|
-
with open(log_path,
|
|
723
|
+
with open(log_path, "w") as f:
|
|
543
724
|
f.write(f"ERROR: Invalid URL: {e}\n")
|
|
544
725
|
return 1
|
|
545
726
|
raise ValueError(f"Invalid URL: {e}")
|
|
546
|
-
|
|
727
|
+
|
|
547
728
|
else:
|
|
548
729
|
# No mode detected, try to handle as URL
|
|
549
|
-
if not target.startswith((
|
|
730
|
+
if not target.startswith(("http://", "https://")):
|
|
550
731
|
target = f"http://{target}"
|
|
551
|
-
|
|
732
|
+
|
|
552
733
|
try:
|
|
553
734
|
target = validate_url(target)
|
|
554
735
|
except ValidationError as e:
|
|
555
736
|
if log_path:
|
|
556
|
-
with open(log_path,
|
|
737
|
+
with open(log_path, "w") as f:
|
|
557
738
|
f.write(f"ERROR: Invalid URL: {e}\n")
|
|
558
739
|
return 1
|
|
559
740
|
raise ValueError(f"Invalid URL: {e}")
|
|
@@ -561,42 +742,44 @@ class GobusterPlugin(PluginBase):
|
|
|
561
742
|
processed_args = [arg.replace("<target>", target) for arg in args]
|
|
562
743
|
|
|
563
744
|
# Pre-flight check for dir/vhost modes: detect false positive responses
|
|
564
|
-
if mode in [
|
|
745
|
+
if mode in ["dir", "vhost", None]:
|
|
565
746
|
base_url = target
|
|
566
747
|
for i, arg in enumerate(processed_args):
|
|
567
|
-
if arg ==
|
|
748
|
+
if arg == "-u" and i + 1 < len(processed_args):
|
|
568
749
|
base_url = processed_args[i + 1]
|
|
569
750
|
break
|
|
570
751
|
|
|
571
752
|
# Always run preflight - merge detected length with any existing exclusions
|
|
572
753
|
preflight = self._preflight_check(base_url, timeout=5.0, log_path=log_path)
|
|
573
|
-
if preflight[
|
|
754
|
+
if preflight["exclude_length"]:
|
|
574
755
|
existing_excludes = set()
|
|
575
756
|
exclude_idx = None
|
|
576
757
|
for i, arg in enumerate(processed_args):
|
|
577
|
-
if arg ==
|
|
758
|
+
if arg == "--exclude-length" and i + 1 < len(processed_args):
|
|
578
759
|
exclude_idx = i
|
|
579
|
-
existing_excludes.update(processed_args[i + 1].split(
|
|
760
|
+
existing_excludes.update(processed_args[i + 1].split(","))
|
|
580
761
|
|
|
581
|
-
if preflight[
|
|
582
|
-
existing_excludes.add(preflight[
|
|
583
|
-
merged =
|
|
762
|
+
if preflight["exclude_length"] not in existing_excludes:
|
|
763
|
+
existing_excludes.add(preflight["exclude_length"])
|
|
764
|
+
merged = ",".join(sorted(existing_excludes))
|
|
584
765
|
|
|
585
766
|
if exclude_idx is not None:
|
|
586
767
|
processed_args[exclude_idx + 1] = merged
|
|
587
768
|
else:
|
|
588
|
-
processed_args.extend([
|
|
769
|
+
processed_args.extend(["--exclude-length", merged])
|
|
589
770
|
|
|
590
771
|
# Add --no-progress to suppress verbose progress output (gobuster v3.6+)
|
|
591
772
|
# This prevents thousands of "Progress: X / Y" lines in output
|
|
592
|
-
if
|
|
593
|
-
processed_args.append(
|
|
773
|
+
if "--no-progress" not in processed_args:
|
|
774
|
+
processed_args.append("--no-progress")
|
|
594
775
|
|
|
595
776
|
cmd = ["gobuster"] + processed_args
|
|
596
777
|
|
|
597
778
|
if not log_path:
|
|
598
779
|
try:
|
|
599
|
-
proc = subprocess.run(
|
|
780
|
+
proc = subprocess.run(
|
|
781
|
+
cmd, capture_output=True, timeout=300, check=False
|
|
782
|
+
)
|
|
600
783
|
return proc.returncode
|
|
601
784
|
except Exception:
|
|
602
785
|
return 1
|
|
@@ -604,7 +787,9 @@ class GobusterPlugin(PluginBase):
|
|
|
604
787
|
try:
|
|
605
788
|
with open(log_path, "a", encoding="utf-8", errors="replace") as fh:
|
|
606
789
|
fh.write(f"Command: {' '.join(cmd)}\n")
|
|
607
|
-
fh.write(
|
|
790
|
+
fh.write(
|
|
791
|
+
f"Started: {time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime())}\n\n"
|
|
792
|
+
)
|
|
608
793
|
fh.flush()
|
|
609
794
|
|
|
610
795
|
proc = subprocess.run(
|
|
@@ -613,29 +798,39 @@ class GobusterPlugin(PluginBase):
|
|
|
613
798
|
stderr=subprocess.STDOUT,
|
|
614
799
|
timeout=300,
|
|
615
800
|
check=False,
|
|
616
|
-
text=True
|
|
801
|
+
text=True,
|
|
617
802
|
)
|
|
618
|
-
|
|
803
|
+
|
|
619
804
|
output = proc.stdout
|
|
620
805
|
fh.write(output)
|
|
621
806
|
|
|
622
|
-
fh.write(
|
|
807
|
+
fh.write(
|
|
808
|
+
f"\nCompleted: {time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime())}\n"
|
|
809
|
+
)
|
|
623
810
|
fh.write(f"Exit Code: {proc.returncode}\n")
|
|
624
|
-
|
|
811
|
+
|
|
625
812
|
# Check for wildcard error in output
|
|
626
|
-
if
|
|
813
|
+
if (
|
|
814
|
+
proc.returncode == 1
|
|
815
|
+
and "the server returns a status code that matches"
|
|
816
|
+
in output.lower()
|
|
817
|
+
):
|
|
627
818
|
# Extract details from error message
|
|
628
|
-
status_match = re.search(r
|
|
629
|
-
length_match = re.search(r
|
|
630
|
-
|
|
819
|
+
status_match = re.search(r"=> (\d{3})", output)
|
|
820
|
+
length_match = re.search(r"\(Length: (\d+)\)", output)
|
|
821
|
+
|
|
631
822
|
status_code = status_match.group(1) if status_match else "403"
|
|
632
823
|
length = length_match.group(1) if length_match else "unknown"
|
|
633
|
-
|
|
824
|
+
|
|
634
825
|
fh.write("\n" + "=" * 70 + "\n")
|
|
635
826
|
fh.write("⚠️ WILDCARD RESPONSE DETECTED\n")
|
|
636
827
|
fh.write("=" * 70 + "\n\n")
|
|
637
|
-
fh.write(
|
|
638
|
-
|
|
828
|
+
fh.write(
|
|
829
|
+
"The server is returning the same response for all URLs (wildcard).\n"
|
|
830
|
+
)
|
|
831
|
+
fh.write(
|
|
832
|
+
"Gobuster cannot differentiate between real and fake directories.\n\n"
|
|
833
|
+
)
|
|
639
834
|
fh.write(f"Detected Response:\n")
|
|
640
835
|
fh.write(f" Status Code: {status_code}\n")
|
|
641
836
|
fh.write(f" Response Length: {length} bytes\n\n")
|
|
@@ -648,12 +843,14 @@ class GobusterPlugin(PluginBase):
|
|
|
648
843
|
fh.write(" Add: --wildcard\n\n")
|
|
649
844
|
fh.write("RETRY COMMAND:\n")
|
|
650
845
|
retry_cmd = cmd.copy()
|
|
651
|
-
retry_cmd.extend([
|
|
846
|
+
retry_cmd.extend(["-b", status_code])
|
|
652
847
|
fh.write(f" {' '.join(retry_cmd)}\n\n")
|
|
653
|
-
fh.write(
|
|
848
|
+
fh.write(
|
|
849
|
+
"TIP: You can add these flags when configuring the scan in the\n"
|
|
850
|
+
)
|
|
654
851
|
fh.write(" interactive menu under 'Additional flags'.\n")
|
|
655
852
|
fh.write("=" * 70 + "\n")
|
|
656
|
-
|
|
853
|
+
|
|
657
854
|
# Treat wildcard detection (exit 1) as informational success
|
|
658
855
|
# since we've provided helpful guidance
|
|
659
856
|
return 0 if proc.returncode in [0, 1] else proc.returncode
|