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.
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 +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
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
souleyez.plugins.web_login_test - Web login credential testing
|
|
4
|
+
|
|
5
|
+
Tests cracked credentials against web login endpoints.
|
|
6
|
+
Supports both JSON REST APIs and traditional HTML form logins.
|
|
7
|
+
"""
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
import ssl
|
|
11
|
+
import time
|
|
12
|
+
import urllib.error
|
|
13
|
+
import urllib.parse
|
|
14
|
+
import urllib.request
|
|
15
|
+
from typing import Any, Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
from .plugin_base import PluginBase
|
|
18
|
+
|
|
19
|
+
HELP = {
|
|
20
|
+
"name": "Web Login Test - Credential Validation",
|
|
21
|
+
"description": (
|
|
22
|
+
"Tests credentials against web login endpoints to validate "
|
|
23
|
+
"that cracked passwords actually work.\n\n"
|
|
24
|
+
"Supports:\n"
|
|
25
|
+
"- JSON REST APIs (Content-Type: application/json)\n"
|
|
26
|
+
"- HTML form submissions\n"
|
|
27
|
+
"- Custom field names via --username-field and --password-field\n"
|
|
28
|
+
),
|
|
29
|
+
"usage": 'souleyez jobs enqueue web_login_test <login_url> --args "--username <user> --password <pass>"',
|
|
30
|
+
"examples": [
|
|
31
|
+
'souleyez jobs enqueue web_login_test http://target/api/login --args "--username admin --password secret"',
|
|
32
|
+
'souleyez jobs enqueue web_login_test http://target/login.php --args "--username admin --password secret --form"',
|
|
33
|
+
],
|
|
34
|
+
"flags": [
|
|
35
|
+
["--username <user>", "Username to test"],
|
|
36
|
+
["--password <pass>", "Password to test"],
|
|
37
|
+
["--form", "Use form POST instead of JSON (default: JSON)"],
|
|
38
|
+
["--username-field <name>", "Custom username field name (default: email)"],
|
|
39
|
+
["--password-field <name>", "Custom password field name (default: password)"],
|
|
40
|
+
],
|
|
41
|
+
"presets": [
|
|
42
|
+
{
|
|
43
|
+
"name": "JSON API Login",
|
|
44
|
+
"args": ["--username", "<username>", "--password", "<password>"],
|
|
45
|
+
"desc": "Test JSON API login endpoint",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "Form Login",
|
|
49
|
+
"args": ["--username", "<username>", "--password", "<password>", "--form"],
|
|
50
|
+
"desc": "Test HTML form login",
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
"help_sections": [
|
|
54
|
+
{
|
|
55
|
+
"title": "How It Works",
|
|
56
|
+
"color": "cyan",
|
|
57
|
+
"content": [
|
|
58
|
+
(
|
|
59
|
+
"Overview",
|
|
60
|
+
[
|
|
61
|
+
"Sends POST request with credentials to login endpoint",
|
|
62
|
+
"Analyzes response for success/failure indicators",
|
|
63
|
+
"Supports JSON APIs and form-based authentication",
|
|
64
|
+
],
|
|
65
|
+
),
|
|
66
|
+
(
|
|
67
|
+
"Success Detection",
|
|
68
|
+
[
|
|
69
|
+
"Looks for: token, success, authenticated in response",
|
|
70
|
+
"HTTP 200/201 with positive indicators = success",
|
|
71
|
+
"HTTP 401/403 or error messages = failure",
|
|
72
|
+
],
|
|
73
|
+
),
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"title": "Usage & Examples",
|
|
78
|
+
"color": "green",
|
|
79
|
+
"content": [
|
|
80
|
+
(
|
|
81
|
+
"JSON API Login (default)",
|
|
82
|
+
[
|
|
83
|
+
"souleyez jobs enqueue web_login_test http://target/api/login \\",
|
|
84
|
+
' --args "--username admin --password secret"',
|
|
85
|
+
' → Sends JSON: {"email": "admin", "password": "secret"}',
|
|
86
|
+
],
|
|
87
|
+
),
|
|
88
|
+
(
|
|
89
|
+
"HTML Form Login",
|
|
90
|
+
[
|
|
91
|
+
"souleyez jobs enqueue web_login_test http://target/login.php \\",
|
|
92
|
+
' --args "--username admin --password secret --form"',
|
|
93
|
+
" → Sends form data: email=admin&password=secret",
|
|
94
|
+
],
|
|
95
|
+
),
|
|
96
|
+
(
|
|
97
|
+
"Custom Field Names",
|
|
98
|
+
[
|
|
99
|
+
"souleyez jobs enqueue web_login_test http://target/login \\",
|
|
100
|
+
' --args "--username admin --password secret --username-field user --password-field pass"',
|
|
101
|
+
" → Uses custom field names instead of email/password",
|
|
102
|
+
],
|
|
103
|
+
),
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"title": "Automatic Chaining",
|
|
108
|
+
"color": "yellow",
|
|
109
|
+
"content": [
|
|
110
|
+
(
|
|
111
|
+
"When This Tool Runs Automatically",
|
|
112
|
+
[
|
|
113
|
+
"After hashcat cracks passwords from SQLMap dumps",
|
|
114
|
+
"Validates cracked credentials against the original login endpoint",
|
|
115
|
+
"Auto-detects JSON vs form format from URL patterns",
|
|
116
|
+
],
|
|
117
|
+
),
|
|
118
|
+
(
|
|
119
|
+
"Chain Flow",
|
|
120
|
+
[
|
|
121
|
+
"SQLMap extracts hashed passwords from database",
|
|
122
|
+
"Hashcat cracks the hashes to plaintext",
|
|
123
|
+
"Web Login Test validates: do these credentials work?",
|
|
124
|
+
"Validated credentials are stored for reporting",
|
|
125
|
+
],
|
|
126
|
+
),
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class WebLoginTestPlugin(PluginBase):
|
|
134
|
+
"""Plugin for testing web login credentials."""
|
|
135
|
+
|
|
136
|
+
name = "Web Login Test"
|
|
137
|
+
tool = "web_login_test"
|
|
138
|
+
category = "credential_access"
|
|
139
|
+
HELP = HELP
|
|
140
|
+
|
|
141
|
+
# Success indicators in response body
|
|
142
|
+
SUCCESS_INDICATORS = [
|
|
143
|
+
"token",
|
|
144
|
+
"access_token",
|
|
145
|
+
"jwt",
|
|
146
|
+
"session",
|
|
147
|
+
"authenticated",
|
|
148
|
+
"success",
|
|
149
|
+
"welcome",
|
|
150
|
+
"dashboard",
|
|
151
|
+
"logged in",
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
# Failure indicators in response body
|
|
155
|
+
FAILURE_INDICATORS = [
|
|
156
|
+
"invalid",
|
|
157
|
+
"incorrect",
|
|
158
|
+
"failed",
|
|
159
|
+
"error",
|
|
160
|
+
"unauthorized",
|
|
161
|
+
"denied",
|
|
162
|
+
"wrong password",
|
|
163
|
+
"bad credentials",
|
|
164
|
+
"authentication failed",
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
def build_command(
|
|
168
|
+
self,
|
|
169
|
+
target: str,
|
|
170
|
+
args: List[str] = None,
|
|
171
|
+
label: str = "",
|
|
172
|
+
log_path: str = None,
|
|
173
|
+
) -> Optional[Dict[str, Any]]:
|
|
174
|
+
"""
|
|
175
|
+
Web login test runs in Python, not via external command.
|
|
176
|
+
Return None to use run() method instead.
|
|
177
|
+
"""
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
def run(
|
|
181
|
+
self,
|
|
182
|
+
target: str,
|
|
183
|
+
args: List[str] = None,
|
|
184
|
+
label: str = "",
|
|
185
|
+
log_path: str = None,
|
|
186
|
+
) -> int:
|
|
187
|
+
"""Execute web login credential test."""
|
|
188
|
+
args = args or []
|
|
189
|
+
|
|
190
|
+
# Parse arguments
|
|
191
|
+
username = None
|
|
192
|
+
password = None
|
|
193
|
+
use_form = False
|
|
194
|
+
username_field = "email"
|
|
195
|
+
password_field = "password"
|
|
196
|
+
|
|
197
|
+
i = 0
|
|
198
|
+
while i < len(args):
|
|
199
|
+
arg = args[i]
|
|
200
|
+
if arg == "--username" and i + 1 < len(args):
|
|
201
|
+
username = args[i + 1]
|
|
202
|
+
i += 2
|
|
203
|
+
elif arg == "--password" and i + 1 < len(args):
|
|
204
|
+
password = args[i + 1]
|
|
205
|
+
i += 2
|
|
206
|
+
elif arg == "--form":
|
|
207
|
+
use_form = True
|
|
208
|
+
i += 1
|
|
209
|
+
elif arg == "--username-field" and i + 1 < len(args):
|
|
210
|
+
username_field = args[i + 1]
|
|
211
|
+
i += 2
|
|
212
|
+
elif arg == "--password-field" and i + 1 < len(args):
|
|
213
|
+
password_field = args[i + 1]
|
|
214
|
+
i += 2
|
|
215
|
+
else:
|
|
216
|
+
i += 1
|
|
217
|
+
|
|
218
|
+
if not username or not password:
|
|
219
|
+
self._write_log(
|
|
220
|
+
log_path,
|
|
221
|
+
target,
|
|
222
|
+
username,
|
|
223
|
+
error="Missing --username or --password argument",
|
|
224
|
+
)
|
|
225
|
+
return 1
|
|
226
|
+
|
|
227
|
+
# Ensure target has scheme
|
|
228
|
+
if not target.startswith(("http://", "https://")):
|
|
229
|
+
target = f"http://{target}"
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
result = self._test_login(
|
|
233
|
+
url=target,
|
|
234
|
+
username=username,
|
|
235
|
+
password=password,
|
|
236
|
+
use_form=use_form,
|
|
237
|
+
username_field=username_field,
|
|
238
|
+
password_field=password_field,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
self._write_log(log_path, target, username, result=result)
|
|
242
|
+
|
|
243
|
+
# Return 0 for success, 1 for failure
|
|
244
|
+
return 0 if result.get("success") else 1
|
|
245
|
+
|
|
246
|
+
except Exception as e:
|
|
247
|
+
self._write_log(log_path, target, username, error=str(e))
|
|
248
|
+
return 1
|
|
249
|
+
|
|
250
|
+
def _test_login(
|
|
251
|
+
self,
|
|
252
|
+
url: str,
|
|
253
|
+
username: str,
|
|
254
|
+
password: str,
|
|
255
|
+
use_form: bool = False,
|
|
256
|
+
username_field: str = "email",
|
|
257
|
+
password_field: str = "password",
|
|
258
|
+
timeout: int = 15,
|
|
259
|
+
) -> Dict[str, Any]:
|
|
260
|
+
"""
|
|
261
|
+
Test login credentials against a web endpoint.
|
|
262
|
+
|
|
263
|
+
Returns dict with:
|
|
264
|
+
- success: bool
|
|
265
|
+
- http_code: int
|
|
266
|
+
- response: str (truncated)
|
|
267
|
+
- reason: str (why success/failure was determined)
|
|
268
|
+
"""
|
|
269
|
+
result = {
|
|
270
|
+
"success": False,
|
|
271
|
+
"http_code": None,
|
|
272
|
+
"response": None,
|
|
273
|
+
"reason": None,
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
# Create SSL context that accepts self-signed certs
|
|
277
|
+
ctx = ssl.create_default_context()
|
|
278
|
+
ctx.check_hostname = False
|
|
279
|
+
ctx.verify_mode = ssl.CERT_NONE
|
|
280
|
+
|
|
281
|
+
# Build request data
|
|
282
|
+
if use_form:
|
|
283
|
+
# Form-encoded POST
|
|
284
|
+
data = urllib.parse.urlencode(
|
|
285
|
+
{username_field: username, password_field: password}
|
|
286
|
+
).encode("utf-8")
|
|
287
|
+
content_type = "application/x-www-form-urlencoded"
|
|
288
|
+
else:
|
|
289
|
+
# JSON POST
|
|
290
|
+
data = json.dumps(
|
|
291
|
+
{username_field: username, password_field: password}
|
|
292
|
+
).encode("utf-8")
|
|
293
|
+
content_type = "application/json"
|
|
294
|
+
|
|
295
|
+
# Create request
|
|
296
|
+
req = urllib.request.Request(
|
|
297
|
+
url,
|
|
298
|
+
data=data,
|
|
299
|
+
headers={
|
|
300
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
301
|
+
"Content-Type": content_type,
|
|
302
|
+
"Accept": "application/json, text/html, */*",
|
|
303
|
+
},
|
|
304
|
+
method="POST",
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
try:
|
|
308
|
+
response = urllib.request.urlopen(req, timeout=timeout, context=ctx)
|
|
309
|
+
result["http_code"] = response.getcode()
|
|
310
|
+
body = response.read().decode("utf-8", errors="replace")
|
|
311
|
+
result["response"] = body[:500] if body else ""
|
|
312
|
+
|
|
313
|
+
# Analyze response
|
|
314
|
+
body_lower = body.lower()
|
|
315
|
+
|
|
316
|
+
# Check for success indicators
|
|
317
|
+
for indicator in self.SUCCESS_INDICATORS:
|
|
318
|
+
if indicator in body_lower:
|
|
319
|
+
result["success"] = True
|
|
320
|
+
result["reason"] = f"Found success indicator: '{indicator}'"
|
|
321
|
+
return result
|
|
322
|
+
|
|
323
|
+
# HTTP 200/201 without explicit failure = possible success
|
|
324
|
+
if result["http_code"] in (200, 201):
|
|
325
|
+
# Check for failure indicators
|
|
326
|
+
for indicator in self.FAILURE_INDICATORS:
|
|
327
|
+
if indicator in body_lower:
|
|
328
|
+
result["success"] = False
|
|
329
|
+
result["reason"] = f"Found failure indicator: '{indicator}'"
|
|
330
|
+
return result
|
|
331
|
+
|
|
332
|
+
# No failure indicators, consider it possible success
|
|
333
|
+
result["success"] = True
|
|
334
|
+
result["reason"] = (
|
|
335
|
+
f"HTTP {result['http_code']} with no failure indicators"
|
|
336
|
+
)
|
|
337
|
+
return result
|
|
338
|
+
|
|
339
|
+
# Other status codes
|
|
340
|
+
result["reason"] = f"HTTP {result['http_code']} - unclear result"
|
|
341
|
+
return result
|
|
342
|
+
|
|
343
|
+
except urllib.error.HTTPError as e:
|
|
344
|
+
result["http_code"] = e.code
|
|
345
|
+
try:
|
|
346
|
+
body = e.read().decode("utf-8", errors="replace")
|
|
347
|
+
result["response"] = body[:500] if body else ""
|
|
348
|
+
except Exception:
|
|
349
|
+
result["response"] = ""
|
|
350
|
+
|
|
351
|
+
# 401/403 = auth failed
|
|
352
|
+
if e.code in (401, 403):
|
|
353
|
+
result["reason"] = f"HTTP {e.code} - Authentication failed"
|
|
354
|
+
else:
|
|
355
|
+
result["reason"] = f"HTTP {e.code} - Request failed"
|
|
356
|
+
return result
|
|
357
|
+
|
|
358
|
+
except urllib.error.URLError as e:
|
|
359
|
+
result["reason"] = f"Connection error: {e.reason}"
|
|
360
|
+
return result
|
|
361
|
+
|
|
362
|
+
except Exception as e:
|
|
363
|
+
result["reason"] = f"Error: {type(e).__name__}: {e}"
|
|
364
|
+
return result
|
|
365
|
+
|
|
366
|
+
def _write_log(
|
|
367
|
+
self,
|
|
368
|
+
log_path: str,
|
|
369
|
+
target: str,
|
|
370
|
+
username: str,
|
|
371
|
+
result: Dict[str, Any] = None,
|
|
372
|
+
error: str = None,
|
|
373
|
+
) -> None:
|
|
374
|
+
"""Write test results to log file."""
|
|
375
|
+
if not log_path:
|
|
376
|
+
return
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
with open(log_path, "w", encoding="utf-8", errors="replace") as fh:
|
|
380
|
+
fh.write("=== Plugin: Web Login Test ===\n")
|
|
381
|
+
fh.write(f"Target: {target}\n")
|
|
382
|
+
fh.write(f"Username: {username}\n")
|
|
383
|
+
fh.write(
|
|
384
|
+
f"Started: {time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime())}\n"
|
|
385
|
+
)
|
|
386
|
+
fh.write("=" * 60 + "\n\n")
|
|
387
|
+
|
|
388
|
+
if error:
|
|
389
|
+
fh.write(f"ERROR: {error}\n")
|
|
390
|
+
fh.write("\n=== JSON_RESULT ===\n")
|
|
391
|
+
fh.write(json.dumps({"error": error}, indent=2))
|
|
392
|
+
fh.write("\n=== END_JSON_RESULT ===\n")
|
|
393
|
+
return
|
|
394
|
+
|
|
395
|
+
if result:
|
|
396
|
+
if result.get("success"):
|
|
397
|
+
fh.write("[+] LOGIN SUCCESS\n")
|
|
398
|
+
else:
|
|
399
|
+
fh.write("[-] LOGIN FAILED\n")
|
|
400
|
+
|
|
401
|
+
fh.write(f"\nHTTP Code: {result.get('http_code', 'N/A')}\n")
|
|
402
|
+
fh.write(f"Reason: {result.get('reason', 'Unknown')}\n")
|
|
403
|
+
|
|
404
|
+
if result.get("response"):
|
|
405
|
+
fh.write(
|
|
406
|
+
f"\nResponse (truncated):\n{result['response'][:200]}\n"
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
fh.write("\n=== JSON_RESULT ===\n")
|
|
410
|
+
fh.write(json.dumps(result, indent=2))
|
|
411
|
+
fh.write("\n=== END_JSON_RESULT ===\n")
|
|
412
|
+
|
|
413
|
+
except Exception:
|
|
414
|
+
pass
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
plugin = WebLoginTestPlugin()
|
souleyez/plugins/whois.py
CHANGED
|
@@ -37,56 +37,80 @@ HELP = {
|
|
|
37
37
|
["-p <port>", "Connect to specific port (default: 43)"],
|
|
38
38
|
],
|
|
39
39
|
"presets": [
|
|
40
|
-
{
|
|
40
|
+
{
|
|
41
|
+
"name": "Standard Lookup",
|
|
42
|
+
"args": [],
|
|
43
|
+
"desc": "Basic WHOIS query for domain information",
|
|
44
|
+
},
|
|
41
45
|
],
|
|
42
46
|
"help_sections": [
|
|
43
47
|
{
|
|
44
48
|
"title": "What is WHOIS?",
|
|
45
49
|
"color": "cyan",
|
|
46
50
|
"content": [
|
|
47
|
-
{
|
|
48
|
-
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
{
|
|
52
|
+
"title": "Overview",
|
|
53
|
+
"desc": "WHOIS queries domain registration databases to retrieve registrant information, registrar details, creation/expiration dates, nameservers, and technical contacts.",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"title": "Use Cases",
|
|
57
|
+
"desc": "Essential for reconnaissance to understand domain ownership and gather contact information.",
|
|
58
|
+
"tips": [
|
|
59
|
+
"Identify domain owner and organization",
|
|
60
|
+
"Find registration and expiration dates",
|
|
61
|
+
"Discover nameservers and DNS configuration",
|
|
62
|
+
"Gather email addresses and phone numbers for social engineering",
|
|
63
|
+
"Identify related domains by registrant",
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
],
|
|
56
67
|
},
|
|
57
68
|
{
|
|
58
69
|
"title": "How to Use",
|
|
59
70
|
"color": "green",
|
|
60
71
|
"content": [
|
|
61
|
-
{
|
|
62
|
-
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
{
|
|
73
|
+
"title": "Basic Workflow",
|
|
74
|
+
"desc": "1. Enter target domain name\n 2. Review registration information\n 3. Note nameservers for DNS enumeration\n 4. Save contact information for later use",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"title": "What to Look For",
|
|
78
|
+
"desc": "Key information in WHOIS results",
|
|
79
|
+
"tips": [
|
|
80
|
+
"Registrant name and organization",
|
|
81
|
+
"Creation/expiration dates (helps identify abandoned domains)",
|
|
82
|
+
"Nameserver configuration",
|
|
83
|
+
"Technical/admin contact emails",
|
|
84
|
+
"Registrar information",
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
],
|
|
70
88
|
},
|
|
71
89
|
{
|
|
72
90
|
"title": "Tips & Best Practices",
|
|
73
91
|
"color": "yellow",
|
|
74
92
|
"content": [
|
|
75
|
-
(
|
|
76
|
-
"
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
(
|
|
94
|
+
"Best Practices:",
|
|
95
|
+
[
|
|
96
|
+
"Query early in reconnaissance phase",
|
|
97
|
+
"Cross-reference with theHarvester results",
|
|
98
|
+
"Note privacy-protected domains (limited info)",
|
|
99
|
+
"Check related TLDs (.com, .net, .org, etc.)",
|
|
100
|
+
"Document contact information for reporting",
|
|
101
|
+
],
|
|
102
|
+
),
|
|
103
|
+
(
|
|
104
|
+
"Common Issues:",
|
|
105
|
+
[
|
|
106
|
+
"Privacy protection: Many domains hide owner details",
|
|
107
|
+
"Rate limiting: WHOIS servers may throttle queries",
|
|
108
|
+
"Different formats: Each TLD registry has different output format",
|
|
109
|
+
],
|
|
110
|
+
),
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
],
|
|
90
114
|
}
|
|
91
115
|
|
|
92
116
|
|
|
@@ -96,35 +120,35 @@ class WhoisPlugin(PluginBase):
|
|
|
96
120
|
category = "reconnaissance"
|
|
97
121
|
HELP = HELP
|
|
98
122
|
|
|
99
|
-
|
|
100
|
-
|
|
123
|
+
def build_command(
|
|
124
|
+
self, target: str, args: List[str] = None, label: str = "", log_path: str = None
|
|
125
|
+
):
|
|
101
126
|
"""Build command for background execution with PID tracking."""
|
|
102
127
|
if not target:
|
|
103
128
|
if log_path:
|
|
104
|
-
with open(log_path,
|
|
129
|
+
with open(log_path, "w") as f:
|
|
105
130
|
f.write("ERROR: Target domain is required\n")
|
|
106
131
|
return None
|
|
107
|
-
|
|
132
|
+
|
|
108
133
|
# Validate target
|
|
109
134
|
try:
|
|
110
135
|
target = validate_target(target)
|
|
111
136
|
except ValidationError as e:
|
|
112
137
|
if log_path:
|
|
113
|
-
with open(log_path,
|
|
138
|
+
with open(log_path, "w") as f:
|
|
114
139
|
f.write(f"ERROR: Invalid target: {e}\n")
|
|
115
140
|
return None
|
|
116
|
-
|
|
141
|
+
|
|
117
142
|
args = args or []
|
|
118
|
-
|
|
143
|
+
|
|
119
144
|
# whois syntax: whois target [args]
|
|
120
145
|
cmd = ["whois", target] + args
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
'cmd': cmd,
|
|
124
|
-
'timeout': 300
|
|
125
|
-
}
|
|
126
146
|
|
|
127
|
-
|
|
147
|
+
return {"cmd": cmd, "timeout": 300}
|
|
148
|
+
|
|
149
|
+
def run(
|
|
150
|
+
self, target: str, args: List[str] = None, label: str = "", log_path: str = None
|
|
151
|
+
) -> int:
|
|
128
152
|
"""
|
|
129
153
|
Execute WHOIS lookup and write output to log_path.
|
|
130
154
|
"""
|
|
@@ -136,7 +160,7 @@ class WhoisPlugin(PluginBase):
|
|
|
136
160
|
target = validate_target(target)
|
|
137
161
|
except ValidationError as e:
|
|
138
162
|
if log_path:
|
|
139
|
-
with open(log_path,
|
|
163
|
+
with open(log_path, "w") as f:
|
|
140
164
|
f.write(f"ERROR: Invalid target: {e}\n")
|
|
141
165
|
return 1
|
|
142
166
|
raise ValueError(f"Invalid target: {e}")
|
|
@@ -147,21 +171,16 @@ class WhoisPlugin(PluginBase):
|
|
|
147
171
|
cmd = ["whois", target] + args
|
|
148
172
|
|
|
149
173
|
if log_path:
|
|
150
|
-
with open(log_path,
|
|
174
|
+
with open(log_path, "w") as f:
|
|
151
175
|
f.write(f"# WHOIS Lookup for {target}\n")
|
|
152
176
|
f.write(f"# Command: {' '.join(cmd)}\n")
|
|
153
177
|
f.write(f"# Started: {time.strftime('%Y-%m-%d %H:%M:%S')}\n\n")
|
|
154
178
|
|
|
155
179
|
try:
|
|
156
|
-
result = subprocess.run(
|
|
157
|
-
cmd,
|
|
158
|
-
capture_output=True,
|
|
159
|
-
text=True,
|
|
160
|
-
timeout=60
|
|
161
|
-
)
|
|
180
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
162
181
|
|
|
163
182
|
if log_path:
|
|
164
|
-
with open(log_path,
|
|
183
|
+
with open(log_path, "a") as f:
|
|
165
184
|
f.write(result.stdout)
|
|
166
185
|
if result.stderr:
|
|
167
186
|
f.write(f"\n\n# Errors:\n{result.stderr}\n")
|
|
@@ -170,12 +189,12 @@ class WhoisPlugin(PluginBase):
|
|
|
170
189
|
|
|
171
190
|
except subprocess.TimeoutExpired:
|
|
172
191
|
if log_path:
|
|
173
|
-
with open(log_path,
|
|
192
|
+
with open(log_path, "a") as f:
|
|
174
193
|
f.write("\n\n# ERROR: Command timed out after 60 seconds\n")
|
|
175
194
|
return 124
|
|
176
195
|
except Exception as e:
|
|
177
196
|
if log_path:
|
|
178
|
-
with open(log_path,
|
|
197
|
+
with open(log_path, "a") as f:
|
|
179
198
|
f.write(f"\n\n# ERROR: {str(e)}\n")
|
|
180
199
|
return 1
|
|
181
200
|
|