souleyez 2.43.26__py3-none-any.whl → 2.43.34__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +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 +23434 -10286
- 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.26.dist-info → souleyez-2.43.34.dist-info}/METADATA +1 -1
- souleyez-2.43.34.dist-info/RECORD +443 -0
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
- souleyez-2.43.26.dist-info/RECORD +0 -379
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/top_level.txt +0 -0
|
@@ -11,14 +11,14 @@ from typing import Dict, Any, List
|
|
|
11
11
|
def parse_hashcat_output(output: str, hash_file: str = "") -> Dict[str, Any]:
|
|
12
12
|
"""
|
|
13
13
|
Parse hashcat output and extract cracked passwords.
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
Hashcat output format:
|
|
16
16
|
<hash>:<password>
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
Args:
|
|
19
19
|
output: Raw hashcat output text
|
|
20
|
-
hash_file: Path to hash file (for reference)
|
|
21
|
-
|
|
20
|
+
hash_file: Path to hash file (for reference, also used to map hashes to usernames)
|
|
21
|
+
|
|
22
22
|
Returns:
|
|
23
23
|
Dict with structure:
|
|
24
24
|
{
|
|
@@ -27,7 +27,8 @@ def parse_hashcat_output(output: str, hash_file: str = "") -> Dict[str, Any]:
|
|
|
27
27
|
{
|
|
28
28
|
'hash': str,
|
|
29
29
|
'password': str,
|
|
30
|
-
'hash_type': str # if identifiable
|
|
30
|
+
'hash_type': str, # if identifiable
|
|
31
|
+
'username': str # if available from hash file
|
|
31
32
|
}
|
|
32
33
|
],
|
|
33
34
|
'stats': {
|
|
@@ -37,123 +38,367 @@ def parse_hashcat_output(output: str, hash_file: str = "") -> Dict[str, Any]:
|
|
|
37
38
|
}
|
|
38
39
|
"""
|
|
39
40
|
result = {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
"hash_file": hash_file,
|
|
42
|
+
"cracked": [],
|
|
43
|
+
"stats": {
|
|
44
|
+
"cracked_count": 0,
|
|
45
|
+
"total_count": 0,
|
|
46
|
+
"potfile_hits": 0, # Hashes already cracked in potfile
|
|
47
|
+
"status": "unknown",
|
|
48
|
+
},
|
|
46
49
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
|
|
51
|
+
# Build hash → username mapping from the original hash file
|
|
52
|
+
# This handles --username format where input is username:hash
|
|
53
|
+
hash_to_username = {}
|
|
54
|
+
if hash_file:
|
|
55
|
+
try:
|
|
56
|
+
import os
|
|
57
|
+
|
|
58
|
+
if os.path.exists(hash_file):
|
|
59
|
+
with open(hash_file, "r", encoding="utf-8", errors="replace") as f:
|
|
60
|
+
for line in f:
|
|
61
|
+
line = line.strip()
|
|
62
|
+
if ":" in line and not line.startswith("#"):
|
|
63
|
+
parts = line.split(":")
|
|
64
|
+
if len(parts) >= 2:
|
|
65
|
+
# Format: username:hash or username:hash:... (for complex hashes)
|
|
66
|
+
potential_user = parts[0].strip()
|
|
67
|
+
potential_hash = parts[1].strip()
|
|
68
|
+
# Hash should be hex (MD5, SHA, etc.) or start with $
|
|
69
|
+
if re.match(
|
|
70
|
+
r"^[a-fA-F0-9]+$", potential_hash
|
|
71
|
+
) or potential_hash.startswith("$"):
|
|
72
|
+
hash_to_username[potential_hash.lower()] = (
|
|
73
|
+
potential_user
|
|
74
|
+
)
|
|
75
|
+
except Exception:
|
|
76
|
+
pass # Continue without username mapping
|
|
77
|
+
|
|
78
|
+
lines = output.split("\n")
|
|
79
|
+
|
|
80
|
+
# Check for potfile hits (already cracked)
|
|
81
|
+
for line in lines:
|
|
82
|
+
if "Removed hash found as potfile entry" in line:
|
|
83
|
+
result["stats"]["potfile_hits"] += 1
|
|
84
|
+
# Also parse "INFO: Removed 3 hashes found as potfile entries"
|
|
85
|
+
potfile_match = re.search(
|
|
86
|
+
r"Removed (\d+) hash(?:es)? found as potfile entr", line
|
|
87
|
+
)
|
|
88
|
+
if potfile_match:
|
|
89
|
+
result["stats"]["potfile_hits"] = int(potfile_match.group(1))
|
|
90
|
+
|
|
50
91
|
for line in lines:
|
|
51
92
|
line_stripped = line.strip()
|
|
52
|
-
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
93
|
+
|
|
94
|
+
# Skip empty lines and comments
|
|
95
|
+
if not line_stripped or line_stripped.startswith("#"):
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
# === PRIORITY 1: Check for cracked Kerberos hashes ===
|
|
99
|
+
# Format: $krb5tgs$23$*user$realm$spn*$...:password
|
|
100
|
+
# Format: $krb5asrep$23$user@realm:hash:password
|
|
101
|
+
if "$krb5" in line_stripped:
|
|
102
|
+
krb_start = line_stripped.find("$krb5")
|
|
103
|
+
if krb_start >= 0:
|
|
104
|
+
krb_portion = line_stripped[krb_start:]
|
|
105
|
+
# Password is after the last colon
|
|
106
|
+
last_colon = krb_portion.rfind(":")
|
|
107
|
+
if last_colon > 0:
|
|
108
|
+
hash_value = krb_portion[:last_colon]
|
|
109
|
+
password = krb_portion[last_colon + 1 :]
|
|
110
|
+
# Validate: hash must be long, password must exist and not look like hash data
|
|
111
|
+
if (
|
|
112
|
+
password
|
|
113
|
+
and len(hash_value) > 50
|
|
114
|
+
and not password.startswith("$")
|
|
115
|
+
):
|
|
116
|
+
# Extract username from TGS hash: $krb5tgs$23$*user$realm$spn*$...
|
|
117
|
+
username = None
|
|
118
|
+
user_match = re.search(
|
|
119
|
+
r"\$krb5(?:tgs|asrep)\$\d+\$\*([^$*]+)", hash_value
|
|
120
|
+
)
|
|
121
|
+
if user_match:
|
|
122
|
+
username = user_match.group(1)
|
|
123
|
+
result["cracked"].append(
|
|
124
|
+
{
|
|
125
|
+
"hash": hash_value,
|
|
126
|
+
"password": password,
|
|
127
|
+
"hash_type": "kerberos",
|
|
128
|
+
"username": username,
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
continue
|
|
132
|
+
|
|
133
|
+
# === PRIORITY 2: Parse status line ===
|
|
134
|
+
# Multiple formats: "Status...........: Cracked" or "Status........: Cracked"
|
|
135
|
+
# Use flexible pattern that handles varying dots/spaces
|
|
136
|
+
status_match = re.search(
|
|
137
|
+
r"Status[.\s]+:\s*(Cracked|Exhausted|Running|Quit)", line_stripped
|
|
138
|
+
)
|
|
139
|
+
if status_match:
|
|
140
|
+
status_value = status_match.group(1).lower()
|
|
141
|
+
result["stats"]["status"] = status_value
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
# === PRIORITY 3: Parse recovered count ===
|
|
145
|
+
# Format: "Recovered........: 1/1 (100.00%)" or "Recovered: 1/1"
|
|
146
|
+
recovered_match = re.search(r"Recovered[.\s]*:\s*(\d+)/(\d+)", line_stripped)
|
|
147
|
+
if recovered_match:
|
|
148
|
+
cracked_count = int(recovered_match.group(1))
|
|
149
|
+
total_count = int(recovered_match.group(2))
|
|
150
|
+
result["stats"]["cracked_count"] = cracked_count
|
|
151
|
+
result["stats"]["total_count"] = total_count
|
|
152
|
+
# If recovered > 0, mark as cracked
|
|
153
|
+
if cracked_count > 0 and result["stats"]["status"] == "unknown":
|
|
154
|
+
result["stats"]["status"] = "cracked"
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
# === Skip known status/progress lines ===
|
|
158
|
+
line_lower = line_stripped.lower()
|
|
159
|
+
if any(
|
|
160
|
+
x in line_lower
|
|
161
|
+
for x in [
|
|
162
|
+
"progress",
|
|
163
|
+
"speed",
|
|
164
|
+
"session",
|
|
165
|
+
"time.",
|
|
166
|
+
"[s]tatus",
|
|
167
|
+
"hash.mode",
|
|
168
|
+
"hash.target",
|
|
169
|
+
"kernel",
|
|
170
|
+
"guess",
|
|
171
|
+
"restore",
|
|
172
|
+
"candidate",
|
|
173
|
+
"hardware",
|
|
174
|
+
"started",
|
|
175
|
+
"stopped",
|
|
176
|
+
"watchdog",
|
|
177
|
+
"attention",
|
|
178
|
+
"pure kernels",
|
|
179
|
+
"optimizers",
|
|
180
|
+
"bitmaps",
|
|
181
|
+
"hashes:",
|
|
182
|
+
"initializ",
|
|
183
|
+
"dictionary cache",
|
|
184
|
+
"approaching",
|
|
185
|
+
"workload",
|
|
186
|
+
"keyspace",
|
|
187
|
+
"rejected",
|
|
188
|
+
"accel",
|
|
189
|
+
"loops",
|
|
190
|
+
"thr",
|
|
191
|
+
"vec",
|
|
192
|
+
]
|
|
193
|
+
):
|
|
194
|
+
continue
|
|
195
|
+
|
|
196
|
+
# === PRIORITY 4: Check for NTLM/other hashes ===
|
|
197
|
+
# Format: hash:password (simple colon-separated)
|
|
198
|
+
# Format with --username: username:hash:password
|
|
199
|
+
if ":" in line_stripped:
|
|
200
|
+
parts = line_stripped.split(":")
|
|
201
|
+
username = None
|
|
202
|
+
hash_value = None
|
|
203
|
+
password = None
|
|
204
|
+
|
|
61
205
|
if len(parts) == 2:
|
|
206
|
+
# Simple hash:password format
|
|
62
207
|
hash_value = parts[0].strip()
|
|
63
208
|
password = parts[1].strip()
|
|
64
|
-
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
209
|
+
elif len(parts) == 3:
|
|
210
|
+
# username:hash:password format (--username flag)
|
|
211
|
+
username = parts[0].strip()
|
|
212
|
+
hash_value = parts[1].strip()
|
|
213
|
+
password = parts[2].strip()
|
|
214
|
+
elif len(parts) > 3:
|
|
215
|
+
# Could be username:hash:password where password contains ':'
|
|
216
|
+
# Try: first part is username, second is hash, rest is password
|
|
217
|
+
username = parts[0].strip()
|
|
218
|
+
hash_value = parts[1].strip()
|
|
219
|
+
password = ":".join(parts[2:]).strip()
|
|
220
|
+
|
|
221
|
+
# Validate: hash looks like hex, password exists
|
|
222
|
+
if hash_value and password and len(hash_value) >= 16:
|
|
223
|
+
if re.match(r"^[a-fA-F0-9]+$", hash_value) or hash_value.startswith(
|
|
224
|
+
"$"
|
|
225
|
+
):
|
|
226
|
+
cracked_entry = {
|
|
227
|
+
"hash": hash_value,
|
|
228
|
+
"password": password,
|
|
229
|
+
"hash_type": "unknown",
|
|
230
|
+
}
|
|
231
|
+
# Get username from parsing or from hash file mapping
|
|
232
|
+
if username:
|
|
233
|
+
cracked_entry["username"] = username
|
|
234
|
+
elif hash_to_username:
|
|
235
|
+
# Look up username from hash file mapping
|
|
236
|
+
mapped_user = hash_to_username.get(hash_value.lower())
|
|
237
|
+
if mapped_user:
|
|
238
|
+
cracked_entry["username"] = mapped_user
|
|
239
|
+
result["cracked"].append(cracked_entry)
|
|
240
|
+
|
|
241
|
+
# === Post-processing ===
|
|
242
|
+
# Update count from cracked list if not found in stats
|
|
243
|
+
if result["stats"]["cracked_count"] == 0 and result["cracked"]:
|
|
244
|
+
result["stats"]["cracked_count"] = len(result["cracked"])
|
|
245
|
+
|
|
246
|
+
# If we found cracked hashes, ensure status reflects that
|
|
247
|
+
if result["cracked"] and result["stats"]["status"] == "unknown":
|
|
248
|
+
result["stats"]["status"] = "cracked"
|
|
249
|
+
|
|
250
|
+
# If no new cracks but potfile hits, retrieve from potfile using --show
|
|
251
|
+
if not result["cracked"] and result["stats"]["potfile_hits"] > 0:
|
|
252
|
+
if result["stats"]["status"] in ["unknown", "exhausted"]:
|
|
253
|
+
result["stats"]["status"] = "already_cracked"
|
|
254
|
+
|
|
255
|
+
# Try to retrieve already cracked passwords using hashcat --show
|
|
256
|
+
if hash_file:
|
|
257
|
+
potfile_cracked = _get_potfile_cracked(hash_file, hash_to_username)
|
|
258
|
+
if potfile_cracked:
|
|
259
|
+
result["cracked"] = potfile_cracked
|
|
260
|
+
result["stats"]["cracked_count"] = len(potfile_cracked)
|
|
261
|
+
|
|
92
262
|
return result
|
|
93
263
|
|
|
94
264
|
|
|
265
|
+
def _get_potfile_cracked(
|
|
266
|
+
hash_file: str, hash_to_username: Dict[str, str] = None
|
|
267
|
+
) -> List[Dict[str, Any]]:
|
|
268
|
+
"""
|
|
269
|
+
Retrieve already cracked passwords from hashcat potfile using --show.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
hash_file: Path to the hash file
|
|
273
|
+
hash_to_username: Mapping of hash -> username from the hash file
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
List of cracked password dicts with username, hash, password
|
|
277
|
+
"""
|
|
278
|
+
import subprocess
|
|
279
|
+
import os
|
|
280
|
+
|
|
281
|
+
cracked = []
|
|
282
|
+
|
|
283
|
+
if not hash_file or not os.path.exists(hash_file):
|
|
284
|
+
return cracked
|
|
285
|
+
|
|
286
|
+
try:
|
|
287
|
+
# Run hashcat --show to get cracked passwords from potfile
|
|
288
|
+
# Use -m 0 for MD5 (most common from SQLi dumps)
|
|
289
|
+
# --username flag tells hashcat the input format is username:hash
|
|
290
|
+
cmd = ["hashcat", "--show", "-m", "0", "--username", hash_file]
|
|
291
|
+
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
292
|
+
|
|
293
|
+
# Parse output: username:hash:password
|
|
294
|
+
for line in proc.stdout.strip().split("\n"):
|
|
295
|
+
line = line.strip()
|
|
296
|
+
if not line:
|
|
297
|
+
continue
|
|
298
|
+
|
|
299
|
+
parts = line.split(":")
|
|
300
|
+
if len(parts) >= 3:
|
|
301
|
+
username = parts[0].strip()
|
|
302
|
+
hash_value = parts[1].strip()
|
|
303
|
+
password = ":".join(parts[2:]).strip() # Password may contain ':'
|
|
304
|
+
|
|
305
|
+
if username and password:
|
|
306
|
+
cracked.append(
|
|
307
|
+
{
|
|
308
|
+
"hash": hash_value,
|
|
309
|
+
"password": password,
|
|
310
|
+
"hash_type": "md5",
|
|
311
|
+
"username": username,
|
|
312
|
+
}
|
|
313
|
+
)
|
|
314
|
+
elif len(parts) == 2:
|
|
315
|
+
# hash:password format (no username in output)
|
|
316
|
+
hash_value = parts[0].strip()
|
|
317
|
+
password = parts[1].strip()
|
|
318
|
+
|
|
319
|
+
# Try to get username from mapping
|
|
320
|
+
username = None
|
|
321
|
+
if hash_to_username:
|
|
322
|
+
username = hash_to_username.get(hash_value.lower())
|
|
323
|
+
|
|
324
|
+
if password:
|
|
325
|
+
cracked.append(
|
|
326
|
+
{
|
|
327
|
+
"hash": hash_value,
|
|
328
|
+
"password": password,
|
|
329
|
+
"hash_type": "md5",
|
|
330
|
+
"username": username,
|
|
331
|
+
}
|
|
332
|
+
)
|
|
333
|
+
except subprocess.TimeoutExpired:
|
|
334
|
+
pass
|
|
335
|
+
except Exception:
|
|
336
|
+
pass
|
|
337
|
+
|
|
338
|
+
return cracked
|
|
339
|
+
|
|
340
|
+
|
|
95
341
|
def parse_hashcat_potfile(potfile_path: str) -> List[Dict[str, str]]:
|
|
96
342
|
"""
|
|
97
343
|
Parse hashcat potfile (hashcat.potfile) for cracked passwords.
|
|
98
|
-
|
|
344
|
+
|
|
99
345
|
The potfile contains all previously cracked hashes in format:
|
|
100
346
|
hash:password
|
|
101
|
-
|
|
347
|
+
|
|
102
348
|
Args:
|
|
103
349
|
potfile_path: Path to hashcat.potfile
|
|
104
|
-
|
|
350
|
+
|
|
105
351
|
Returns:
|
|
106
352
|
List of dicts with hash and password
|
|
107
353
|
"""
|
|
108
354
|
cracked = []
|
|
109
|
-
|
|
355
|
+
|
|
110
356
|
try:
|
|
111
|
-
with open(potfile_path,
|
|
357
|
+
with open(potfile_path, "r", encoding="utf-8", errors="replace") as f:
|
|
112
358
|
for line in f:
|
|
113
359
|
line = line.strip()
|
|
114
|
-
if
|
|
115
|
-
parts = line.split(
|
|
360
|
+
if ":" in line:
|
|
361
|
+
parts = line.split(":", 1)
|
|
116
362
|
if len(parts) == 2:
|
|
117
|
-
cracked.append({
|
|
118
|
-
'hash': parts[0],
|
|
119
|
-
'password': parts[1]
|
|
120
|
-
})
|
|
363
|
+
cracked.append({"hash": parts[0], "password": parts[1]})
|
|
121
364
|
except Exception:
|
|
122
365
|
pass
|
|
123
|
-
|
|
366
|
+
|
|
124
367
|
return cracked
|
|
125
368
|
|
|
126
369
|
|
|
127
|
-
def map_to_credentials(
|
|
370
|
+
def map_to_credentials(
|
|
371
|
+
parsed_data: Dict[str, Any], engagement_id: int, host_id: int = None
|
|
372
|
+
) -> List[Dict[str, Any]]:
|
|
128
373
|
"""
|
|
129
374
|
Convert parsed hashcat data into credential records for database storage.
|
|
130
|
-
|
|
375
|
+
|
|
131
376
|
Args:
|
|
132
377
|
parsed_data: Output from parse_hashcat_output()
|
|
133
378
|
engagement_id: Current engagement ID
|
|
134
379
|
host_id: Optional host ID if known
|
|
135
|
-
|
|
380
|
+
|
|
136
381
|
Returns:
|
|
137
382
|
List of credential dicts ready for CredentialManager.add()
|
|
138
383
|
"""
|
|
139
384
|
credentials = []
|
|
140
|
-
|
|
141
|
-
for cracked in parsed_data.get(
|
|
385
|
+
|
|
386
|
+
for cracked in parsed_data.get("cracked", []):
|
|
142
387
|
credential = {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
388
|
+
"password": cracked["password"],
|
|
389
|
+
"credential_type": "password",
|
|
390
|
+
"source": "hashcat",
|
|
391
|
+
"validation_status": "cracked",
|
|
392
|
+
"notes": f"Cracked from hash: {cracked['hash'][:32]}...",
|
|
393
|
+
"hash_original": cracked["hash"],
|
|
149
394
|
}
|
|
150
|
-
|
|
395
|
+
|
|
151
396
|
if host_id:
|
|
152
|
-
credential[
|
|
153
|
-
|
|
397
|
+
credential["host_id"] = host_id
|
|
398
|
+
|
|
154
399
|
credentials.append(credential)
|
|
155
|
-
|
|
400
|
+
|
|
156
401
|
return credentials
|
|
157
402
|
|
|
158
403
|
|
|
159
|
-
__all__ = [
|
|
404
|
+
__all__ = ["parse_hashcat_output", "parse_hashcat_potfile", "map_to_credentials"]
|