souleyez 2.43.29__py3-none-any.whl → 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of souleyez might be problematic. Click here for more details.
- 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 +9564 -2881
- 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 +564 -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 +409 -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 +417 -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 +913 -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 +219 -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 +237 -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 +23034 -10679
- 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-3.0.0.dist-info}/METADATA +2 -2
- souleyez-3.0.0.dist-info/RECORD +443 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/WHEEL +1 -1
- souleyez-2.43.29.dist-info/RECORD +0 -379
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/entry_points.txt +0 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/top_level.txt +0 -0
souleyez/parsers/hydra_parser.py
CHANGED
|
@@ -49,22 +49,26 @@ def parse_hydra_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
49
49
|
}
|
|
50
50
|
"""
|
|
51
51
|
result = {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
"target_host": target,
|
|
53
|
+
"service": None,
|
|
54
|
+
"port": None,
|
|
55
|
+
"credentials": [],
|
|
56
|
+
"usernames": [], # For username-only enumeration results
|
|
57
|
+
"attempts": 0,
|
|
58
|
+
"status": "failed",
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
lines = output.split(
|
|
61
|
+
lines = output.split("\n")
|
|
62
62
|
|
|
63
63
|
# Check for WordPress admin redirect (indicates successful login)
|
|
64
|
-
has_wp_admin_redirect = bool(
|
|
64
|
+
has_wp_admin_redirect = bool(
|
|
65
|
+
re.search(r"redirected to.*[:/]wp-admin", output, re.IGNORECASE)
|
|
66
|
+
)
|
|
65
67
|
|
|
66
68
|
# Detect if this is a WordPress/http-post-form attack
|
|
67
|
-
is_wordpress_attack =
|
|
69
|
+
is_wordpress_attack = "http-post-form" in output.lower() and (
|
|
70
|
+
"wp-login" in output.lower() or "wordpress" in output.lower()
|
|
71
|
+
)
|
|
68
72
|
|
|
69
73
|
# Track attempted credentials from [ATTEMPT] lines (for when Hydra doesn't report match lines)
|
|
70
74
|
last_attempt = None
|
|
@@ -76,13 +80,13 @@ def parse_hydra_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
76
80
|
# Format: [ATTEMPT] target HOST - login "USER" - pass "PASS" - N of M [child X] (Y/Z)
|
|
77
81
|
attempt_match = re.search(
|
|
78
82
|
r'\[ATTEMPT\]\s+target\s+(\S+)\s+-\s+login\s+"([^"]+)"\s+-\s+pass\s+"([^"]+)"',
|
|
79
|
-
line_stripped
|
|
83
|
+
line_stripped,
|
|
80
84
|
)
|
|
81
85
|
if attempt_match:
|
|
82
86
|
last_attempt = {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
"host": attempt_match.group(1),
|
|
88
|
+
"username": attempt_match.group(2),
|
|
89
|
+
"password": attempt_match.group(3),
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
# Parse successful login lines with multiple format support
|
|
@@ -100,8 +104,9 @@ def parse_hydra_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
100
104
|
|
|
101
105
|
# Try standard format: [PORT][SERVICE] host: HOST login: USER password: PASS
|
|
102
106
|
login_match = re.search(
|
|
103
|
-
r
|
|
104
|
-
line_stripped,
|
|
107
|
+
r"\[(\d+)\]\[([\w-]+)\]\s+host:\s*(\S+)\s+login:\s*(\S+)\s+password:\s*(.+)",
|
|
108
|
+
line_stripped,
|
|
109
|
+
re.IGNORECASE,
|
|
105
110
|
)
|
|
106
111
|
if login_match:
|
|
107
112
|
port = int(login_match.group(1))
|
|
@@ -113,8 +118,9 @@ def parse_hydra_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
113
118
|
# Try swapped format: [SERVICE][PORT]
|
|
114
119
|
if not login_match:
|
|
115
120
|
login_match = re.search(
|
|
116
|
-
r
|
|
117
|
-
line_stripped,
|
|
121
|
+
r"\[([\w-]+)\]\[(\d+)\]\s+host:\s*(\S+)\s+login:\s*(\S+)\s+password:\s*(.+)",
|
|
122
|
+
line_stripped,
|
|
123
|
+
re.IGNORECASE,
|
|
118
124
|
)
|
|
119
125
|
if login_match:
|
|
120
126
|
service = login_match.group(1).lower()
|
|
@@ -126,8 +132,9 @@ def parse_hydra_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
126
132
|
# Try format without "host:" label
|
|
127
133
|
if not login_match:
|
|
128
134
|
login_match = re.search(
|
|
129
|
-
r
|
|
130
|
-
line_stripped,
|
|
135
|
+
r"\[(\d+)\]\[([\w-]+)\]\s+(\d+\.\d+\.\d+\.\d+|\S+)\s+login:\s*(\S+)\s+password:\s*(.+)",
|
|
136
|
+
line_stripped,
|
|
137
|
+
re.IGNORECASE,
|
|
131
138
|
)
|
|
132
139
|
if login_match:
|
|
133
140
|
port = int(login_match.group(1))
|
|
@@ -137,10 +144,13 @@ def parse_hydra_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
137
144
|
password = login_match.group(5).strip()
|
|
138
145
|
|
|
139
146
|
# Try flexible format with any whitespace between fields
|
|
147
|
+
# Note: Use \s+login: to require whitespace before "login:" and require the colon
|
|
148
|
+
# This prevents matching "login" inside paths like "wp-login.php"
|
|
140
149
|
if not login_match:
|
|
141
150
|
login_match = re.search(
|
|
142
|
-
r
|
|
143
|
-
line_stripped,
|
|
151
|
+
r"\[(\d+)\]\[([\w-]+)\].*?(?:host:?\s*)?(\d+\.\d+\.\d+\.\d+|\S+\.\S+).*?\s+login:\s*(\S+).*?password:\s*(.+)",
|
|
152
|
+
line_stripped,
|
|
153
|
+
re.IGNORECASE,
|
|
144
154
|
)
|
|
145
155
|
if login_match:
|
|
146
156
|
port = int(login_match.group(1))
|
|
@@ -151,76 +161,85 @@ def parse_hydra_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
151
161
|
|
|
152
162
|
if login_match and port and service and username:
|
|
153
163
|
# Store service info if not already set
|
|
154
|
-
if not result[
|
|
155
|
-
result[
|
|
156
|
-
if not result[
|
|
157
|
-
result[
|
|
158
|
-
if not result[
|
|
159
|
-
result[
|
|
164
|
+
if not result["service"]:
|
|
165
|
+
result["service"] = service
|
|
166
|
+
if not result["port"]:
|
|
167
|
+
result["port"] = port
|
|
168
|
+
if not result["target_host"] or result["target_host"] == "":
|
|
169
|
+
result["target_host"] = host
|
|
160
170
|
|
|
161
171
|
# For WordPress attacks, check if this is a full credential or just username enumeration
|
|
162
172
|
if is_wordpress_attack and not has_wp_admin_redirect:
|
|
163
173
|
# No redirect to wp-admin means only username was validated
|
|
164
174
|
# (password was wrong but username exists)
|
|
165
|
-
if username not in result[
|
|
166
|
-
result[
|
|
167
|
-
result[
|
|
175
|
+
if username not in result["usernames"]:
|
|
176
|
+
result["usernames"].append(username)
|
|
177
|
+
result["status"] = "partial" # Partial success - username found
|
|
168
178
|
else:
|
|
169
179
|
# Full credential validation (redirect found or non-WordPress service)
|
|
170
180
|
credential = {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
181
|
+
"username": username,
|
|
182
|
+
"password": password,
|
|
183
|
+
"service": service,
|
|
184
|
+
"port": port,
|
|
185
|
+
"host": host,
|
|
186
|
+
"username_only": False,
|
|
177
187
|
}
|
|
178
188
|
|
|
179
|
-
result[
|
|
180
|
-
result[
|
|
189
|
+
result["credentials"].append(credential)
|
|
190
|
+
result["status"] = "success"
|
|
181
191
|
|
|
182
192
|
# Extract attacking target info
|
|
183
|
-
attacking_match = re.search(
|
|
193
|
+
attacking_match = re.search(
|
|
194
|
+
r"attacking\s+([\w-]+)://([^:]+):(\d+)", line_stripped
|
|
195
|
+
)
|
|
184
196
|
if attacking_match:
|
|
185
|
-
result[
|
|
186
|
-
if not result[
|
|
187
|
-
result[
|
|
188
|
-
result[
|
|
197
|
+
result["service"] = attacking_match.group(1).lower()
|
|
198
|
+
if not result["target_host"] or result["target_host"] == "":
|
|
199
|
+
result["target_host"] = attacking_match.group(2)
|
|
200
|
+
result["port"] = int(attacking_match.group(3))
|
|
189
201
|
|
|
190
202
|
# Extract attempt count
|
|
191
|
-
attempts_match = re.search(r
|
|
203
|
+
attempts_match = re.search(r"(\d+)\s+tasks", line_stripped)
|
|
192
204
|
if attempts_match:
|
|
193
|
-
result[
|
|
205
|
+
result["attempts"] = int(attempts_match.group(1))
|
|
194
206
|
|
|
195
207
|
# For WordPress attacks: If we see wp-admin redirect but no match lines were found,
|
|
196
208
|
# extract credentials from the last ATTEMPT line (handles F=loginform case where
|
|
197
209
|
# Hydra doesn't produce match lines even for valid credentials)
|
|
198
|
-
if
|
|
210
|
+
if (
|
|
211
|
+
is_wordpress_attack
|
|
212
|
+
and has_wp_admin_redirect
|
|
213
|
+
and not result["credentials"]
|
|
214
|
+
and last_attempt
|
|
215
|
+
):
|
|
199
216
|
credential = {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
217
|
+
"username": last_attempt["username"],
|
|
218
|
+
"password": last_attempt["password"],
|
|
219
|
+
"service": result.get("service", "http-post-form"),
|
|
220
|
+
"port": result.get("port", 80),
|
|
221
|
+
"host": last_attempt["host"],
|
|
222
|
+
"username_only": False,
|
|
206
223
|
}
|
|
207
|
-
result[
|
|
208
|
-
result[
|
|
224
|
+
result["credentials"].append(credential)
|
|
225
|
+
result["status"] = "success"
|
|
209
226
|
|
|
210
227
|
# Determine final status
|
|
211
|
-
if result[
|
|
212
|
-
result[
|
|
213
|
-
elif result[
|
|
214
|
-
result[
|
|
215
|
-
elif
|
|
216
|
-
result[
|
|
228
|
+
if result["credentials"]:
|
|
229
|
+
result["status"] = "success"
|
|
230
|
+
elif result["usernames"]:
|
|
231
|
+
result["status"] = "partial"
|
|
232
|
+
elif "attack finished" in output.lower():
|
|
233
|
+
result["status"] = "failed"
|
|
217
234
|
else:
|
|
218
|
-
result[
|
|
235
|
+
result["status"] = "partial"
|
|
219
236
|
|
|
220
237
|
return result
|
|
221
238
|
|
|
222
239
|
|
|
223
|
-
def map_to_credentials(
|
|
240
|
+
def map_to_credentials(
|
|
241
|
+
parsed_data: Dict[str, Any], engagement_id: int, host_id: Optional[int] = None
|
|
242
|
+
) -> List[Dict[str, Any]]:
|
|
224
243
|
"""
|
|
225
244
|
Convert parsed Hydra data into credential records for database storage.
|
|
226
245
|
|
|
@@ -234,21 +253,21 @@ def map_to_credentials(parsed_data: Dict[str, Any], engagement_id: int, host_id:
|
|
|
234
253
|
"""
|
|
235
254
|
credentials = []
|
|
236
255
|
|
|
237
|
-
for cred in parsed_data.get(
|
|
256
|
+
for cred in parsed_data.get("credentials", []):
|
|
238
257
|
credential = {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
258
|
+
"username": cred["username"],
|
|
259
|
+
"password": cred["password"],
|
|
260
|
+
"credential_type": _service_to_credential_type(cred["service"]),
|
|
261
|
+
"source": "hydra",
|
|
262
|
+
"validation_status": "confirmed", # Hydra only reports successful logins
|
|
263
|
+
"notes": f"Brute-forced via Hydra on {cred['service']}:{cred['port']}",
|
|
264
|
+
"target": f"{parsed_data.get('target_host')}:{cred['port']}",
|
|
265
|
+
"service": cred["service"],
|
|
266
|
+
"port": cred["port"],
|
|
248
267
|
}
|
|
249
268
|
|
|
250
269
|
if host_id:
|
|
251
|
-
credential[
|
|
270
|
+
credential["host_id"] = host_id
|
|
252
271
|
|
|
253
272
|
credentials.append(credential)
|
|
254
273
|
|
|
@@ -268,29 +287,29 @@ def _service_to_credential_type(service: str) -> str:
|
|
|
268
287
|
service_lower = service.lower()
|
|
269
288
|
|
|
270
289
|
type_map = {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
290
|
+
"ssh": "ssh",
|
|
291
|
+
"ftp": "ftp",
|
|
292
|
+
"smb": "windows",
|
|
293
|
+
"rdp": "windows",
|
|
294
|
+
"mysql": "database",
|
|
295
|
+
"postgres": "database",
|
|
296
|
+
"postgresql": "database",
|
|
297
|
+
"mssql": "database",
|
|
298
|
+
"oracle": "database",
|
|
299
|
+
"http": "web",
|
|
300
|
+
"https": "web",
|
|
301
|
+
"http-get": "web",
|
|
302
|
+
"http-post": "web",
|
|
303
|
+
"http-get-form": "web",
|
|
304
|
+
"http-post-form": "web",
|
|
305
|
+
"https-get-form": "web",
|
|
306
|
+
"https-post-form": "web",
|
|
307
|
+
"telnet": "telnet",
|
|
308
|
+
"vnc": "vnc",
|
|
309
|
+
"ldap": "ldap",
|
|
291
310
|
}
|
|
292
311
|
|
|
293
|
-
return type_map.get(service_lower,
|
|
312
|
+
return type_map.get(service_lower, "other")
|
|
294
313
|
|
|
295
314
|
|
|
296
315
|
def generate_summary(parsed_data: Dict[str, Any]) -> str:
|
|
@@ -303,12 +322,12 @@ def generate_summary(parsed_data: Dict[str, Any]) -> str:
|
|
|
303
322
|
Returns:
|
|
304
323
|
Formatted summary string
|
|
305
324
|
"""
|
|
306
|
-
target = parsed_data.get(
|
|
307
|
-
service = parsed_data.get(
|
|
308
|
-
port = parsed_data.get(
|
|
309
|
-
creds = parsed_data.get(
|
|
310
|
-
usernames = parsed_data.get(
|
|
311
|
-
status = parsed_data.get(
|
|
325
|
+
target = parsed_data.get("target_host", "unknown")
|
|
326
|
+
service = parsed_data.get("service", "unknown")
|
|
327
|
+
port = parsed_data.get("port", "unknown")
|
|
328
|
+
creds = parsed_data.get("credentials", [])
|
|
329
|
+
usernames = parsed_data.get("usernames", [])
|
|
330
|
+
status = parsed_data.get("status", "unknown")
|
|
312
331
|
|
|
313
332
|
summary = "Hydra Attack Summary\n"
|
|
314
333
|
summary += f"{'=' * 50}\n"
|
|
@@ -348,23 +367,24 @@ def extract_failed_attempts(output: str) -> Dict[str, int]:
|
|
|
348
367
|
Returns:
|
|
349
368
|
Dict with failure statistics
|
|
350
369
|
"""
|
|
351
|
-
stats = {
|
|
352
|
-
'total_attempts': 0,
|
|
353
|
-
'successful': 0,
|
|
354
|
-
'failed': 0
|
|
355
|
-
}
|
|
370
|
+
stats = {"total_attempts": 0, "successful": 0, "failed": 0}
|
|
356
371
|
|
|
357
372
|
# Count login attempts (successful ones)
|
|
358
|
-
successful_count = len(re.findall(r
|
|
359
|
-
stats[
|
|
373
|
+
successful_count = len(re.findall(r"\[\d+\]\[\w+\]\s+host:", output))
|
|
374
|
+
stats["successful"] = successful_count
|
|
360
375
|
|
|
361
376
|
# Try to extract total from status messages
|
|
362
|
-
status_match = re.search(r
|
|
377
|
+
status_match = re.search(r"(\d+)\s+valid passwords? found", output, re.IGNORECASE)
|
|
363
378
|
if status_match:
|
|
364
|
-
stats[
|
|
379
|
+
stats["successful"] = int(status_match.group(1))
|
|
365
380
|
|
|
366
381
|
return stats
|
|
367
382
|
|
|
368
383
|
|
|
369
384
|
# Export the main functions
|
|
370
|
-
__all__ = [
|
|
385
|
+
__all__ = [
|
|
386
|
+
"parse_hydra_output",
|
|
387
|
+
"map_to_credentials",
|
|
388
|
+
"generate_summary",
|
|
389
|
+
"extract_failed_attempts",
|
|
390
|
+
]
|