souleyez 2.43.29__py3-none-any.whl → 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- souleyez/__init__.py +1 -2
- souleyez/ai/__init__.py +21 -15
- souleyez/ai/action_mapper.py +249 -150
- souleyez/ai/chain_advisor.py +116 -100
- souleyez/ai/claude_provider.py +29 -28
- souleyez/ai/context_builder.py +80 -62
- souleyez/ai/executor.py +158 -117
- souleyez/ai/feedback_handler.py +136 -121
- souleyez/ai/llm_factory.py +27 -20
- souleyez/ai/llm_provider.py +4 -2
- souleyez/ai/ollama_provider.py +6 -9
- souleyez/ai/ollama_service.py +44 -37
- souleyez/ai/path_scorer.py +91 -76
- souleyez/ai/recommender.py +176 -144
- souleyez/ai/report_context.py +74 -73
- souleyez/ai/report_service.py +84 -66
- souleyez/ai/result_parser.py +222 -229
- souleyez/ai/safety.py +67 -44
- souleyez/auth/__init__.py +23 -22
- souleyez/auth/audit.py +36 -26
- souleyez/auth/engagement_access.py +65 -48
- souleyez/auth/permissions.py +14 -3
- souleyez/auth/session_manager.py +54 -37
- souleyez/auth/user_manager.py +109 -64
- souleyez/commands/audit.py +40 -43
- souleyez/commands/auth.py +35 -15
- souleyez/commands/deliverables.py +55 -50
- souleyez/commands/engagement.py +47 -28
- souleyez/commands/license.py +32 -23
- souleyez/commands/screenshots.py +36 -32
- souleyez/commands/user.py +82 -36
- souleyez/config.py +52 -44
- souleyez/core/credential_tester.py +87 -81
- souleyez/core/cve_mappings.py +179 -192
- souleyez/core/cve_matcher.py +162 -148
- souleyez/core/msf_auto_mapper.py +100 -83
- souleyez/core/msf_chain_engine.py +294 -256
- souleyez/core/msf_database.py +153 -70
- souleyez/core/msf_integration.py +679 -673
- souleyez/core/msf_rpc_client.py +40 -42
- souleyez/core/msf_rpc_manager.py +77 -79
- souleyez/core/msf_sync_manager.py +241 -181
- souleyez/core/network_utils.py +22 -15
- souleyez/core/parser_handler.py +34 -25
- souleyez/core/pending_chains.py +114 -63
- souleyez/core/templates.py +158 -107
- souleyez/core/tool_chaining.py +9564 -2881
- souleyez/core/version_utils.py +79 -94
- souleyez/core/vuln_correlation.py +136 -89
- souleyez/core/web_utils.py +33 -32
- souleyez/data/wordlists/ad_users.txt +378 -0
- souleyez/data/wordlists/api_endpoints_large.txt +769 -0
- souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
- souleyez/data/wordlists/lfi_payloads.txt +82 -0
- souleyez/data/wordlists/passwords_brute.txt +1548 -0
- souleyez/data/wordlists/passwords_crack.txt +2479 -0
- souleyez/data/wordlists/passwords_spray.txt +386 -0
- souleyez/data/wordlists/subdomains_large.txt +5057 -0
- souleyez/data/wordlists/usernames_common.txt +694 -0
- souleyez/data/wordlists/web_dirs_large.txt +4769 -0
- souleyez/detection/__init__.py +1 -1
- souleyez/detection/attack_signatures.py +12 -17
- souleyez/detection/mitre_mappings.py +61 -55
- souleyez/detection/validator.py +97 -86
- souleyez/devtools.py +23 -10
- souleyez/docs/README.md +4 -4
- souleyez/docs/api-reference/cli-commands.md +2 -2
- souleyez/docs/developer-guide/adding-new-tools.md +562 -0
- souleyez/docs/user-guide/auto-chaining.md +30 -8
- souleyez/docs/user-guide/getting-started.md +1 -1
- souleyez/docs/user-guide/installation.md +26 -3
- souleyez/docs/user-guide/metasploit-integration.md +2 -2
- souleyez/docs/user-guide/rbac.md +1 -1
- souleyez/docs/user-guide/scope-management.md +1 -1
- souleyez/docs/user-guide/siem-integration.md +1 -1
- souleyez/docs/user-guide/tools-reference.md +1 -8
- souleyez/docs/user-guide/worker-management.md +1 -1
- souleyez/engine/background.py +1239 -535
- souleyez/engine/base.py +4 -1
- souleyez/engine/job_status.py +17 -49
- souleyez/engine/log_sanitizer.py +103 -77
- souleyez/engine/manager.py +38 -7
- souleyez/engine/result_handler.py +2200 -1550
- souleyez/engine/worker_manager.py +50 -41
- souleyez/export/evidence_bundle.py +72 -62
- souleyez/feature_flags/features.py +16 -20
- souleyez/feature_flags.py +5 -9
- souleyez/handlers/__init__.py +11 -0
- souleyez/handlers/base.py +188 -0
- souleyez/handlers/bash_handler.py +277 -0
- souleyez/handlers/bloodhound_handler.py +243 -0
- souleyez/handlers/certipy_handler.py +311 -0
- souleyez/handlers/crackmapexec_handler.py +486 -0
- souleyez/handlers/dnsrecon_handler.py +344 -0
- souleyez/handlers/enum4linux_handler.py +400 -0
- souleyez/handlers/evil_winrm_handler.py +493 -0
- souleyez/handlers/ffuf_handler.py +815 -0
- souleyez/handlers/gobuster_handler.py +1114 -0
- souleyez/handlers/gpp_extract_handler.py +334 -0
- souleyez/handlers/hashcat_handler.py +444 -0
- souleyez/handlers/hydra_handler.py +564 -0
- souleyez/handlers/impacket_getuserspns_handler.py +343 -0
- souleyez/handlers/impacket_psexec_handler.py +222 -0
- souleyez/handlers/impacket_secretsdump_handler.py +426 -0
- souleyez/handlers/john_handler.py +286 -0
- souleyez/handlers/katana_handler.py +425 -0
- souleyez/handlers/kerbrute_handler.py +298 -0
- souleyez/handlers/ldapsearch_handler.py +636 -0
- souleyez/handlers/lfi_extract_handler.py +464 -0
- souleyez/handlers/msf_auxiliary_handler.py +409 -0
- souleyez/handlers/msf_exploit_handler.py +380 -0
- souleyez/handlers/nikto_handler.py +413 -0
- souleyez/handlers/nmap_handler.py +821 -0
- souleyez/handlers/nuclei_handler.py +359 -0
- souleyez/handlers/nxc_handler.py +417 -0
- souleyez/handlers/rdp_sec_check_handler.py +353 -0
- souleyez/handlers/registry.py +292 -0
- souleyez/handlers/responder_handler.py +232 -0
- souleyez/handlers/service_explorer_handler.py +434 -0
- souleyez/handlers/smbclient_handler.py +344 -0
- souleyez/handlers/smbmap_handler.py +510 -0
- souleyez/handlers/smbpasswd_handler.py +296 -0
- souleyez/handlers/sqlmap_handler.py +1116 -0
- souleyez/handlers/theharvester_handler.py +601 -0
- souleyez/handlers/web_login_test_handler.py +327 -0
- souleyez/handlers/whois_handler.py +277 -0
- souleyez/handlers/wpscan_handler.py +554 -0
- souleyez/history.py +32 -16
- souleyez/importers/msf_importer.py +106 -75
- souleyez/importers/smart_importer.py +208 -147
- souleyez/integrations/siem/__init__.py +10 -10
- souleyez/integrations/siem/base.py +17 -18
- souleyez/integrations/siem/elastic.py +108 -122
- souleyez/integrations/siem/factory.py +207 -80
- souleyez/integrations/siem/googlesecops.py +146 -154
- souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
- souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
- souleyez/integrations/siem/sentinel.py +107 -109
- souleyez/integrations/siem/splunk.py +246 -212
- souleyez/integrations/siem/wazuh.py +65 -71
- souleyez/integrations/wazuh/__init__.py +5 -5
- souleyez/integrations/wazuh/client.py +70 -93
- souleyez/integrations/wazuh/config.py +85 -57
- souleyez/integrations/wazuh/host_mapper.py +28 -36
- souleyez/integrations/wazuh/sync.py +78 -68
- souleyez/intelligence/__init__.py +4 -5
- souleyez/intelligence/correlation_analyzer.py +309 -295
- souleyez/intelligence/exploit_knowledge.py +661 -623
- souleyez/intelligence/exploit_suggestions.py +159 -139
- souleyez/intelligence/gap_analyzer.py +132 -97
- souleyez/intelligence/gap_detector.py +251 -214
- souleyez/intelligence/sensitive_tables.py +266 -129
- souleyez/intelligence/service_parser.py +137 -123
- souleyez/intelligence/surface_analyzer.py +407 -268
- souleyez/intelligence/target_parser.py +159 -162
- souleyez/licensing/__init__.py +6 -6
- souleyez/licensing/validator.py +17 -19
- souleyez/log_config.py +79 -54
- souleyez/main.py +1505 -687
- souleyez/migrations/fix_job_counter.py +16 -14
- souleyez/parsers/bloodhound_parser.py +41 -39
- souleyez/parsers/crackmapexec_parser.py +178 -111
- souleyez/parsers/dalfox_parser.py +72 -77
- souleyez/parsers/dnsrecon_parser.py +103 -91
- souleyez/parsers/enum4linux_parser.py +183 -153
- souleyez/parsers/ffuf_parser.py +29 -25
- souleyez/parsers/gobuster_parser.py +301 -41
- souleyez/parsers/hashcat_parser.py +324 -79
- souleyez/parsers/http_fingerprint_parser.py +350 -103
- souleyez/parsers/hydra_parser.py +131 -111
- souleyez/parsers/impacket_parser.py +231 -178
- souleyez/parsers/john_parser.py +98 -86
- souleyez/parsers/katana_parser.py +316 -0
- souleyez/parsers/msf_parser.py +943 -498
- souleyez/parsers/nikto_parser.py +346 -65
- souleyez/parsers/nmap_parser.py +262 -174
- souleyez/parsers/nuclei_parser.py +40 -44
- souleyez/parsers/responder_parser.py +26 -26
- souleyez/parsers/searchsploit_parser.py +74 -74
- souleyez/parsers/service_explorer_parser.py +279 -0
- souleyez/parsers/smbmap_parser.py +180 -124
- souleyez/parsers/sqlmap_parser.py +434 -308
- souleyez/parsers/theharvester_parser.py +75 -57
- souleyez/parsers/whois_parser.py +135 -94
- souleyez/parsers/wpscan_parser.py +278 -190
- souleyez/plugins/afp.py +44 -36
- souleyez/plugins/afp_brute.py +114 -46
- souleyez/plugins/ard.py +48 -37
- souleyez/plugins/bloodhound.py +95 -61
- souleyez/plugins/certipy.py +303 -0
- souleyez/plugins/crackmapexec.py +186 -85
- souleyez/plugins/dalfox.py +120 -59
- souleyez/plugins/dns_hijack.py +146 -41
- souleyez/plugins/dnsrecon.py +97 -61
- souleyez/plugins/enum4linux.py +91 -66
- souleyez/plugins/evil_winrm.py +291 -0
- souleyez/plugins/ffuf.py +166 -90
- souleyez/plugins/firmware_extract.py +133 -29
- souleyez/plugins/gobuster.py +387 -190
- souleyez/plugins/gpp_extract.py +393 -0
- souleyez/plugins/hashcat.py +100 -73
- souleyez/plugins/http_fingerprint.py +913 -267
- souleyez/plugins/hydra.py +566 -200
- souleyez/plugins/impacket_getnpusers.py +117 -69
- souleyez/plugins/impacket_psexec.py +84 -64
- souleyez/plugins/impacket_secretsdump.py +103 -69
- souleyez/plugins/impacket_smbclient.py +89 -75
- souleyez/plugins/john.py +86 -69
- souleyez/plugins/katana.py +313 -0
- souleyez/plugins/kerbrute.py +237 -0
- souleyez/plugins/lfi_extract.py +541 -0
- souleyez/plugins/macos_ssh.py +117 -48
- souleyez/plugins/mdns.py +35 -30
- souleyez/plugins/msf_auxiliary.py +253 -130
- souleyez/plugins/msf_exploit.py +239 -161
- souleyez/plugins/nikto.py +134 -78
- souleyez/plugins/nmap.py +275 -91
- souleyez/plugins/nuclei.py +180 -89
- souleyez/plugins/nxc.py +285 -0
- souleyez/plugins/plugin_base.py +35 -36
- souleyez/plugins/plugin_template.py +13 -5
- souleyez/plugins/rdp_sec_check.py +130 -0
- souleyez/plugins/responder.py +112 -71
- souleyez/plugins/router_http_brute.py +76 -65
- souleyez/plugins/router_ssh_brute.py +118 -41
- souleyez/plugins/router_telnet_brute.py +124 -42
- souleyez/plugins/routersploit.py +91 -59
- souleyez/plugins/routersploit_exploit.py +77 -55
- souleyez/plugins/searchsploit.py +91 -77
- souleyez/plugins/service_explorer.py +1160 -0
- souleyez/plugins/smbmap.py +122 -72
- souleyez/plugins/smbpasswd.py +215 -0
- souleyez/plugins/sqlmap.py +301 -113
- souleyez/plugins/theharvester.py +127 -75
- souleyez/plugins/tr069.py +79 -57
- souleyez/plugins/upnp.py +65 -47
- souleyez/plugins/upnp_abuse.py +73 -55
- souleyez/plugins/vnc_access.py +129 -42
- souleyez/plugins/vnc_brute.py +109 -38
- souleyez/plugins/web_login_test.py +417 -0
- souleyez/plugins/whois.py +77 -58
- souleyez/plugins/wpscan.py +219 -69
- souleyez/reporting/__init__.py +2 -1
- souleyez/reporting/attack_chain.py +411 -346
- souleyez/reporting/charts.py +436 -501
- souleyez/reporting/compliance_mappings.py +334 -201
- souleyez/reporting/detection_report.py +126 -125
- souleyez/reporting/formatters.py +828 -591
- souleyez/reporting/generator.py +386 -302
- souleyez/reporting/metrics.py +72 -75
- souleyez/scanner.py +35 -29
- souleyez/security/__init__.py +37 -11
- souleyez/security/scope_validator.py +175 -106
- souleyez/security/validation.py +237 -149
- souleyez/security.py +22 -6
- souleyez/storage/credentials.py +247 -186
- souleyez/storage/crypto.py +296 -129
- souleyez/storage/database.py +73 -50
- souleyez/storage/db.py +58 -36
- souleyez/storage/deliverable_evidence.py +177 -128
- souleyez/storage/deliverable_exporter.py +282 -246
- souleyez/storage/deliverable_templates.py +134 -116
- souleyez/storage/deliverables.py +135 -130
- souleyez/storage/engagements.py +109 -56
- souleyez/storage/evidence.py +181 -152
- souleyez/storage/execution_log.py +31 -17
- souleyez/storage/exploit_attempts.py +93 -57
- souleyez/storage/exploits.py +67 -36
- souleyez/storage/findings.py +48 -61
- souleyez/storage/hosts.py +176 -144
- souleyez/storage/migrate_to_engagements.py +43 -19
- souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
- souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
- souleyez/storage/migrations/_003_add_execution_log.py +14 -8
- souleyez/storage/migrations/_005_screenshots.py +13 -5
- souleyez/storage/migrations/_006_deliverables.py +13 -5
- souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
- souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
- souleyez/storage/migrations/_010_evidence_linking.py +17 -10
- souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
- souleyez/storage/migrations/_012_team_collaboration.py +34 -21
- souleyez/storage/migrations/_013_add_host_tags.py +12 -6
- souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
- souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
- souleyez/storage/migrations/_016_add_domain_field.py +10 -4
- souleyez/storage/migrations/_017_msf_sessions.py +16 -8
- souleyez/storage/migrations/_018_add_osint_target.py +10 -6
- souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
- souleyez/storage/migrations/_020_add_rbac.py +36 -15
- souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
- souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
- souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
- souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
- souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
- souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
- souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
- souleyez/storage/migrations/__init__.py +26 -26
- souleyez/storage/migrations/migration_manager.py +19 -19
- souleyez/storage/msf_sessions.py +100 -65
- souleyez/storage/osint.py +17 -24
- souleyez/storage/recommendation_engine.py +269 -235
- souleyez/storage/screenshots.py +33 -32
- souleyez/storage/smb_shares.py +136 -92
- souleyez/storage/sqlmap_data.py +183 -128
- souleyez/storage/team_collaboration.py +135 -141
- souleyez/storage/timeline_tracker.py +122 -94
- souleyez/storage/wazuh_vulns.py +64 -66
- souleyez/storage/web_paths.py +33 -37
- souleyez/testing/credential_tester.py +221 -205
- souleyez/ui/__init__.py +1 -1
- souleyez/ui/ai_quotes.py +12 -12
- souleyez/ui/attack_surface.py +2439 -1516
- souleyez/ui/chain_rules_view.py +914 -382
- souleyez/ui/correlation_view.py +312 -230
- souleyez/ui/dashboard.py +2382 -1130
- souleyez/ui/deliverables_view.py +148 -62
- souleyez/ui/design_system.py +13 -13
- souleyez/ui/errors.py +49 -49
- souleyez/ui/evidence_linking_view.py +284 -179
- souleyez/ui/evidence_vault.py +393 -285
- souleyez/ui/exploit_suggestions_view.py +555 -349
- souleyez/ui/export_view.py +100 -66
- souleyez/ui/gap_analysis_view.py +315 -171
- souleyez/ui/help_system.py +105 -97
- souleyez/ui/intelligence_view.py +436 -293
- souleyez/ui/interactive.py +23034 -10679
- souleyez/ui/interactive_selector.py +75 -68
- souleyez/ui/log_formatter.py +47 -39
- souleyez/ui/menu_components.py +22 -13
- souleyez/ui/msf_auxiliary_menu.py +184 -133
- souleyez/ui/pending_chains_view.py +336 -172
- souleyez/ui/progress_indicators.py +5 -3
- souleyez/ui/recommendations_view.py +195 -137
- souleyez/ui/rule_builder.py +343 -225
- souleyez/ui/setup_wizard.py +678 -284
- souleyez/ui/shortcuts.py +217 -165
- souleyez/ui/splunk_gap_analysis_view.py +452 -270
- souleyez/ui/splunk_vulns_view.py +139 -86
- souleyez/ui/team_dashboard.py +498 -335
- souleyez/ui/template_selector.py +196 -105
- souleyez/ui/terminal.py +6 -6
- souleyez/ui/timeline_view.py +198 -127
- souleyez/ui/tool_setup.py +264 -164
- souleyez/ui/tutorial.py +202 -72
- souleyez/ui/tutorial_state.py +40 -40
- souleyez/ui/wazuh_vulns_view.py +235 -141
- souleyez/ui/wordlist_browser.py +260 -107
- souleyez/ui.py +464 -312
- souleyez/utils/tool_checker.py +427 -367
- souleyez/utils.py +33 -29
- souleyez/wordlists.py +134 -167
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/METADATA +2 -2
- souleyez-3.0.0.dist-info/RECORD +443 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/WHEEL +1 -1
- souleyez-2.43.29.dist-info/RECORD +0 -379
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/entry_points.txt +0 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Web Login Test handler.
|
|
4
|
+
|
|
5
|
+
Parses and displays results from web login credential tests.
|
|
6
|
+
"""
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
from typing import Any, Dict, Optional
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
|
|
15
|
+
from souleyez.engine.job_status import STATUS_DONE, STATUS_ERROR, STATUS_NO_RESULTS
|
|
16
|
+
from souleyez.handlers.base import BaseToolHandler
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class WebLoginTestHandler(BaseToolHandler):
|
|
22
|
+
"""Handler for web login credential test jobs."""
|
|
23
|
+
|
|
24
|
+
tool_name = "web_login_test"
|
|
25
|
+
display_name = "Web Login Test"
|
|
26
|
+
|
|
27
|
+
has_error_handler = True
|
|
28
|
+
has_warning_handler = False
|
|
29
|
+
has_no_results_handler = True
|
|
30
|
+
has_done_handler = True
|
|
31
|
+
|
|
32
|
+
def parse_job(
|
|
33
|
+
self,
|
|
34
|
+
engagement_id: int,
|
|
35
|
+
log_path: str,
|
|
36
|
+
job: Dict[str, Any],
|
|
37
|
+
host_manager: Optional[Any] = None,
|
|
38
|
+
findings_manager: Optional[Any] = None,
|
|
39
|
+
credentials_manager: Optional[Any] = None,
|
|
40
|
+
) -> Dict[str, Any]:
|
|
41
|
+
"""
|
|
42
|
+
Parse web login test results.
|
|
43
|
+
|
|
44
|
+
Returns parsed result including:
|
|
45
|
+
- login_success: bool
|
|
46
|
+
- username: str
|
|
47
|
+
- http_code: int
|
|
48
|
+
- reason: str
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
if not log_path or not os.path.exists(log_path):
|
|
52
|
+
return {
|
|
53
|
+
"tool": self.tool_name,
|
|
54
|
+
"status": STATUS_ERROR,
|
|
55
|
+
"error": "Log file not found",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
with open(log_path, "r", encoding="utf-8", errors="replace") as f:
|
|
59
|
+
log_content = f.read()
|
|
60
|
+
|
|
61
|
+
# Try to parse JSON result
|
|
62
|
+
json_match = re.search(
|
|
63
|
+
r"=== JSON_RESULT ===\s*(.*?)\s*=== END_JSON_RESULT ===",
|
|
64
|
+
log_content,
|
|
65
|
+
re.DOTALL,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
result = {
|
|
69
|
+
"tool": self.tool_name,
|
|
70
|
+
"status": STATUS_NO_RESULTS,
|
|
71
|
+
"login_success": False,
|
|
72
|
+
"username": None,
|
|
73
|
+
"http_code": None,
|
|
74
|
+
"reason": None,
|
|
75
|
+
"response": None,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Extract username from log
|
|
79
|
+
username_match = re.search(r"Username: (\S+)", log_content)
|
|
80
|
+
if username_match:
|
|
81
|
+
result["username"] = username_match.group(1)
|
|
82
|
+
|
|
83
|
+
if json_match:
|
|
84
|
+
try:
|
|
85
|
+
json_data = json.loads(json_match.group(1))
|
|
86
|
+
|
|
87
|
+
if json_data.get("error"):
|
|
88
|
+
result["status"] = STATUS_ERROR
|
|
89
|
+
result["error"] = json_data["error"]
|
|
90
|
+
result["summary"] = f"Error: {json_data['error'][:50]}"
|
|
91
|
+
return result
|
|
92
|
+
|
|
93
|
+
result["login_success"] = json_data.get("success", False)
|
|
94
|
+
result["http_code"] = json_data.get("http_code")
|
|
95
|
+
result["reason"] = json_data.get("reason")
|
|
96
|
+
result["response"] = json_data.get("response", "")[:100]
|
|
97
|
+
|
|
98
|
+
except json.JSONDecodeError:
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
# Fallback: parse from log text
|
|
102
|
+
if "[+] LOGIN SUCCESS" in log_content:
|
|
103
|
+
result["login_success"] = True
|
|
104
|
+
result["status"] = STATUS_DONE
|
|
105
|
+
elif "[-] LOGIN FAILED" in log_content:
|
|
106
|
+
result["login_success"] = False
|
|
107
|
+
result["status"] = STATUS_NO_RESULTS
|
|
108
|
+
|
|
109
|
+
# Extract HTTP code if not already found
|
|
110
|
+
if not result["http_code"]:
|
|
111
|
+
http_match = re.search(r"HTTP Code: (\d+)", log_content)
|
|
112
|
+
if http_match:
|
|
113
|
+
result["http_code"] = int(http_match.group(1))
|
|
114
|
+
|
|
115
|
+
# Extract reason if not already found
|
|
116
|
+
if not result["reason"]:
|
|
117
|
+
reason_match = re.search(r"Reason: (.+?)(?:\n|$)", log_content)
|
|
118
|
+
if reason_match:
|
|
119
|
+
result["reason"] = reason_match.group(1).strip()
|
|
120
|
+
|
|
121
|
+
# Build summary
|
|
122
|
+
if result["login_success"]:
|
|
123
|
+
result["status"] = STATUS_DONE
|
|
124
|
+
result["summary"] = f"Login successful: {result['username']}"
|
|
125
|
+
# Store validated credential
|
|
126
|
+
if credentials_manager and result["username"]:
|
|
127
|
+
target = job.get("target", "")
|
|
128
|
+
# Extract password from args
|
|
129
|
+
password = self._extract_password_from_args(job.get("args", []))
|
|
130
|
+
if password:
|
|
131
|
+
try:
|
|
132
|
+
credentials_manager.add_credential(
|
|
133
|
+
engagement_id=engagement_id,
|
|
134
|
+
host_id=None,
|
|
135
|
+
username=result["username"],
|
|
136
|
+
password=password,
|
|
137
|
+
service="web",
|
|
138
|
+
credential_type="password",
|
|
139
|
+
tool="web_login_test",
|
|
140
|
+
status="validated",
|
|
141
|
+
notes=f"Validated against {target}",
|
|
142
|
+
)
|
|
143
|
+
result["credentials_validated"] = 1
|
|
144
|
+
logger.info(
|
|
145
|
+
f"Web login test: Validated credential {result['username']}"
|
|
146
|
+
)
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.debug(f"Error storing validated credential: {e}")
|
|
149
|
+
else:
|
|
150
|
+
result["status"] = STATUS_NO_RESULTS
|
|
151
|
+
# Build informative failure summary
|
|
152
|
+
if result["reason"]:
|
|
153
|
+
# Truncate long reasons
|
|
154
|
+
reason = result["reason"]
|
|
155
|
+
if len(reason) > 50:
|
|
156
|
+
reason = reason[:47] + "..."
|
|
157
|
+
result["summary"] = f"Login failed: {reason}"
|
|
158
|
+
elif result["http_code"]:
|
|
159
|
+
result["summary"] = f"Login failed (HTTP {result['http_code']})"
|
|
160
|
+
else:
|
|
161
|
+
result["summary"] = "Login failed"
|
|
162
|
+
|
|
163
|
+
return result
|
|
164
|
+
|
|
165
|
+
except Exception as e:
|
|
166
|
+
logger.error(f"Error parsing web_login_test job: {e}")
|
|
167
|
+
return {"tool": self.tool_name, "status": STATUS_ERROR, "error": str(e)}
|
|
168
|
+
|
|
169
|
+
def _extract_password_from_args(self, args: list) -> Optional[str]:
|
|
170
|
+
"""Extract password from job args."""
|
|
171
|
+
for i, arg in enumerate(args):
|
|
172
|
+
if arg == "--password" and i + 1 < len(args):
|
|
173
|
+
return args[i + 1]
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
def display_done(
|
|
177
|
+
self,
|
|
178
|
+
job: Dict[str, Any],
|
|
179
|
+
log_path: str,
|
|
180
|
+
show_all: bool = False,
|
|
181
|
+
show_passwords: bool = False,
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Display successful login test results."""
|
|
184
|
+
result = self._parse_log(log_path)
|
|
185
|
+
|
|
186
|
+
click.echo()
|
|
187
|
+
click.echo(click.style("=" * 60, fg="green"))
|
|
188
|
+
click.echo(click.style("WEB LOGIN TEST - SUCCESS", bold=True, fg="green"))
|
|
189
|
+
click.echo(click.style("=" * 60, fg="green"))
|
|
190
|
+
click.echo()
|
|
191
|
+
|
|
192
|
+
target = job.get("target", "unknown")
|
|
193
|
+
click.echo(f" Target: {target}")
|
|
194
|
+
|
|
195
|
+
username = result.get("username", "unknown")
|
|
196
|
+
if show_passwords:
|
|
197
|
+
password = self._extract_password_from_args(job.get("args", []))
|
|
198
|
+
click.echo(click.style(f" Credential: {username}:{password}", fg="green"))
|
|
199
|
+
else:
|
|
200
|
+
click.echo(click.style(f" Credential: {username}:***", fg="green"))
|
|
201
|
+
|
|
202
|
+
if result.get("http_code"):
|
|
203
|
+
click.echo(f" HTTP Code: {result['http_code']}")
|
|
204
|
+
|
|
205
|
+
if result.get("reason"):
|
|
206
|
+
click.echo(f" Reason: {result['reason']}")
|
|
207
|
+
|
|
208
|
+
click.echo()
|
|
209
|
+
click.echo(click.style("=" * 60, fg="green"))
|
|
210
|
+
click.echo()
|
|
211
|
+
|
|
212
|
+
def display_no_results(
|
|
213
|
+
self,
|
|
214
|
+
job: Dict[str, Any],
|
|
215
|
+
log_path: str,
|
|
216
|
+
) -> None:
|
|
217
|
+
"""Display failed login test results."""
|
|
218
|
+
result = self._parse_log(log_path)
|
|
219
|
+
|
|
220
|
+
click.echo()
|
|
221
|
+
click.echo(click.style("=" * 60, fg="yellow"))
|
|
222
|
+
click.echo(click.style("WEB LOGIN TEST - FAILED", bold=True, fg="yellow"))
|
|
223
|
+
click.echo(click.style("=" * 60, fg="yellow"))
|
|
224
|
+
click.echo()
|
|
225
|
+
|
|
226
|
+
target = job.get("target", "unknown")
|
|
227
|
+
click.echo(f" Target: {target}")
|
|
228
|
+
|
|
229
|
+
username = result.get("username", "unknown")
|
|
230
|
+
click.echo(f" Credential: {username}:***")
|
|
231
|
+
|
|
232
|
+
if result.get("http_code"):
|
|
233
|
+
click.echo(f" HTTP Code: {result['http_code']}")
|
|
234
|
+
|
|
235
|
+
if result.get("reason"):
|
|
236
|
+
click.echo(f" Reason: {result['reason']}")
|
|
237
|
+
|
|
238
|
+
click.echo()
|
|
239
|
+
click.echo(click.style(" Result: Invalid credentials", fg="yellow"))
|
|
240
|
+
click.echo()
|
|
241
|
+
click.echo(click.style("=" * 60, fg="yellow"))
|
|
242
|
+
click.echo()
|
|
243
|
+
|
|
244
|
+
def display_error(
|
|
245
|
+
self,
|
|
246
|
+
job: Dict[str, Any],
|
|
247
|
+
log_path: str,
|
|
248
|
+
log_content: Optional[str] = None,
|
|
249
|
+
) -> None:
|
|
250
|
+
"""Display error status."""
|
|
251
|
+
click.echo()
|
|
252
|
+
click.echo(click.style("=" * 60, fg="red"))
|
|
253
|
+
click.echo(click.style("[ERROR] WEB LOGIN TEST FAILED", bold=True, fg="red"))
|
|
254
|
+
click.echo(click.style("=" * 60, fg="red"))
|
|
255
|
+
click.echo()
|
|
256
|
+
|
|
257
|
+
target = job.get("target", "unknown")
|
|
258
|
+
click.echo(f" Target: {target}")
|
|
259
|
+
|
|
260
|
+
# Try to get error details
|
|
261
|
+
if log_path and os.path.exists(log_path):
|
|
262
|
+
try:
|
|
263
|
+
with open(log_path, "r", encoding="utf-8", errors="replace") as f:
|
|
264
|
+
content = f.read()
|
|
265
|
+
error_match = re.search(r"ERROR: (.+?)(?:\n|$)", content)
|
|
266
|
+
if error_match:
|
|
267
|
+
click.echo(f" Error: {error_match.group(1)}")
|
|
268
|
+
except Exception:
|
|
269
|
+
pass
|
|
270
|
+
|
|
271
|
+
click.echo()
|
|
272
|
+
click.echo(" Check raw logs for details (press 'r').")
|
|
273
|
+
click.echo()
|
|
274
|
+
click.echo(click.style("=" * 60, fg="red"))
|
|
275
|
+
click.echo()
|
|
276
|
+
|
|
277
|
+
def _parse_log(self, log_path: str) -> Dict[str, Any]:
|
|
278
|
+
"""Parse log file and return result dict."""
|
|
279
|
+
result = {
|
|
280
|
+
"username": None,
|
|
281
|
+
"http_code": None,
|
|
282
|
+
"reason": None,
|
|
283
|
+
"response": None,
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if not log_path or not os.path.exists(log_path):
|
|
287
|
+
return result
|
|
288
|
+
|
|
289
|
+
try:
|
|
290
|
+
with open(log_path, "r", encoding="utf-8", errors="replace") as f:
|
|
291
|
+
log_content = f.read()
|
|
292
|
+
|
|
293
|
+
# Extract username
|
|
294
|
+
username_match = re.search(r"Username: (\S+)", log_content)
|
|
295
|
+
if username_match:
|
|
296
|
+
result["username"] = username_match.group(1)
|
|
297
|
+
|
|
298
|
+
# Try JSON result
|
|
299
|
+
json_match = re.search(
|
|
300
|
+
r"=== JSON_RESULT ===\s*(.*?)\s*=== END_JSON_RESULT ===",
|
|
301
|
+
log_content,
|
|
302
|
+
re.DOTALL,
|
|
303
|
+
)
|
|
304
|
+
if json_match:
|
|
305
|
+
try:
|
|
306
|
+
json_data = json.loads(json_match.group(1))
|
|
307
|
+
result["http_code"] = json_data.get("http_code")
|
|
308
|
+
result["reason"] = json_data.get("reason")
|
|
309
|
+
result["response"] = json_data.get("response", "")[:100]
|
|
310
|
+
except json.JSONDecodeError:
|
|
311
|
+
pass
|
|
312
|
+
|
|
313
|
+
# Fallback parsing
|
|
314
|
+
if not result["http_code"]:
|
|
315
|
+
http_match = re.search(r"HTTP Code: (\d+)", log_content)
|
|
316
|
+
if http_match:
|
|
317
|
+
result["http_code"] = int(http_match.group(1))
|
|
318
|
+
|
|
319
|
+
if not result["reason"]:
|
|
320
|
+
reason_match = re.search(r"Reason: (.+?)(?:\n|$)", log_content)
|
|
321
|
+
if reason_match:
|
|
322
|
+
result["reason"] = reason_match.group(1).strip()
|
|
323
|
+
|
|
324
|
+
except Exception:
|
|
325
|
+
pass
|
|
326
|
+
|
|
327
|
+
return result
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
WHOIS handler.
|
|
4
|
+
|
|
5
|
+
Consolidates parsing and display logic for WHOIS domain lookup jobs.
|
|
6
|
+
"""
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
from typing import Any, Dict, Optional
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
|
|
13
|
+
from souleyez.engine.job_status import STATUS_DONE, STATUS_NO_RESULTS
|
|
14
|
+
from souleyez.handlers.base import BaseToolHandler
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class WhoisHandler(BaseToolHandler):
|
|
20
|
+
"""Handler for WHOIS domain lookup jobs."""
|
|
21
|
+
|
|
22
|
+
tool_name = "whois"
|
|
23
|
+
display_name = "WHOIS"
|
|
24
|
+
|
|
25
|
+
# All handlers enabled
|
|
26
|
+
has_error_handler = True
|
|
27
|
+
has_warning_handler = True
|
|
28
|
+
has_no_results_handler = True
|
|
29
|
+
has_done_handler = True
|
|
30
|
+
|
|
31
|
+
def parse_job(
|
|
32
|
+
self,
|
|
33
|
+
engagement_id: int,
|
|
34
|
+
log_path: str,
|
|
35
|
+
job: Dict[str, Any],
|
|
36
|
+
host_manager: Optional[Any] = None,
|
|
37
|
+
findings_manager: Optional[Any] = None,
|
|
38
|
+
credentials_manager: Optional[Any] = None,
|
|
39
|
+
) -> Dict[str, Any]:
|
|
40
|
+
"""
|
|
41
|
+
Parse WHOIS job results.
|
|
42
|
+
|
|
43
|
+
Extracts domain registration information and stores as OSINT data.
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
from souleyez.parsers.whois_parser import (
|
|
47
|
+
parse_whois_output,
|
|
48
|
+
map_to_osint_data,
|
|
49
|
+
extract_emails,
|
|
50
|
+
)
|
|
51
|
+
from souleyez.storage.osint import OsintManager
|
|
52
|
+
|
|
53
|
+
# Read the log file
|
|
54
|
+
with open(log_path, "r", encoding="utf-8", errors="replace") as f:
|
|
55
|
+
log_content = f.read()
|
|
56
|
+
|
|
57
|
+
# Parse WHOIS output
|
|
58
|
+
target = job.get("target", "")
|
|
59
|
+
parsed = parse_whois_output(log_content, target)
|
|
60
|
+
|
|
61
|
+
# Store OSINT data
|
|
62
|
+
om = OsintManager()
|
|
63
|
+
osint_record = map_to_osint_data(parsed, engagement_id)
|
|
64
|
+
om.add_osint_data(
|
|
65
|
+
engagement_id,
|
|
66
|
+
osint_record["data_type"],
|
|
67
|
+
osint_record["target"],
|
|
68
|
+
source=osint_record["source"],
|
|
69
|
+
target=target,
|
|
70
|
+
summary=osint_record["summary"],
|
|
71
|
+
content=osint_record["content"],
|
|
72
|
+
metadata=osint_record["metadata"],
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Extract emails and add separately for better querying
|
|
76
|
+
emails = extract_emails(parsed)
|
|
77
|
+
emails_added = 0
|
|
78
|
+
if emails:
|
|
79
|
+
emails_added = om.bulk_add_osint_data(
|
|
80
|
+
engagement_id, "email", emails, "whois", target
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
"tool": "whois",
|
|
85
|
+
"status": (
|
|
86
|
+
STATUS_DONE
|
|
87
|
+
if (parsed.get("registrar") or parsed.get("nameservers"))
|
|
88
|
+
else STATUS_NO_RESULTS
|
|
89
|
+
),
|
|
90
|
+
"domain": parsed.get("domain", target),
|
|
91
|
+
"registrar": parsed.get("registrar"),
|
|
92
|
+
"created": parsed.get("dates", {}).get("created"),
|
|
93
|
+
"expires": parsed.get("dates", {}).get("expires"),
|
|
94
|
+
"emails_found": len(emails),
|
|
95
|
+
"nameservers": len(parsed.get("nameservers", [])),
|
|
96
|
+
"osint_records_added": 1,
|
|
97
|
+
"emails_added": emails_added,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
except Exception as e:
|
|
101
|
+
logger.error(f"Error parsing whois job: {e}")
|
|
102
|
+
return {"error": str(e)}
|
|
103
|
+
|
|
104
|
+
def display_done(
|
|
105
|
+
self,
|
|
106
|
+
job: Dict[str, Any],
|
|
107
|
+
log_path: str,
|
|
108
|
+
show_all: bool = False,
|
|
109
|
+
show_passwords: bool = False,
|
|
110
|
+
) -> None:
|
|
111
|
+
"""Display successful WHOIS results."""
|
|
112
|
+
try:
|
|
113
|
+
from souleyez.parsers.whois_parser import parse_whois_output
|
|
114
|
+
|
|
115
|
+
if not log_path or not os.path.exists(log_path):
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
with open(log_path, "r", encoding="utf-8", errors="replace") as f:
|
|
119
|
+
log_content = f.read()
|
|
120
|
+
|
|
121
|
+
parsed = parse_whois_output(log_content, job.get("target", ""))
|
|
122
|
+
|
|
123
|
+
click.echo(click.style("=" * 70, fg="cyan"))
|
|
124
|
+
click.echo(click.style("WHOIS DOMAIN INFORMATION", bold=True, fg="cyan"))
|
|
125
|
+
click.echo(click.style("=" * 70, fg="cyan"))
|
|
126
|
+
click.echo()
|
|
127
|
+
|
|
128
|
+
# Check if we have any data
|
|
129
|
+
has_data = (
|
|
130
|
+
parsed.get("domain")
|
|
131
|
+
or parsed.get("registrar")
|
|
132
|
+
or parsed.get("dates")
|
|
133
|
+
or parsed.get("nameservers")
|
|
134
|
+
or parsed.get("status")
|
|
135
|
+
or parsed.get("dnssec")
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if has_data:
|
|
139
|
+
# Domain and registrar
|
|
140
|
+
if parsed.get("domain"):
|
|
141
|
+
click.echo(click.style(f"Domain: {parsed['domain']}", bold=True))
|
|
142
|
+
elif job.get("target"):
|
|
143
|
+
click.echo(click.style(f"Target: {job.get('target')}", bold=True))
|
|
144
|
+
if parsed.get("registrar"):
|
|
145
|
+
click.echo(f"Registrar: {parsed['registrar']}")
|
|
146
|
+
click.echo()
|
|
147
|
+
|
|
148
|
+
# Registration dates
|
|
149
|
+
dates = parsed.get("dates", {})
|
|
150
|
+
if dates:
|
|
151
|
+
click.echo(click.style("Registration Information:", bold=True))
|
|
152
|
+
if dates.get("created"):
|
|
153
|
+
click.echo(f" Created: {dates['created']}")
|
|
154
|
+
if dates.get("updated"):
|
|
155
|
+
click.echo(f" Updated: {dates['updated']}")
|
|
156
|
+
if dates.get("expires"):
|
|
157
|
+
click.echo(f" Expires: {dates['expires']}")
|
|
158
|
+
click.echo()
|
|
159
|
+
|
|
160
|
+
# Nameservers
|
|
161
|
+
ns = parsed.get("nameservers", [])
|
|
162
|
+
if ns:
|
|
163
|
+
click.echo(click.style(f"Nameservers: {len(ns)}", bold=True))
|
|
164
|
+
for server in ns:
|
|
165
|
+
click.echo(f" - {server}")
|
|
166
|
+
click.echo()
|
|
167
|
+
|
|
168
|
+
# Status
|
|
169
|
+
status_list = parsed.get("status", [])
|
|
170
|
+
if status_list:
|
|
171
|
+
click.echo(click.style("Domain Status:", bold=True))
|
|
172
|
+
for status in status_list:
|
|
173
|
+
click.echo(f" - {status}")
|
|
174
|
+
click.echo()
|
|
175
|
+
|
|
176
|
+
# DNSSEC
|
|
177
|
+
if parsed.get("dnssec"):
|
|
178
|
+
click.echo(f"DNSSEC: {parsed['dnssec']}")
|
|
179
|
+
click.echo()
|
|
180
|
+
else:
|
|
181
|
+
self.display_no_results(job, log_path)
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
click.echo(click.style("=" * 70, fg="cyan"))
|
|
185
|
+
click.echo()
|
|
186
|
+
|
|
187
|
+
except Exception as e:
|
|
188
|
+
logger.debug(f"Error in display_done: {e}")
|
|
189
|
+
|
|
190
|
+
def display_warning(
|
|
191
|
+
self,
|
|
192
|
+
job: Dict[str, Any],
|
|
193
|
+
log_path: str,
|
|
194
|
+
log_content: Optional[str] = None,
|
|
195
|
+
) -> None:
|
|
196
|
+
"""Display warning status for WHOIS."""
|
|
197
|
+
click.echo(click.style("=" * 70, fg="yellow"))
|
|
198
|
+
click.echo(click.style("[WARNING] WHOIS LOOKUP", bold=True, fg="yellow"))
|
|
199
|
+
click.echo(click.style("=" * 70, fg="yellow"))
|
|
200
|
+
click.echo()
|
|
201
|
+
click.echo(" WHOIS lookup completed with warnings.")
|
|
202
|
+
click.echo(" Check raw logs for details (press 'r').")
|
|
203
|
+
click.echo()
|
|
204
|
+
click.echo(click.style("=" * 70, fg="yellow"))
|
|
205
|
+
click.echo()
|
|
206
|
+
|
|
207
|
+
def display_error(
|
|
208
|
+
self,
|
|
209
|
+
job: Dict[str, Any],
|
|
210
|
+
log_path: str,
|
|
211
|
+
log_content: Optional[str] = None,
|
|
212
|
+
) -> None:
|
|
213
|
+
"""Display error status for WHOIS."""
|
|
214
|
+
# Read log if not provided
|
|
215
|
+
if log_content is None and log_path and os.path.exists(log_path):
|
|
216
|
+
try:
|
|
217
|
+
with open(log_path, "r", encoding="utf-8", errors="replace") as f:
|
|
218
|
+
log_content = f.read()
|
|
219
|
+
except Exception:
|
|
220
|
+
log_content = ""
|
|
221
|
+
|
|
222
|
+
click.echo(click.style("=" * 70, fg="red"))
|
|
223
|
+
click.echo(click.style("[ERROR] WHOIS LOOKUP FAILED", bold=True, fg="red"))
|
|
224
|
+
click.echo(click.style("=" * 70, fg="red"))
|
|
225
|
+
click.echo()
|
|
226
|
+
|
|
227
|
+
# Check for common whois errors
|
|
228
|
+
error_msg = None
|
|
229
|
+
if log_content:
|
|
230
|
+
if "No match for" in log_content or "NOT FOUND" in log_content.upper():
|
|
231
|
+
error_msg = "Domain not found in WHOIS database"
|
|
232
|
+
elif "timed out" in log_content.lower() or "timeout" in log_content.lower():
|
|
233
|
+
error_msg = "WHOIS query timed out - server may be slow"
|
|
234
|
+
elif "Connection refused" in log_content:
|
|
235
|
+
error_msg = "Connection refused - WHOIS server may be down"
|
|
236
|
+
elif (
|
|
237
|
+
"rate limit" in log_content.lower() or "too many" in log_content.lower()
|
|
238
|
+
):
|
|
239
|
+
error_msg = "Rate limited - too many WHOIS queries"
|
|
240
|
+
|
|
241
|
+
if error_msg:
|
|
242
|
+
click.echo(f" {error_msg}")
|
|
243
|
+
else:
|
|
244
|
+
click.echo(" Lookup failed - see raw logs for details (press 'r')")
|
|
245
|
+
|
|
246
|
+
click.echo()
|
|
247
|
+
click.echo(click.style("=" * 70, fg="red"))
|
|
248
|
+
click.echo()
|
|
249
|
+
|
|
250
|
+
def display_no_results(
|
|
251
|
+
self,
|
|
252
|
+
job: Dict[str, Any],
|
|
253
|
+
log_path: str,
|
|
254
|
+
) -> None:
|
|
255
|
+
"""Display no_results status for WHOIS."""
|
|
256
|
+
click.echo(click.style("=" * 70, fg="cyan"))
|
|
257
|
+
click.echo(click.style("WHOIS DOMAIN INFORMATION", bold=True, fg="cyan"))
|
|
258
|
+
click.echo(click.style("=" * 70, fg="cyan"))
|
|
259
|
+
click.echo()
|
|
260
|
+
|
|
261
|
+
if job.get("target"):
|
|
262
|
+
click.echo(click.style(f"Target: {job.get('target')}", bold=True))
|
|
263
|
+
click.echo()
|
|
264
|
+
|
|
265
|
+
click.echo(
|
|
266
|
+
click.style("Result: No WHOIS information found", fg="yellow", bold=True)
|
|
267
|
+
)
|
|
268
|
+
click.echo()
|
|
269
|
+
click.echo(" The WHOIS lookup did not return any information.")
|
|
270
|
+
click.echo()
|
|
271
|
+
click.echo(click.style("Tips:", dim=True))
|
|
272
|
+
click.echo(" - Verify the domain name is correct")
|
|
273
|
+
click.echo(" - Some domains have private WHOIS")
|
|
274
|
+
click.echo(" - Try a different WHOIS server")
|
|
275
|
+
click.echo()
|
|
276
|
+
click.echo(click.style("=" * 70, fg="cyan"))
|
|
277
|
+
click.echo()
|