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
|
@@ -61,18 +61,22 @@ def parse_wpscan_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
61
61
|
}
|
|
62
62
|
"""
|
|
63
63
|
result = {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
"target_url": target,
|
|
65
|
+
"wordpress_version": None,
|
|
66
|
+
"version_status": None, # 'Insecure', 'Outdated', etc.
|
|
67
|
+
"version_release_date": None,
|
|
68
|
+
"users": [],
|
|
69
|
+
"findings": [],
|
|
70
|
+
"plugins": [],
|
|
71
|
+
"themes": [],
|
|
72
|
+
"info": [], # Additional findings (multisite, wp-cron, readme, headers, etc.)
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
# Strip ANSI escape codes from output (wpscan uses colored output)
|
|
76
|
+
ansi_escape = re.compile(r"\x1b\[[0-9;]*m|\[[\d;]*m")
|
|
77
|
+
output = ansi_escape.sub("", output)
|
|
78
|
+
|
|
79
|
+
lines = output.split("\n")
|
|
76
80
|
current_section = None
|
|
77
81
|
# current_plugin and current_theme not used currently
|
|
78
82
|
|
|
@@ -80,173 +84,233 @@ def parse_wpscan_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
80
84
|
line_stripped = line.strip()
|
|
81
85
|
|
|
82
86
|
# Extract WordPress version with status
|
|
83
|
-
if
|
|
84
|
-
|
|
87
|
+
if "WordPress version" in line or line_stripped.startswith(
|
|
88
|
+
"[+] WordPress version"
|
|
89
|
+
):
|
|
90
|
+
version_match = re.search(
|
|
91
|
+
r"WordPress version[:\s]+(\d+\.\d+(?:\.\d+)?)", line
|
|
92
|
+
)
|
|
85
93
|
if version_match:
|
|
86
|
-
result[
|
|
94
|
+
result["wordpress_version"] = version_match.group(1)
|
|
87
95
|
|
|
88
96
|
# Check for insecure/outdated status
|
|
89
|
-
if
|
|
90
|
-
result[
|
|
91
|
-
elif
|
|
92
|
-
result[
|
|
97
|
+
if "Insecure" in line:
|
|
98
|
+
result["version_status"] = "Insecure"
|
|
99
|
+
elif "Outdated" in line:
|
|
100
|
+
result["version_status"] = "Outdated"
|
|
93
101
|
|
|
94
102
|
# Extract release date
|
|
95
|
-
date_match = re.search(r
|
|
103
|
+
date_match = re.search(r"released on (\d{4}-\d{2}-\d{2})", line)
|
|
96
104
|
if date_match:
|
|
97
|
-
result[
|
|
105
|
+
result["version_release_date"] = date_match.group(1)
|
|
98
106
|
|
|
99
107
|
# Detect multisite
|
|
100
|
-
if
|
|
108
|
+
if "seems to be a multisite" in line.lower():
|
|
101
109
|
# Check if not already added
|
|
102
|
-
if not any(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
if not any(
|
|
111
|
+
item["title"] == "WordPress Multisite Detected"
|
|
112
|
+
for item in result["info"]
|
|
113
|
+
):
|
|
114
|
+
result["info"].append(
|
|
115
|
+
{
|
|
116
|
+
"type": "config",
|
|
117
|
+
"title": "WordPress Multisite Detected",
|
|
118
|
+
"description": "This installation is configured as a WordPress multisite network.",
|
|
119
|
+
"severity": "info",
|
|
120
|
+
}
|
|
121
|
+
)
|
|
109
122
|
|
|
110
123
|
# Detect WP-Cron
|
|
111
|
-
if
|
|
112
|
-
if not any(
|
|
113
|
-
result[
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
if "external WP-Cron seems to be enabled" in line.lower():
|
|
125
|
+
if not any(
|
|
126
|
+
item["title"] == "External WP-Cron Enabled" for item in result["info"]
|
|
127
|
+
):
|
|
128
|
+
result["info"].append(
|
|
129
|
+
{
|
|
130
|
+
"type": "config",
|
|
131
|
+
"title": "External WP-Cron Enabled",
|
|
132
|
+
"description": "The external WP-Cron endpoint is accessible.",
|
|
133
|
+
"severity": "low",
|
|
134
|
+
}
|
|
135
|
+
)
|
|
119
136
|
|
|
120
137
|
# Detect README file
|
|
121
|
-
if
|
|
122
|
-
if not any(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
138
|
+
if "readme found:" in line.lower() and "readme.html" in line.lower():
|
|
139
|
+
if not any(
|
|
140
|
+
item["title"] == "WordPress README File Exposed"
|
|
141
|
+
for item in result["info"]
|
|
142
|
+
):
|
|
143
|
+
result["info"].append(
|
|
144
|
+
{
|
|
145
|
+
"type": "disclosure",
|
|
146
|
+
"title": "WordPress README File Exposed",
|
|
147
|
+
"description": "The WordPress readme.html file is accessible, which can reveal version information.",
|
|
148
|
+
"severity": "low",
|
|
149
|
+
}
|
|
150
|
+
)
|
|
129
151
|
|
|
130
152
|
# Detect interesting headers
|
|
131
|
-
if line_stripped.startswith(
|
|
132
|
-
|
|
153
|
+
if line_stripped.startswith("|") and any(
|
|
154
|
+
header in line for header in ["X-Mod-Pagespeed", "X-Powered-By", "Server:"]
|
|
155
|
+
):
|
|
156
|
+
header_match = re.search(r"\|\s+[-\s]+([^:]+):\s+(.+)", line)
|
|
133
157
|
if header_match:
|
|
134
158
|
header_name = header_match.group(1).strip()
|
|
135
159
|
header_value = header_match.group(2).strip()
|
|
136
|
-
header_title = f
|
|
137
|
-
if not any(item[
|
|
138
|
-
result[
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
160
|
+
header_title = f"HTTP Header: {header_name}"
|
|
161
|
+
if not any(item["title"] == header_title for item in result["info"]):
|
|
162
|
+
result["info"].append(
|
|
163
|
+
{
|
|
164
|
+
"type": "header",
|
|
165
|
+
"title": header_title,
|
|
166
|
+
"description": f"Server header detected: {header_name}: {header_value}",
|
|
167
|
+
"severity": "info",
|
|
168
|
+
}
|
|
169
|
+
)
|
|
144
170
|
|
|
145
171
|
# Detect no API token warning
|
|
146
|
-
if
|
|
147
|
-
if not any(
|
|
148
|
-
result[
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
172
|
+
if "No WPScan API Token given" in line:
|
|
173
|
+
if not any(
|
|
174
|
+
item["title"] == "No WPScan API Token" for item in result["info"]
|
|
175
|
+
):
|
|
176
|
+
result["info"].append(
|
|
177
|
+
{
|
|
178
|
+
"type": "warning",
|
|
179
|
+
"title": "No WPScan API Token",
|
|
180
|
+
"description": "Vulnerability data not included. Register at https://wpscan.com/register for a free API token.",
|
|
181
|
+
"severity": "info",
|
|
182
|
+
}
|
|
183
|
+
)
|
|
154
184
|
|
|
155
185
|
# Detect theme detection failure
|
|
156
|
-
if
|
|
157
|
-
if not any(
|
|
158
|
-
result[
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
186
|
+
if "main theme could not be detected" in line.lower():
|
|
187
|
+
if not any(
|
|
188
|
+
item["title"] == "Theme Detection Failed" for item in result["info"]
|
|
189
|
+
):
|
|
190
|
+
result["info"].append(
|
|
191
|
+
{
|
|
192
|
+
"type": "info",
|
|
193
|
+
"title": "Theme Detection Failed",
|
|
194
|
+
"description": "WPScan could not identify the active theme.",
|
|
195
|
+
"severity": "info",
|
|
196
|
+
}
|
|
197
|
+
)
|
|
164
198
|
|
|
165
199
|
# Detect no plugins found
|
|
166
|
-
if
|
|
167
|
-
if not any(
|
|
168
|
-
result[
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
200
|
+
if "[i] No plugins Found" in line or "No plugins Found" in line:
|
|
201
|
+
if not any(
|
|
202
|
+
item["title"] == "No Plugins Detected" for item in result["info"]
|
|
203
|
+
):
|
|
204
|
+
result["info"].append(
|
|
205
|
+
{
|
|
206
|
+
"type": "info",
|
|
207
|
+
"title": "No Plugins Detected",
|
|
208
|
+
"description": "WPScan did not detect any WordPress plugins.",
|
|
209
|
+
"severity": "info",
|
|
210
|
+
}
|
|
211
|
+
)
|
|
174
212
|
|
|
175
213
|
# Detect sections
|
|
176
|
-
if
|
|
177
|
-
current_section =
|
|
178
|
-
elif
|
|
179
|
-
current_section =
|
|
180
|
-
elif
|
|
181
|
-
current_section =
|
|
214
|
+
if "[+] WordPress theme in use:" in line or "Theme(s) Detected:" in line:
|
|
215
|
+
current_section = "themes"
|
|
216
|
+
elif "[+] Plugins Found:" in line or "Plugin(s) Identified:" in line:
|
|
217
|
+
current_section = "plugins"
|
|
218
|
+
elif "User(s) Identified:" in line or "Username(s) Identified:" in line:
|
|
219
|
+
current_section = "users"
|
|
182
220
|
|
|
183
221
|
# Parse vulnerabilities with [!] marker
|
|
184
|
-
|
|
185
|
-
|
|
222
|
+
# WPScan outputs vulns as either "[!] Title:" or "| [!] Title:" (indented under sections)
|
|
223
|
+
if line_stripped.startswith("[!]") or line_stripped.startswith("| [!]"):
|
|
224
|
+
finding = _parse_vulnerability_line(line, lines[i : i + 10])
|
|
186
225
|
if finding:
|
|
187
|
-
result[
|
|
188
|
-
|
|
189
|
-
# Parse enumerated users
|
|
190
|
-
if current_section ==
|
|
191
|
-
|
|
192
|
-
if
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
226
|
+
result["findings"].append(finding)
|
|
227
|
+
|
|
228
|
+
# Parse enumerated users - format: [+] username (on its own line)
|
|
229
|
+
if current_section == "users":
|
|
230
|
+
# End users section on [!] warning or [+] Finished/Requests
|
|
231
|
+
if (
|
|
232
|
+
line_stripped.startswith("[!]")
|
|
233
|
+
or "Finished:" in line
|
|
234
|
+
or "Requests Done:" in line
|
|
235
|
+
):
|
|
236
|
+
current_section = None
|
|
237
|
+
else:
|
|
238
|
+
# Match username after [+] at start of line (username may have spaces)
|
|
239
|
+
user_match = re.match(r"^\[\+\]\s*(.+)$", line_stripped)
|
|
240
|
+
if user_match:
|
|
241
|
+
username = user_match.group(1).strip()
|
|
242
|
+
if (
|
|
243
|
+
username
|
|
244
|
+
and username not in result["users"]
|
|
245
|
+
and username.lower() not in ("id", "found by")
|
|
246
|
+
):
|
|
247
|
+
result["users"].append(username)
|
|
196
248
|
|
|
197
249
|
# Parse plugin info
|
|
198
|
-
if current_section ==
|
|
199
|
-
plugin_match = re.search(r
|
|
250
|
+
if current_section == "plugins":
|
|
251
|
+
plugin_match = re.search(r"\[i\]\s+Plugin\(s\)\s+Identified:\s+(.+)", line)
|
|
200
252
|
if not plugin_match:
|
|
201
|
-
plugin_match = re.search(r
|
|
253
|
+
plugin_match = re.search(r"\|\s+Name:\s+(.+)", line)
|
|
202
254
|
|
|
203
255
|
if plugin_match:
|
|
204
256
|
plugin_name = plugin_match.group(1).strip()
|
|
205
|
-
plugin_data = {
|
|
257
|
+
plugin_data = {
|
|
258
|
+
"name": plugin_name,
|
|
259
|
+
"version": None,
|
|
260
|
+
"location": None,
|
|
261
|
+
"vulnerable": False,
|
|
262
|
+
}
|
|
206
263
|
|
|
207
264
|
# Look ahead for version and location
|
|
208
265
|
for j in range(i + 1, min(i + 10, len(lines))):
|
|
209
|
-
if
|
|
210
|
-
ver_match = re.search(r
|
|
266
|
+
if "Latest version:" in lines[j] or "Version:" in lines[j]:
|
|
267
|
+
ver_match = re.search(r"(\d+\.\d+(?:\.\d+)?)", lines[j])
|
|
211
268
|
if ver_match:
|
|
212
|
-
plugin_data[
|
|
213
|
-
if
|
|
214
|
-
loc_match = re.search(r
|
|
269
|
+
plugin_data["version"] = ver_match.group(1)
|
|
270
|
+
if "Location:" in lines[j]:
|
|
271
|
+
loc_match = re.search(r"Location:\s+(.+)", lines[j])
|
|
215
272
|
if loc_match:
|
|
216
|
-
plugin_data[
|
|
217
|
-
if
|
|
218
|
-
plugin_data[
|
|
273
|
+
plugin_data["location"] = loc_match.group(1).strip()
|
|
274
|
+
if "[!]" in lines[j]:
|
|
275
|
+
plugin_data["vulnerable"] = True
|
|
219
276
|
|
|
220
277
|
if plugin_name:
|
|
221
|
-
result[
|
|
278
|
+
result["plugins"].append(plugin_data)
|
|
222
279
|
|
|
223
280
|
# Parse theme info
|
|
224
|
-
if current_section ==
|
|
225
|
-
theme_match = re.search(r
|
|
281
|
+
if current_section == "themes":
|
|
282
|
+
theme_match = re.search(r"\|\s+Name:\s+(.+)", line)
|
|
226
283
|
if theme_match:
|
|
227
284
|
theme_name = theme_match.group(1).strip()
|
|
228
|
-
theme_data = {
|
|
285
|
+
theme_data = {
|
|
286
|
+
"name": theme_name,
|
|
287
|
+
"version": None,
|
|
288
|
+
"location": None,
|
|
289
|
+
"vulnerable": False,
|
|
290
|
+
}
|
|
229
291
|
|
|
230
292
|
# Look ahead for version and location
|
|
231
293
|
for j in range(i + 1, min(i + 10, len(lines))):
|
|
232
|
-
if
|
|
233
|
-
ver_match = re.search(r
|
|
294
|
+
if "Version:" in lines[j]:
|
|
295
|
+
ver_match = re.search(r"(\d+\.\d+(?:\.\d+)?)", lines[j])
|
|
234
296
|
if ver_match:
|
|
235
|
-
theme_data[
|
|
236
|
-
if
|
|
237
|
-
loc_match = re.search(r
|
|
297
|
+
theme_data["version"] = ver_match.group(1)
|
|
298
|
+
if "Location:" in lines[j]:
|
|
299
|
+
loc_match = re.search(r"Location:\s+(.+)", lines[j])
|
|
238
300
|
if loc_match:
|
|
239
|
-
theme_data[
|
|
240
|
-
if
|
|
241
|
-
theme_data[
|
|
301
|
+
theme_data["location"] = loc_match.group(1).strip()
|
|
302
|
+
if "[!]" in lines[j]:
|
|
303
|
+
theme_data["vulnerable"] = True
|
|
242
304
|
|
|
243
305
|
if theme_name:
|
|
244
|
-
result[
|
|
306
|
+
result["themes"].append(theme_data)
|
|
245
307
|
|
|
246
308
|
return result
|
|
247
309
|
|
|
248
310
|
|
|
249
|
-
def _parse_vulnerability_line(
|
|
311
|
+
def _parse_vulnerability_line(
|
|
312
|
+
line: str, context_lines: List[str]
|
|
313
|
+
) -> Optional[Dict[str, Any]]:
|
|
250
314
|
"""
|
|
251
315
|
Parse a vulnerability line and surrounding context.
|
|
252
316
|
|
|
@@ -257,74 +321,92 @@ def _parse_vulnerability_line(line: str, context_lines: List[str]) -> Optional[D
|
|
|
257
321
|
| - https://wpvulndb.com/vulnerabilities/xxxx
|
|
258
322
|
| - CVE-2021-12345
|
|
259
323
|
"""
|
|
260
|
-
# Extract title
|
|
261
|
-
|
|
324
|
+
# Extract title - must have "Title:" prefix for actual vulnerabilities
|
|
325
|
+
# WPScan uses "[!] Title: ..." for vulns, other [!] lines are warnings/info
|
|
326
|
+
title_match = re.search(r"\[!\]\s+Title:\s+(.+)", line)
|
|
262
327
|
if not title_match:
|
|
263
|
-
#
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
328
|
+
# Only accept lines with explicit "Title:" prefix - skip warnings like:
|
|
329
|
+
# "[!] No WPScan API Token given..."
|
|
330
|
+
# "[!] The version is out of date..."
|
|
331
|
+
# "[!] 121 vulnerabilities identified:"
|
|
332
|
+
return None
|
|
267
333
|
|
|
268
334
|
title = title_match.group(1).strip()
|
|
269
335
|
|
|
270
336
|
finding = {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
337
|
+
"title": title,
|
|
338
|
+
"type": "unknown",
|
|
339
|
+
"name": "",
|
|
340
|
+
"version": None,
|
|
341
|
+
"severity": "medium", # Default
|
|
342
|
+
"description": title,
|
|
343
|
+
"references": [],
|
|
344
|
+
"fixed_in": None,
|
|
279
345
|
}
|
|
280
346
|
|
|
281
347
|
# Determine type and severity from title
|
|
282
|
-
if
|
|
283
|
-
finding[
|
|
284
|
-
finding[
|
|
285
|
-
elif
|
|
286
|
-
finding[
|
|
287
|
-
elif
|
|
288
|
-
finding[
|
|
289
|
-
elif
|
|
290
|
-
finding[
|
|
348
|
+
if "WordPress" in title and "Core" in title:
|
|
349
|
+
finding["type"] = "core"
|
|
350
|
+
finding["name"] = "WordPress Core"
|
|
351
|
+
elif "plugin" in title.lower():
|
|
352
|
+
finding["type"] = "plugin"
|
|
353
|
+
elif "theme" in title.lower():
|
|
354
|
+
finding["type"] = "theme"
|
|
355
|
+
elif "config" in title.lower() or "header" in title.lower():
|
|
356
|
+
finding["type"] = "config"
|
|
291
357
|
|
|
292
358
|
# Severity indicators
|
|
293
|
-
if any(
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
finding[
|
|
303
|
-
|
|
304
|
-
|
|
359
|
+
if any(
|
|
360
|
+
word in title.lower()
|
|
361
|
+
for word in ["critical", "rce", "sql injection", "remote code"]
|
|
362
|
+
):
|
|
363
|
+
finding["severity"] = "critical"
|
|
364
|
+
elif any(
|
|
365
|
+
word in title.lower()
|
|
366
|
+
for word in ["high", "authentication bypass", "privilege escalation"]
|
|
367
|
+
):
|
|
368
|
+
finding["severity"] = "high"
|
|
369
|
+
elif any(word in title.lower() for word in ["xss", "csrf", "medium"]):
|
|
370
|
+
finding["severity"] = "medium"
|
|
371
|
+
elif any(word in title.lower() for word in ["disclosure", "enumeration", "low"]):
|
|
372
|
+
finding["severity"] = "low"
|
|
373
|
+
elif any(word in title.lower() for word in ["info", "version"]):
|
|
374
|
+
finding["severity"] = "info"
|
|
375
|
+
|
|
376
|
+
# Extract additional info from context lines (stop at blank line or next vuln)
|
|
305
377
|
for context_line in context_lines[1:]:
|
|
306
378
|
context_stripped = context_line.strip()
|
|
307
379
|
|
|
308
|
-
|
|
309
|
-
|
|
380
|
+
# Stop parsing context at blank lines or next vulnerability marker
|
|
381
|
+
if not context_stripped or context_stripped == "|":
|
|
382
|
+
break
|
|
383
|
+
if "[!]" in context_stripped and "Title:" in context_stripped:
|
|
384
|
+
break
|
|
385
|
+
|
|
386
|
+
if "Fixed in:" in context_stripped:
|
|
387
|
+
fixed_match = re.search(
|
|
388
|
+
r"Fixed in:\s+(\d+\.\d+(?:\.\d+)?)", context_stripped
|
|
389
|
+
)
|
|
310
390
|
if fixed_match:
|
|
311
|
-
finding[
|
|
391
|
+
finding["fixed_in"] = fixed_match.group(1)
|
|
312
392
|
|
|
313
|
-
elif
|
|
314
|
-
cve_matches = re.findall(r
|
|
315
|
-
finding[
|
|
393
|
+
elif "CVE-" in context_stripped:
|
|
394
|
+
cve_matches = re.findall(r"CVE-\d{4}-\d+", context_stripped)
|
|
395
|
+
finding["references"].extend(cve_matches)
|
|
316
396
|
|
|
317
|
-
elif
|
|
318
|
-
url_match = re.search(r
|
|
397
|
+
elif "https://" in context_stripped or "http://" in context_stripped:
|
|
398
|
+
url_match = re.search(r"(https?://[^\s<>]+)", context_stripped)
|
|
319
399
|
if url_match:
|
|
320
|
-
url = url_match.group(1).rstrip(
|
|
321
|
-
if url not in finding[
|
|
322
|
-
finding[
|
|
400
|
+
url = url_match.group(1).rstrip(",.;)")
|
|
401
|
+
if url not in finding["references"]:
|
|
402
|
+
finding["references"].append(url)
|
|
323
403
|
|
|
324
404
|
return finding
|
|
325
405
|
|
|
326
406
|
|
|
327
|
-
def map_to_findings(
|
|
407
|
+
def map_to_findings(
|
|
408
|
+
parsed_data: Dict[str, Any], engagement_id: int
|
|
409
|
+
) -> List[Dict[str, Any]]:
|
|
328
410
|
"""
|
|
329
411
|
Convert parsed WPScan data into finding records for database storage.
|
|
330
412
|
|
|
@@ -337,24 +419,30 @@ def map_to_findings(parsed_data: Dict[str, Any], engagement_id: int) -> List[Dic
|
|
|
337
419
|
"""
|
|
338
420
|
findings = []
|
|
339
421
|
|
|
340
|
-
for vuln in parsed_data.get(
|
|
422
|
+
for vuln in parsed_data.get("findings", []):
|
|
341
423
|
finding = {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
424
|
+
"title": vuln["title"],
|
|
425
|
+
"severity": vuln["severity"],
|
|
426
|
+
"description": vuln["description"],
|
|
427
|
+
"affected_target": parsed_data.get("target_url", ""),
|
|
428
|
+
"tool": "wpscan",
|
|
429
|
+
"category": "web",
|
|
430
|
+
"remediation": (
|
|
431
|
+
f"Update {vuln['name']} to version {vuln['fixed_in']}"
|
|
432
|
+
if vuln["fixed_in"]
|
|
433
|
+
else "Review WordPress security settings"
|
|
434
|
+
),
|
|
435
|
+
"references": "\n".join(vuln["references"]) if vuln["references"] else None,
|
|
436
|
+
"cvss_score": _severity_to_cvss(vuln["severity"]),
|
|
437
|
+
"metadata": json.dumps(
|
|
438
|
+
{
|
|
439
|
+
"wordpress_version": parsed_data.get("wordpress_version"),
|
|
440
|
+
"component_type": vuln["type"],
|
|
441
|
+
"component_name": vuln["name"],
|
|
442
|
+
"component_version": vuln["version"],
|
|
443
|
+
"fixed_in": vuln["fixed_in"],
|
|
444
|
+
}
|
|
445
|
+
),
|
|
358
446
|
}
|
|
359
447
|
findings.append(finding)
|
|
360
448
|
|
|
@@ -364,14 +452,14 @@ def map_to_findings(parsed_data: Dict[str, Any], engagement_id: int) -> List[Dic
|
|
|
364
452
|
def _severity_to_cvss(severity: str) -> Optional[float]:
|
|
365
453
|
"""Map severity string to approximate CVSS score."""
|
|
366
454
|
severity_map = {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
455
|
+
"critical": 9.5,
|
|
456
|
+
"high": 7.5,
|
|
457
|
+
"medium": 5.0,
|
|
458
|
+
"low": 3.0,
|
|
459
|
+
"info": 0.0,
|
|
372
460
|
}
|
|
373
461
|
return severity_map.get(severity.lower())
|
|
374
462
|
|
|
375
463
|
|
|
376
464
|
# Export the main function
|
|
377
|
-
__all__ = [
|
|
465
|
+
__all__ = ["parse_wpscan_output", "map_to_findings"]
|