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
|
@@ -21,59 +21,61 @@ def fix_job_counter():
|
|
|
21
21
|
jobs_dir = os.path.join(root, "data", "jobs")
|
|
22
22
|
jobs_file = os.path.join(jobs_dir, "jobs.json")
|
|
23
23
|
counter_file = os.path.join(jobs_dir, ".job_counter")
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
if not os.path.exists(jobs_dir):
|
|
26
26
|
print(f"✗ Jobs directory not found: {jobs_dir}")
|
|
27
27
|
return False
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
# Read existing jobs
|
|
30
30
|
jobs = []
|
|
31
31
|
if os.path.exists(jobs_file):
|
|
32
32
|
try:
|
|
33
|
-
with open(jobs_file,
|
|
33
|
+
with open(jobs_file, "r") as f:
|
|
34
34
|
jobs = json.load(f)
|
|
35
35
|
except Exception as e:
|
|
36
36
|
print(f"✗ Error reading jobs file: {e}")
|
|
37
37
|
return False
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
# Find max job ID
|
|
40
40
|
max_id = 0
|
|
41
41
|
stuck_jobs = []
|
|
42
42
|
for job in jobs:
|
|
43
|
-
job_id = job.get(
|
|
43
|
+
job_id = job.get("id", 0)
|
|
44
44
|
if job_id > max_id:
|
|
45
45
|
max_id = job_id
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
# Identify stuck jobs (queued with no PID)
|
|
48
|
-
if job.get(
|
|
48
|
+
if job.get("status") == "queued" and job.get("pid") is None:
|
|
49
49
|
stuck_jobs.append(job_id)
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
# Create counter file
|
|
52
52
|
next_id = max_id + 1
|
|
53
53
|
try:
|
|
54
|
-
with open(counter_file,
|
|
54
|
+
with open(counter_file, "w") as f:
|
|
55
55
|
f.write(str(next_id))
|
|
56
56
|
print(f"✓ Created job counter: next ID will be {next_id}")
|
|
57
57
|
except Exception as e:
|
|
58
58
|
print(f"✗ Error creating counter file: {e}")
|
|
59
59
|
return False
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
# Report stuck jobs
|
|
62
62
|
if stuck_jobs:
|
|
63
63
|
print(f"\n⚠️ Found {len(stuck_jobs)} stuck job(s): {stuck_jobs}")
|
|
64
64
|
print(" These jobs are in 'queued' status but have no PID (never started)")
|
|
65
|
-
print(
|
|
65
|
+
print(
|
|
66
|
+
" Recommendation: Delete them with 'souleyez jobs purge' or kill them individually"
|
|
67
|
+
)
|
|
66
68
|
else:
|
|
67
69
|
print("✓ No stuck jobs found")
|
|
68
|
-
|
|
70
|
+
|
|
69
71
|
print(f"\n✅ Migration complete!")
|
|
70
72
|
print(f" - Job counter initialized to {next_id}")
|
|
71
73
|
print(f" - Total jobs in queue: {len(jobs)}")
|
|
72
74
|
print(f" - Stuck jobs: {len(stuck_jobs)}")
|
|
73
|
-
|
|
75
|
+
|
|
74
76
|
return True
|
|
75
77
|
|
|
76
78
|
|
|
77
|
-
if __name__ ==
|
|
79
|
+
if __name__ == "__main__":
|
|
78
80
|
success = fix_job_counter()
|
|
79
81
|
sys.exit(0 if success else 1)
|
|
@@ -20,53 +20,55 @@ def parse_bloodhound(log_path: str, target: str) -> Dict:
|
|
|
20
20
|
"""
|
|
21
21
|
output_dir = Path.home() / ".souleyez" / "bloodhound_data"
|
|
22
22
|
|
|
23
|
-
collections = sorted(
|
|
23
|
+
collections = sorted(
|
|
24
|
+
output_dir.glob("*"), key=lambda p: p.stat().st_mtime, reverse=True
|
|
25
|
+
)
|
|
24
26
|
|
|
25
27
|
if not collections:
|
|
26
28
|
return {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
"tool": "bloodhound",
|
|
30
|
+
"target": target,
|
|
31
|
+
"error": "No Bloodhound data found",
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
latest_collection = collections[0]
|
|
33
35
|
|
|
34
36
|
stats = {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
"users": 0,
|
|
38
|
+
"groups": 0,
|
|
39
|
+
"computers": 0,
|
|
40
|
+
"domains": 0,
|
|
41
|
+
"gpos": 0,
|
|
42
|
+
"sessions": 0,
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
for json_file in latest_collection.glob("*.json"):
|
|
44
46
|
try:
|
|
45
|
-
with open(json_file,
|
|
47
|
+
with open(json_file, "r") as f:
|
|
46
48
|
data = json.load(f)
|
|
47
49
|
|
|
48
|
-
if
|
|
49
|
-
stats[
|
|
50
|
-
if
|
|
51
|
-
stats[
|
|
52
|
-
if
|
|
53
|
-
stats[
|
|
54
|
-
if
|
|
55
|
-
stats[
|
|
56
|
-
if
|
|
57
|
-
stats[
|
|
58
|
-
if
|
|
59
|
-
stats[
|
|
50
|
+
if "users" in data:
|
|
51
|
+
stats["users"] += len(data["users"])
|
|
52
|
+
if "groups" in data:
|
|
53
|
+
stats["groups"] += len(data["groups"])
|
|
54
|
+
if "computers" in data:
|
|
55
|
+
stats["computers"] += len(data["computers"])
|
|
56
|
+
if "domains" in data:
|
|
57
|
+
stats["domains"] += len(data["domains"])
|
|
58
|
+
if "gpos" in data:
|
|
59
|
+
stats["gpos"] += len(data["gpos"])
|
|
60
|
+
if "sessions" in data:
|
|
61
|
+
stats["sessions"] += len(data["sessions"])
|
|
60
62
|
except Exception:
|
|
61
63
|
continue
|
|
62
64
|
|
|
63
65
|
return {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
"tool": "bloodhound",
|
|
67
|
+
"target": target,
|
|
68
|
+
"collection_path": str(latest_collection),
|
|
69
|
+
"statistics": stats,
|
|
70
|
+
"summary": f"Enumerated {stats['users']} users, {stats['groups']} groups, "
|
|
71
|
+
f"{stats['computers']} computers in AD domain",
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
|
|
@@ -83,19 +85,19 @@ def store_bloodhound_results(result: Dict, engagement_id: int, job_id: int):
|
|
|
83
85
|
|
|
84
86
|
fm = FindingsManager()
|
|
85
87
|
|
|
86
|
-
stats = result.get(
|
|
88
|
+
stats = result.get("statistics", {})
|
|
87
89
|
|
|
88
90
|
fm.add_finding(
|
|
89
91
|
engagement_id=engagement_id,
|
|
90
92
|
title=f"Active Directory Enumerated - {stats.get('users', 0)} Users Discovered",
|
|
91
93
|
description=f"Bloodhound successfully enumerated Active Directory:\n"
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
severity=
|
|
99
|
-
tool=
|
|
100
|
-
evidence=result.get(
|
|
94
|
+
f"- Users: {stats.get('users', 0)}\n"
|
|
95
|
+
f"- Groups: {stats.get('groups', 0)}\n"
|
|
96
|
+
f"- Computers: {stats.get('computers', 0)}\n"
|
|
97
|
+
f"- GPOs: {stats.get('gpos', 0)}\n\n"
|
|
98
|
+
f"Data saved to: {result.get('collection_path', 'Unknown')}\n"
|
|
99
|
+
f"Import into Bloodhound GUI to visualize attack paths.",
|
|
100
|
+
severity="info",
|
|
101
|
+
tool="bloodhound",
|
|
102
|
+
evidence=result.get("summary", "AD enumeration complete"),
|
|
101
103
|
)
|
|
@@ -32,22 +32,22 @@ def parse_crackmapexec(log_path: str, target: str) -> Dict[str, Any]:
|
|
|
32
32
|
Dict containing parsed findings
|
|
33
33
|
"""
|
|
34
34
|
try:
|
|
35
|
-
with open(log_path,
|
|
35
|
+
with open(log_path, "r", encoding="utf-8", errors="replace") as f:
|
|
36
36
|
content = f.read()
|
|
37
37
|
return _parse_content(content, target)
|
|
38
38
|
except FileNotFoundError:
|
|
39
39
|
return {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
40
|
+
"tool": "crackmapexec",
|
|
41
|
+
"target": target,
|
|
42
|
+
"error": "Log file not found",
|
|
43
|
+
"findings": {
|
|
44
|
+
"hosts": [],
|
|
45
|
+
"shares": [],
|
|
46
|
+
"users": [],
|
|
47
|
+
"groups": [],
|
|
48
|
+
"credentials": [],
|
|
49
|
+
"vulnerabilities": [],
|
|
50
|
+
},
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
|
|
@@ -63,36 +63,45 @@ def _parse_content(content: str, target: str) -> Dict[str, Any]:
|
|
|
63
63
|
Dict containing parsed findings
|
|
64
64
|
"""
|
|
65
65
|
findings = {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
"hosts": [],
|
|
67
|
+
"shares": [],
|
|
68
|
+
"users": [],
|
|
69
|
+
"groups": [],
|
|
70
|
+
"credentials": [],
|
|
71
|
+
"vulnerabilities": [],
|
|
72
|
+
"auth_info": {},
|
|
73
|
+
"password_must_change": [], # Users with STATUS_PASSWORD_MUST_CHANGE
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
# Remove ANSI color codes first
|
|
76
|
-
content = re.sub(r
|
|
77
|
+
content = re.sub(r"\x1b\[[0-9;]*m", "", content)
|
|
77
78
|
|
|
78
|
-
for line in content.split(
|
|
79
|
+
for line in content.split("\n"):
|
|
79
80
|
# Parse host information (Windows OR Unix/Samba)
|
|
80
81
|
# Format variations:
|
|
81
82
|
# SMB 10.0.0.88 445 HOSTNAME [*] Windows/Unix ... (name:HOSTNAME) (domain:DOMAIN) ...
|
|
82
83
|
# SMB 10.0.0.88 445 HOSTNAME [*] Windows Server 2016 ...
|
|
83
84
|
# WINRM 10.0.0.88 5985 HOSTNAME [*] http://10.0.0.88:5985/wsman
|
|
84
85
|
|
|
85
|
-
os_keywords = [
|
|
86
|
-
if
|
|
86
|
+
os_keywords = ["Windows", "Unix", "Samba", "Linux", "Server", "Microsoft"]
|
|
87
|
+
if (
|
|
88
|
+
any(proto in line for proto in ["SMB", "WINRM", "SSH", "RDP"])
|
|
89
|
+
and "[*]" in line
|
|
90
|
+
):
|
|
87
91
|
# Try multiple patterns for host info
|
|
88
92
|
host_match = None
|
|
89
93
|
|
|
90
94
|
# Pattern 1: Standard format with flexible whitespace
|
|
91
|
-
host_match = re.search(
|
|
95
|
+
host_match = re.search(
|
|
96
|
+
r"(\d+\.\d+\.\d+\.\d+)\s+(\d+)\s+(\S+)\s+\[\*\]\s*(.+)", line
|
|
97
|
+
)
|
|
92
98
|
|
|
93
99
|
# Pattern 2: Protocol prefix format
|
|
94
100
|
if not host_match:
|
|
95
|
-
host_match = re.search(
|
|
101
|
+
host_match = re.search(
|
|
102
|
+
r"(?:SMB|WINRM|SSH|RDP)\s+(\d+\.\d+\.\d+\.\d+)\s+(\d+)\s+(\S+)\s+\[\*\]\s*(.+)",
|
|
103
|
+
line,
|
|
104
|
+
)
|
|
96
105
|
|
|
97
106
|
if host_match:
|
|
98
107
|
ip = host_match.group(1)
|
|
@@ -101,43 +110,51 @@ def _parse_content(content: str, target: str) -> Dict[str, Any]:
|
|
|
101
110
|
details = host_match.group(4).strip()
|
|
102
111
|
|
|
103
112
|
# Only process as host info if it looks like OS/version info
|
|
104
|
-
if any(kw in details for kw in os_keywords) or
|
|
113
|
+
if any(kw in details for kw in os_keywords) or "(domain:" in details:
|
|
105
114
|
# Extract domain from (domain:DOMAIN) or domain: pattern
|
|
106
|
-
domain_match = re.search(
|
|
115
|
+
domain_match = re.search(
|
|
116
|
+
r"\(?domain:?\s*([^)\s]+)\)?", details, re.IGNORECASE
|
|
117
|
+
)
|
|
107
118
|
domain = domain_match.group(1) if domain_match else None
|
|
108
119
|
|
|
109
120
|
# Extract OS info (everything before the first parenthesis)
|
|
110
|
-
os_match = re.match(r
|
|
121
|
+
os_match = re.match(r"([^(]+)", details)
|
|
111
122
|
os_info = os_match.group(1).strip() if os_match else details
|
|
112
123
|
|
|
113
124
|
# Extract SMB signing status (multiple formats)
|
|
114
|
-
signing_match = re.search(
|
|
125
|
+
signing_match = re.search(
|
|
126
|
+
r"\(?signing:?\s*(\w+)\)?", details, re.IGNORECASE
|
|
127
|
+
)
|
|
115
128
|
signing = signing_match.group(1) if signing_match else None
|
|
116
129
|
|
|
117
130
|
# Extract SMBv1 status
|
|
118
|
-
smbv1_match = re.search(
|
|
131
|
+
smbv1_match = re.search(
|
|
132
|
+
r"\(?SMBv1:?\s*(\w+)\)?", details, re.IGNORECASE
|
|
133
|
+
)
|
|
119
134
|
smbv1 = smbv1_match.group(1) if smbv1_match else None
|
|
120
135
|
|
|
121
|
-
findings[
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
136
|
+
findings["hosts"].append(
|
|
137
|
+
{
|
|
138
|
+
"ip": ip,
|
|
139
|
+
"port": port,
|
|
140
|
+
"hostname": hostname,
|
|
141
|
+
"domain": domain,
|
|
142
|
+
"os": os_info,
|
|
143
|
+
"signing": signing,
|
|
144
|
+
"smbv1": smbv1,
|
|
145
|
+
}
|
|
146
|
+
)
|
|
130
147
|
|
|
131
148
|
# Parse authentication status
|
|
132
149
|
# Format: SMB 10.0.0.14 445 HOSTNAME [+] \: (Guest)
|
|
133
|
-
if
|
|
134
|
-
auth_match = re.search(r
|
|
150
|
+
if "SMB" in line and "[+]" in line and ("Guest" in line or "\\" in line):
|
|
151
|
+
auth_match = re.search(r"\[\+\]\s+(.+)", line)
|
|
135
152
|
if auth_match:
|
|
136
153
|
auth_str = auth_match.group(1).strip()
|
|
137
|
-
findings[
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
154
|
+
findings["auth_info"] = {
|
|
155
|
+
"status": "success",
|
|
156
|
+
"details": auth_str,
|
|
157
|
+
"is_guest": "Guest" in auth_str,
|
|
141
158
|
}
|
|
142
159
|
|
|
143
160
|
# Parse share enumeration (shares WITH permissions)
|
|
@@ -146,59 +163,102 @@ def _parse_content(content: str, target: str) -> Dict[str, Any]:
|
|
|
146
163
|
# SMB ... ADMIN$ READ, WRITE Remote Admin (with space)
|
|
147
164
|
# SMB ... C$ READ ONLY Default share
|
|
148
165
|
share_perm_match = re.search(
|
|
149
|
-
r
|
|
150
|
-
line,
|
|
166
|
+
r"SMB.*\s+(\S+\$?)\s+(READ,?\s*WRITE|READ\s*ONLY|WRITE\s*ONLY|READ|WRITE|NO\s*ACCESS)\s*(.*)$",
|
|
167
|
+
line,
|
|
168
|
+
re.IGNORECASE,
|
|
151
169
|
)
|
|
152
170
|
if share_perm_match:
|
|
153
171
|
share_name = share_perm_match.group(1)
|
|
154
172
|
# Skip if it looks like a header or status line
|
|
155
|
-
if share_name not in [
|
|
156
|
-
findings[
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
173
|
+
if share_name not in ["Share", "Permissions", "shares"]:
|
|
174
|
+
findings["shares"].append(
|
|
175
|
+
{
|
|
176
|
+
"name": share_name,
|
|
177
|
+
"permissions": share_perm_match.group(2)
|
|
178
|
+
.upper()
|
|
179
|
+
.replace(" ", ""),
|
|
180
|
+
"comment": (
|
|
181
|
+
share_perm_match.group(3).strip()
|
|
182
|
+
if share_perm_match.group(3)
|
|
183
|
+
else ""
|
|
184
|
+
),
|
|
185
|
+
}
|
|
186
|
+
)
|
|
161
187
|
# Parse share enumeration (shares WITHOUT explicit permissions - just listed)
|
|
162
|
-
elif
|
|
188
|
+
elif (
|
|
189
|
+
"SMB" in line
|
|
190
|
+
and not ("Share" in line and "Permissions" in line)
|
|
191
|
+
and not "-----" in line
|
|
192
|
+
):
|
|
163
193
|
# Look for lines with share names (ending with $, or common names like print$, public, IPC$)
|
|
164
|
-
share_list_match = re.search(
|
|
194
|
+
share_list_match = re.search(
|
|
195
|
+
r"SMB\s+\S+\s+\d+\s+\S+\s+(\w+\$?|\w+)\s+(.+)?$", line
|
|
196
|
+
)
|
|
165
197
|
if share_list_match:
|
|
166
198
|
share_name = share_list_match.group(1).strip()
|
|
167
|
-
remark =
|
|
199
|
+
remark = (
|
|
200
|
+
share_list_match.group(2).strip()
|
|
201
|
+
if share_list_match.group(2)
|
|
202
|
+
else ""
|
|
203
|
+
)
|
|
168
204
|
# Only add if it looks like a share (not header text, not empty)
|
|
169
|
-
if
|
|
205
|
+
if (
|
|
206
|
+
share_name
|
|
207
|
+
and share_name not in ["Share", "Enumerated", "shares"]
|
|
208
|
+
and not share_name.startswith("[")
|
|
209
|
+
):
|
|
170
210
|
# Check if not already added
|
|
171
|
-
if not any(s[
|
|
172
|
-
findings[
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
'comment': remark
|
|
176
|
-
})
|
|
211
|
+
if not any(s["name"] == share_name for s in findings["shares"]):
|
|
212
|
+
findings["shares"].append(
|
|
213
|
+
{"name": share_name, "permissions": None, "comment": remark}
|
|
214
|
+
)
|
|
177
215
|
|
|
178
216
|
# Parse user enumeration with flexible format
|
|
179
217
|
# Format variations:
|
|
180
218
|
# username badpwdcount: 0 desc: Description
|
|
181
219
|
# username badpwdcount:0 desc:Description
|
|
182
220
|
# username baddpwdcount: 0 description: Description
|
|
183
|
-
if
|
|
221
|
+
if "badpwdcount" in line.lower() or "baddpwdcount" in line.lower():
|
|
184
222
|
user_match = re.search(
|
|
185
|
-
r
|
|
186
|
-
line,
|
|
223
|
+
r"(\S+)\s+bad+pwdcount:?\s*(\d+)\s+(?:desc(?:ription)?:?\s*)?(.+)?",
|
|
224
|
+
line,
|
|
225
|
+
re.IGNORECASE,
|
|
187
226
|
)
|
|
188
227
|
if user_match:
|
|
189
|
-
findings[
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
228
|
+
findings["users"].append(
|
|
229
|
+
{
|
|
230
|
+
"username": user_match.group(1),
|
|
231
|
+
"badpwdcount": int(user_match.group(2)),
|
|
232
|
+
"description": (
|
|
233
|
+
user_match.group(3).strip() if user_match.group(3) else ""
|
|
234
|
+
),
|
|
235
|
+
}
|
|
236
|
+
)
|
|
194
237
|
|
|
195
238
|
# Parse vulnerability findings
|
|
196
|
-
if
|
|
197
|
-
vuln_match = re.search(r
|
|
239
|
+
if "vulnerable" in line.lower() or "MS17-010" in line:
|
|
240
|
+
vuln_match = re.search(r"\[\+\]\s+(.+)", line)
|
|
198
241
|
if vuln_match:
|
|
199
|
-
findings[
|
|
200
|
-
|
|
201
|
-
|
|
242
|
+
findings["vulnerabilities"].append(
|
|
243
|
+
{"description": vuln_match.group(1).strip()}
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# Parse STATUS_PASSWORD_MUST_CHANGE - user needs to change password
|
|
247
|
+
# Format: SMB IP 445 HOST [-] domain\user:password STATUS_PASSWORD_MUST_CHANGE
|
|
248
|
+
if "STATUS_PASSWORD_MUST_CHANGE" in line:
|
|
249
|
+
pwchange_match = re.search(
|
|
250
|
+
r"\[-\]\s*([^\\/:]+)[\\\/]+([^:]+):([^\s]+)\s+STATUS_PASSWORD_MUST_CHANGE",
|
|
251
|
+
line,
|
|
252
|
+
re.IGNORECASE,
|
|
253
|
+
)
|
|
254
|
+
if pwchange_match:
|
|
255
|
+
findings["password_must_change"].append(
|
|
256
|
+
{
|
|
257
|
+
"domain": pwchange_match.group(1).strip(),
|
|
258
|
+
"username": pwchange_match.group(2).strip(),
|
|
259
|
+
"password": pwchange_match.group(3).strip(),
|
|
260
|
+
}
|
|
261
|
+
)
|
|
202
262
|
|
|
203
263
|
# Parse valid credentials (but not Guest authentication)
|
|
204
264
|
# Format variations:
|
|
@@ -206,57 +266,64 @@ def _parse_content(content: str, target: str) -> Dict[str, Any]:
|
|
|
206
266
|
# [+] DOMAIN\\username:password (Pwn3d!)
|
|
207
267
|
# [+] username:password (Pwn3d!)
|
|
208
268
|
# [+] DOMAIN/username:password (Pwn3d!)
|
|
209
|
-
if
|
|
269
|
+
if "[+]" in line and ("Pwn3d" in line or ":" in line):
|
|
210
270
|
# Try domain\user:pass format first
|
|
211
271
|
cred_match = re.search(
|
|
212
|
-
r
|
|
213
|
-
line,
|
|
272
|
+
r"\[\+\]\s*([^\\/:]+)[\\\/]+([^:]+):([^\s(]+)\s*(\(Pwn3d!?\))?",
|
|
273
|
+
line,
|
|
274
|
+
re.IGNORECASE,
|
|
214
275
|
)
|
|
215
276
|
if cred_match:
|
|
216
|
-
findings[
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
277
|
+
findings["credentials"].append(
|
|
278
|
+
{
|
|
279
|
+
"domain": cred_match.group(1).strip(),
|
|
280
|
+
"username": cred_match.group(2).strip(),
|
|
281
|
+
"password": cred_match.group(3).strip(),
|
|
282
|
+
"admin": bool(cred_match.group(4)),
|
|
283
|
+
}
|
|
284
|
+
)
|
|
222
285
|
else:
|
|
223
286
|
# Try user:pass format (no domain)
|
|
224
287
|
cred_match = re.search(
|
|
225
|
-
r
|
|
226
|
-
line,
|
|
288
|
+
r"\[\+\]\s*([^:@\s]+):([^\s(]+)\s*(\(Pwn3d!?\))?",
|
|
289
|
+
line,
|
|
290
|
+
re.IGNORECASE,
|
|
227
291
|
)
|
|
228
|
-
if cred_match and
|
|
229
|
-
findings[
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
292
|
+
if cred_match and "@" not in cred_match.group(1):
|
|
293
|
+
findings["credentials"].append(
|
|
294
|
+
{
|
|
295
|
+
"domain": "",
|
|
296
|
+
"username": cred_match.group(1).strip(),
|
|
297
|
+
"password": cred_match.group(2).strip(),
|
|
298
|
+
"admin": bool(cred_match.group(3)),
|
|
299
|
+
}
|
|
300
|
+
)
|
|
235
301
|
|
|
236
302
|
# Extract admin credentials for auto-chaining
|
|
237
|
-
admin_creds = [c for c in findings[
|
|
303
|
+
admin_creds = [c for c in findings["credentials"] if c.get("admin")]
|
|
238
304
|
|
|
239
305
|
# Extract unique domains for auto-chaining
|
|
240
306
|
domains = []
|
|
241
307
|
seen_domains = set()
|
|
242
|
-
for host in findings[
|
|
243
|
-
domain = host.get(
|
|
308
|
+
for host in findings["hosts"]:
|
|
309
|
+
domain = host.get("domain")
|
|
244
310
|
if domain and domain not in seen_domains:
|
|
245
|
-
domains.append({
|
|
246
|
-
'domain': domain,
|
|
247
|
-
'ip': host.get('ip')
|
|
248
|
-
})
|
|
311
|
+
domains.append({"domain": domain, "ip": host.get("ip")})
|
|
249
312
|
seen_domains.add(domain)
|
|
250
313
|
|
|
251
314
|
return {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
315
|
+
"tool": "crackmapexec",
|
|
316
|
+
"target": target,
|
|
317
|
+
"hosts_found": len(findings["hosts"]),
|
|
318
|
+
"shares_found": len(findings["shares"]),
|
|
319
|
+
"users_found": len(findings["users"]),
|
|
320
|
+
"valid_credentials": len(findings["credentials"]),
|
|
321
|
+
"vulnerabilities_found": len(findings["vulnerabilities"]),
|
|
322
|
+
"password_must_change_found": len(findings["password_must_change"]),
|
|
323
|
+
"findings": findings,
|
|
324
|
+
"domains": domains, # For auto-chaining to GetNPUsers and other AD tools
|
|
325
|
+
"valid_admin_credentials": admin_creds, # For auto-chaining to secretsdump
|
|
326
|
+
"password_must_change": findings[
|
|
327
|
+
"password_must_change"
|
|
328
|
+
], # For smbpasswd chaining
|
|
262
329
|
}
|