souleyez 3.0.0__py3-none-any.whl → 3.0.9__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 -1
- souleyez/ai/__init__.py +7 -7
- souleyez/ai/action_mapper.py +3 -2
- souleyez/ai/chain_advisor.py +2 -1
- souleyez/ai/claude_provider.py +2 -2
- souleyez/ai/context_builder.py +4 -2
- souleyez/ai/executor.py +9 -6
- souleyez/ai/feedback_handler.py +4 -2
- souleyez/ai/llm_provider.py +2 -2
- souleyez/ai/ollama_provider.py +2 -2
- souleyez/ai/ollama_service.py +10 -26
- souleyez/ai/path_scorer.py +2 -1
- souleyez/ai/recommender.py +6 -4
- souleyez/ai/report_context.py +2 -2
- souleyez/ai/report_service.py +5 -5
- souleyez/ai/result_parser.py +3 -2
- souleyez/ai/safety.py +5 -2
- souleyez/auth/__init__.py +6 -6
- souleyez/auth/audit.py +2 -2
- souleyez/auth/engagement_access.py +5 -7
- souleyez/auth/permissions.py +1 -1
- souleyez/auth/session_manager.py +5 -5
- souleyez/auth/user_manager.py +4 -5
- souleyez/commands/audit.py +6 -5
- souleyez/commands/auth.py +6 -5
- souleyez/commands/deliverables.py +2 -3
- souleyez/commands/engagement.py +3 -3
- souleyez/commands/license.py +3 -2
- souleyez/commands/screenshots.py +5 -4
- souleyez/commands/user.py +10 -8
- souleyez/config.py +4 -2
- souleyez/core/credential_tester.py +4 -2
- souleyez/core/cve_mappings.py +2 -1
- souleyez/core/cve_matcher.py +2 -1
- souleyez/core/msf_auto_mapper.py +2 -0
- souleyez/core/msf_chain_engine.py +3 -1
- souleyez/core/msf_database.py +7 -13
- souleyez/core/msf_integration.py +2 -2
- souleyez/core/msf_rpc_client.py +3 -2
- souleyez/core/msf_rpc_manager.py +4 -4
- souleyez/core/msf_sync_manager.py +7 -7
- souleyez/core/network_utils.py +1 -1
- souleyez/core/parser_handler.py +2 -1
- souleyez/core/pending_chains.py +4 -3
- souleyez/core/templates.py +5 -2
- souleyez/core/tool_chaining.py +101 -70
- souleyez/core/version_utils.py +1 -0
- souleyez/core/vuln_correlation.py +3 -2
- souleyez/core/web_utils.py +2 -1
- souleyez/detection/__init__.py +1 -1
- souleyez/detection/attack_signatures.py +1 -1
- souleyez/detection/mitre_mappings.py +1 -2
- souleyez/detection/validator.py +5 -4
- souleyez/devtools.py +4 -2
- souleyez/docs/README.md +2 -2
- souleyez/engine/background.py +168 -7
- souleyez/engine/base.py +2 -1
- souleyez/engine/loader.py +4 -2
- souleyez/engine/log_sanitizer.py +1 -0
- souleyez/engine/manager.py +3 -1
- souleyez/engine/result_handler.py +50 -67
- souleyez/engine/worker_manager.py +6 -4
- souleyez/export/evidence_bundle.py +1 -0
- souleyez/handlers/base.py +1 -0
- souleyez/handlers/bash_handler.py +1 -0
- souleyez/handlers/bloodhound_handler.py +1 -0
- souleyez/handlers/certipy_handler.py +1 -0
- souleyez/handlers/crackmapexec_handler.py +2 -20
- souleyez/handlers/dnsrecon_handler.py +2 -1
- souleyez/handlers/enum4linux_handler.py +65 -37
- souleyez/handlers/evil_winrm_handler.py +1 -0
- souleyez/handlers/ffuf_handler.py +3 -1
- souleyez/handlers/gobuster_handler.py +7 -6
- souleyez/handlers/gpp_extract_handler.py +1 -0
- souleyez/handlers/hashcat_handler.py +1 -0
- souleyez/handlers/hydra_handler.py +5 -2
- souleyez/handlers/impacket_getuserspns_handler.py +1 -0
- souleyez/handlers/impacket_psexec_handler.py +1 -0
- souleyez/handlers/impacket_secretsdump_handler.py +1 -0
- souleyez/handlers/john_handler.py +1 -0
- souleyez/handlers/katana_handler.py +39 -2
- souleyez/handlers/kerbrute_handler.py +1 -0
- souleyez/handlers/ldapsearch_handler.py +90 -17
- souleyez/handlers/lfi_extract_handler.py +1 -0
- souleyez/handlers/msf_auxiliary_handler.py +1 -0
- souleyez/handlers/msf_exploit_handler.py +1 -0
- souleyez/handlers/nikto_handler.py +2 -1
- souleyez/handlers/nmap_handler.py +2 -1
- souleyez/handlers/nuclei_handler.py +2 -1
- souleyez/handlers/nxc_handler.py +3 -18
- souleyez/handlers/rdp_sec_check_handler.py +1 -0
- souleyez/handlers/registry.py +1 -0
- souleyez/handlers/responder_handler.py +1 -0
- souleyez/handlers/service_explorer_handler.py +2 -1
- souleyez/handlers/smbclient_handler.py +1 -0
- souleyez/handlers/smbmap_handler.py +3 -2
- souleyez/handlers/sqlmap_handler.py +6 -4
- souleyez/handlers/theharvester_handler.py +2 -1
- souleyez/handlers/web_login_test_handler.py +1 -0
- souleyez/handlers/whois_handler.py +3 -2
- souleyez/handlers/wpscan_handler.py +2 -1
- souleyez/history.py +4 -3
- souleyez/importers/msf_importer.py +5 -3
- souleyez/importers/smart_importer.py +6 -4
- souleyez/integrations/siem/__init__.py +6 -6
- souleyez/integrations/siem/base.py +1 -1
- souleyez/integrations/siem/elastic.py +3 -3
- souleyez/integrations/siem/factory.py +1 -2
- souleyez/integrations/siem/googlesecops.py +4 -4
- souleyez/integrations/siem/rule_mappings/wazuh_rules.py +1 -1
- souleyez/integrations/siem/sentinel.py +3 -3
- souleyez/integrations/siem/splunk.py +3 -3
- souleyez/integrations/siem/wazuh.py +4 -4
- souleyez/integrations/wazuh/__init__.py +1 -1
- souleyez/integrations/wazuh/client.py +3 -2
- souleyez/integrations/wazuh/config.py +3 -2
- souleyez/integrations/wazuh/host_mapper.py +3 -1
- souleyez/integrations/wazuh/sync.py +4 -1
- souleyez/intelligence/__init__.py +1 -1
- souleyez/intelligence/correlation_analyzer.py +6 -5
- souleyez/intelligence/exploit_knowledge.py +4 -4
- souleyez/intelligence/exploit_suggestions.py +4 -3
- souleyez/intelligence/gap_analyzer.py +5 -3
- souleyez/intelligence/gap_detector.py +2 -0
- souleyez/intelligence/sensitive_tables.py +1 -1
- souleyez/intelligence/service_parser.py +1 -0
- souleyez/intelligence/surface_analyzer.py +9 -9
- souleyez/intelligence/target_parser.py +1 -0
- souleyez/licensing/__init__.py +3 -3
- souleyez/main.py +25 -18
- souleyez/migrations/fix_job_counter.py +2 -1
- souleyez/parsers/bloodhound_parser.py +1 -0
- souleyez/parsers/crackmapexec_parser.py +2 -1
- souleyez/parsers/dalfox_parser.py +3 -2
- souleyez/parsers/dnsrecon_parser.py +2 -1
- souleyez/parsers/enum4linux_parser.py +2 -1
- souleyez/parsers/ffuf_parser.py +2 -1
- souleyez/parsers/gobuster_parser.py +2 -1
- souleyez/parsers/hashcat_parser.py +3 -2
- souleyez/parsers/http_fingerprint_parser.py +2 -1
- souleyez/parsers/hydra_parser.py +2 -1
- souleyez/parsers/impacket_parser.py +2 -1
- souleyez/parsers/john_parser.py +4 -3
- souleyez/parsers/katana_parser.py +134 -2
- souleyez/parsers/msf_parser.py +2 -1
- souleyez/parsers/nikto_parser.py +2 -1
- souleyez/parsers/nmap_parser.py +14 -3
- souleyez/parsers/nuclei_parser.py +3 -2
- souleyez/parsers/responder_parser.py +1 -0
- souleyez/parsers/searchsploit_parser.py +3 -2
- souleyez/parsers/service_explorer_parser.py +1 -0
- souleyez/parsers/smbmap_parser.py +2 -1
- souleyez/parsers/sqlmap_parser.py +36 -2
- souleyez/parsers/theharvester_parser.py +2 -1
- souleyez/parsers/whois_parser.py +2 -1
- souleyez/parsers/wpscan_parser.py +3 -2
- souleyez/plugins/afp.py +3 -1
- souleyez/plugins/afp_brute.py +3 -1
- souleyez/plugins/ard.py +3 -1
- souleyez/plugins/bloodhound.py +3 -2
- souleyez/plugins/certipy.py +1 -0
- souleyez/plugins/crackmapexec.py +11 -7
- souleyez/plugins/dalfox.py +5 -2
- souleyez/plugins/dns_hijack.py +3 -1
- souleyez/plugins/dnsrecon.py +3 -1
- souleyez/plugins/enum4linux.py +3 -1
- souleyez/plugins/evil_winrm.py +1 -0
- souleyez/plugins/ffuf.py +3 -1
- souleyez/plugins/firmware_extract.py +3 -2
- souleyez/plugins/gobuster.py +6 -3
- souleyez/plugins/gpp_extract.py +1 -0
- souleyez/plugins/hashcat.py +2 -1
- souleyez/plugins/http_fingerprint.py +57 -7
- souleyez/plugins/hydra.py +5 -3
- souleyez/plugins/impacket_common.py +40 -0
- souleyez/plugins/impacket_getnpusers.py +19 -2
- souleyez/plugins/impacket_getuserspns.py +158 -0
- souleyez/plugins/impacket_psexec.py +19 -2
- souleyez/plugins/impacket_secretsdump.py +19 -2
- souleyez/plugins/impacket_smbclient.py +19 -2
- souleyez/plugins/john.py +2 -1
- souleyez/plugins/katana.py +48 -6
- souleyez/plugins/kerbrute.py +1 -0
- souleyez/plugins/lfi_extract.py +1 -0
- souleyez/plugins/macos_ssh.py +3 -1
- souleyez/plugins/mdns.py +3 -1
- souleyez/plugins/msf_auxiliary.py +3 -2
- souleyez/plugins/msf_exploit.py +6 -5
- souleyez/plugins/nikto.py +5 -2
- souleyez/plugins/nmap.py +6 -4
- souleyez/plugins/nuclei.py +3 -1
- souleyez/plugins/nxc.py +1 -0
- souleyez/plugins/plugin_base.py +3 -2
- souleyez/plugins/plugin_template.py +3 -2
- souleyez/plugins/rdp_sec_check.py +1 -0
- souleyez/plugins/responder.py +2 -1
- souleyez/plugins/router_http_brute.py +3 -1
- souleyez/plugins/router_ssh_brute.py +3 -1
- souleyez/plugins/router_telnet_brute.py +3 -1
- souleyez/plugins/routersploit.py +5 -3
- souleyez/plugins/routersploit_exploit.py +5 -3
- souleyez/plugins/searchsploit.py +1 -0
- souleyez/plugins/service_explorer.py +2 -1
- souleyez/plugins/smbmap.py +3 -1
- souleyez/plugins/smbpasswd.py +1 -0
- souleyez/plugins/sqlmap.py +3 -1
- souleyez/plugins/theharvester.py +3 -1
- souleyez/plugins/tr069.py +3 -1
- souleyez/plugins/upnp.py +3 -1
- souleyez/plugins/upnp_abuse.py +4 -2
- souleyez/plugins/vnc_access.py +4 -2
- souleyez/plugins/vnc_brute.py +3 -1
- souleyez/plugins/web_login_test.py +1 -0
- souleyez/plugins/whois.py +3 -1
- souleyez/plugins/wpscan.py +3 -1
- souleyez/reporting/attack_chain.py +2 -1
- souleyez/reporting/charts.py +1 -0
- souleyez/reporting/compliance_mappings.py +1 -0
- souleyez/reporting/detection_report.py +10 -10
- souleyez/reporting/formatters.py +7 -12
- souleyez/reporting/generator.py +34 -46
- souleyez/reporting/metrics.py +2 -1
- souleyez/scanner.py +6 -3
- souleyez/security/__init__.py +7 -5
- souleyez/security/scope_validator.py +5 -4
- souleyez/security.py +5 -2
- souleyez/storage/credentials.py +14 -19
- souleyez/storage/crypto.py +7 -4
- souleyez/storage/database.py +6 -6
- souleyez/storage/db.py +8 -8
- souleyez/storage/deliverable_evidence.py +2 -1
- souleyez/storage/deliverable_exporter.py +3 -2
- souleyez/storage/deliverable_templates.py +2 -1
- souleyez/storage/deliverables.py +2 -1
- souleyez/storage/engagements.py +6 -4
- souleyez/storage/evidence.py +5 -4
- souleyez/storage/execution_log.py +4 -2
- souleyez/storage/exploit_attempts.py +3 -2
- souleyez/storage/exploits.py +3 -1
- souleyez/storage/findings.py +3 -1
- souleyez/storage/hosts.py +5 -2
- souleyez/storage/migrate_to_engagements.py +14 -24
- souleyez/storage/migrations/_001_add_credential_enhancements.py +12 -21
- souleyez/storage/migrations/_003_add_execution_log.py +8 -13
- souleyez/storage/migrations/_005_screenshots.py +2 -4
- souleyez/storage/migrations/_006_deliverables.py +2 -4
- souleyez/storage/migrations/_007_deliverable_templates.py +4 -8
- souleyez/storage/migrations/_008_add_nuclei_table.py +2 -4
- souleyez/storage/migrations/_010_evidence_linking.py +6 -12
- souleyez/storage/migrations/_012_team_collaboration.py +12 -24
- souleyez/storage/migrations/_013_add_host_tags.py +2 -4
- souleyez/storage/migrations/_014_exploit_attempts.py +10 -20
- souleyez/storage/migrations/_015_add_mac_os_fields.py +4 -8
- souleyez/storage/migrations/_016_add_domain_field.py +2 -4
- souleyez/storage/migrations/_017_msf_sessions.py +8 -16
- souleyez/storage/migrations/_018_add_osint_target.py +4 -8
- souleyez/storage/migrations/_019_add_engagement_type.py +4 -8
- souleyez/storage/migrations/_020_add_rbac.py +9 -17
- souleyez/storage/migrations/_021_wazuh_integration.py +4 -8
- souleyez/storage/migrations/_023_fix_detection_results_fk.py +2 -4
- souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +4 -8
- souleyez/storage/migrations/_026_add_engagement_scope.py +4 -8
- souleyez/storage/migrations/_027_multi_siem_persistence.py +8 -16
- souleyez/storage/migrations/__init__.py +1 -4
- souleyez/storage/migrations/migration_manager.py +6 -9
- souleyez/storage/msf_sessions.py +1 -1
- souleyez/storage/osint.py +3 -1
- souleyez/storage/recommendation_engine.py +3 -2
- souleyez/storage/screenshots.py +2 -1
- souleyez/storage/smb_shares.py +3 -1
- souleyez/storage/sqlmap_data.py +6 -4
- souleyez/storage/team_collaboration.py +3 -2
- souleyez/storage/timeline_tracker.py +2 -1
- souleyez/storage/wazuh_vulns.py +3 -1
- souleyez/storage/web_paths.py +3 -1
- souleyez/testing/credential_tester.py +2 -0
- souleyez/ui/__init__.py +2 -1
- souleyez/ui/ai_quotes.py +1 -1
- souleyez/ui/attack_surface.py +50 -28
- souleyez/ui/chain_rules_view.py +6 -3
- souleyez/ui/correlation_view.py +3 -2
- souleyez/ui/dashboard.py +85 -139
- souleyez/ui/deliverables_view.py +1 -1
- souleyez/ui/design_system.py +5 -3
- souleyez/ui/errors.py +3 -1
- souleyez/ui/evidence_linking_view.py +2 -1
- souleyez/ui/evidence_vault.py +11 -6
- souleyez/ui/exploit_suggestions_view.py +11 -7
- souleyez/ui/export_view.py +3 -1
- souleyez/ui/gap_analysis_view.py +6 -3
- souleyez/ui/help_system.py +4 -1
- souleyez/ui/intelligence_view.py +7 -3
- souleyez/ui/interactive.py +1280 -558
- souleyez/ui/interactive_selector.py +3 -2
- souleyez/ui/log_formatter.py +1 -0
- souleyez/ui/menu_components.py +3 -1
- souleyez/ui/msf_auxiliary_menu.py +4 -1
- souleyez/ui/pending_chains_view.py +15 -12
- souleyez/ui/progress_indicators.py +5 -2
- souleyez/ui/recommendations_view.py +4 -2
- souleyez/ui/rule_builder.py +4 -1
- souleyez/ui/setup_wizard.py +10 -8
- souleyez/ui/shortcuts.py +1 -1
- souleyez/ui/splunk_gap_analysis_view.py +7 -4
- souleyez/ui/splunk_vulns_view.py +4 -1
- souleyez/ui/team_dashboard.py +7 -5
- souleyez/ui/template_selector.py +2 -1
- souleyez/ui/terminal.py +3 -2
- souleyez/ui/timeline_view.py +2 -1
- souleyez/ui/tool_setup.py +92 -31
- souleyez/ui/tutorial.py +7 -4
- souleyez/ui/tutorial_state.py +3 -2
- souleyez/ui/wazuh_vulns_view.py +5 -2
- souleyez/ui/wordlist_browser.py +4 -3
- souleyez/ui.py +13 -7
- souleyez/utils/tool_checker.py +95 -17
- souleyez/utils.py +4 -4
- souleyez/wordlists.py +1 -0
- {souleyez-3.0.0.dist-info → souleyez-3.0.9.dist-info}/METADATA +1 -1
- souleyez-3.0.9.dist-info/RECORD +445 -0
- souleyez-3.0.0.dist-info/RECORD +0 -443
- {souleyez-3.0.0.dist-info → souleyez-3.0.9.dist-info}/WHEEL +0 -0
- {souleyez-3.0.0.dist-info → souleyez-3.0.9.dist-info}/entry_points.txt +0 -0
- {souleyez-3.0.0.dist-info → souleyez-3.0.9.dist-info}/licenses/LICENSE +0 -0
- {souleyez-3.0.0.dist-info → souleyez-3.0.9.dist-info}/top_level.txt +0 -0
souleyez/parsers/nmap_parser.py
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
"""
|
|
3
3
|
souleyez.parsers.nmap_parser - Parse nmap output into structured data
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
import re
|
|
6
|
-
from typing import
|
|
7
|
+
from typing import Any, Dict, List
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
def parse_nmap_vuln_scripts(output: str) -> List[Dict[str, Any]]:
|
|
@@ -568,8 +569,18 @@ def parse_nmap_text(output: str) -> Dict[str, Any]:
|
|
|
568
569
|
# Fallback: If service is unknown but port is a common web port, assume HTTP
|
|
569
570
|
# This handles cases where nmap misidentifies or can't fingerprint web apps
|
|
570
571
|
# Common misidentifications: ppp (port 3000), upnp (port 8000), tcpwrapped
|
|
571
|
-
|
|
572
|
-
|
|
572
|
+
# Note: nmap adds "?" suffix for uncertain matches (e.g., "ppp?")
|
|
573
|
+
# Port 11434 is Ollama API - runs HTTP but nmap often identifies as "unknown"
|
|
574
|
+
common_web_ports = [3000, 8080, 8000, 8888, 9090, 11434]
|
|
575
|
+
misidentified_services = [
|
|
576
|
+
"unknown",
|
|
577
|
+
"tcpwrapped",
|
|
578
|
+
"ppp",
|
|
579
|
+
"ppp?",
|
|
580
|
+
"upnp",
|
|
581
|
+
"upnp?",
|
|
582
|
+
None,
|
|
583
|
+
]
|
|
573
584
|
if (
|
|
574
585
|
service_name in misidentified_services or not service_name
|
|
575
586
|
) and port in common_web_ports:
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
"""
|
|
3
3
|
souleyez.parsers.nuclei_parser - Parse Nuclei JSONL output
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
import json
|
|
6
|
-
import tempfile
|
|
7
7
|
import os
|
|
8
|
-
|
|
8
|
+
import tempfile
|
|
9
|
+
from typing import Any, Dict
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def parse_nuclei(log_path: str, target: str) -> Dict[str, Any]:
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
"""
|
|
3
3
|
souleyez.parsers.searchsploit_parser - Parse SearchSploit JSON output
|
|
4
4
|
"""
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
import csv
|
|
7
|
+
import json
|
|
7
8
|
import re
|
|
8
9
|
from pathlib import Path
|
|
9
|
-
from typing import
|
|
10
|
+
from typing import Any, Dict, Optional
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def _load_exploitdb_csv() -> Dict[str, Dict[str, str]]:
|
|
@@ -4,8 +4,9 @@ souleyez.parsers.smbmap_parser
|
|
|
4
4
|
|
|
5
5
|
Parses smbmap SMB share enumeration output into structured data.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import re
|
|
8
|
-
from typing import Dict, List
|
|
9
|
+
from typing import Any, Dict, List
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def parse_smbmap_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
@@ -4,8 +4,9 @@ souleyez.parsers.sqlmap_parser
|
|
|
4
4
|
|
|
5
5
|
Parses SQLMap SQL injection detection output into structured findings.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import re
|
|
8
|
-
from typing import
|
|
9
|
+
from typing import Any, Dict
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
@@ -181,6 +182,29 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
181
182
|
elif method == "POST" and effective_post_data:
|
|
182
183
|
result["injectable_post_data"] = effective_post_data
|
|
183
184
|
|
|
185
|
+
# Look ahead for Type: lines to extract techniques
|
|
186
|
+
techniques = []
|
|
187
|
+
technique_str = None
|
|
188
|
+
for m in range(j + 1, min(j + 20, len(lines))):
|
|
189
|
+
type_line = lines[m].strip()
|
|
190
|
+
if type_line.startswith("Type:"):
|
|
191
|
+
tech_type = type_line.replace("Type:", "").strip()
|
|
192
|
+
techniques.append(tech_type)
|
|
193
|
+
if not technique_str:
|
|
194
|
+
technique_str = tech_type
|
|
195
|
+
elif type_line.startswith("---") and techniques:
|
|
196
|
+
break # End of technique block
|
|
197
|
+
elif type_line.startswith("[") and techniques:
|
|
198
|
+
break # Hit next section
|
|
199
|
+
|
|
200
|
+
# Determine technique string
|
|
201
|
+
if len(techniques) > 1:
|
|
202
|
+
technique_str = "multiple"
|
|
203
|
+
elif techniques:
|
|
204
|
+
technique_str = techniques[0]
|
|
205
|
+
else:
|
|
206
|
+
technique_str = "sqli"
|
|
207
|
+
|
|
184
208
|
# Add vulnerability entry
|
|
185
209
|
result["vulnerabilities"].append(
|
|
186
210
|
{
|
|
@@ -190,6 +214,8 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
190
214
|
"injectable": True,
|
|
191
215
|
"severity": "critical",
|
|
192
216
|
"description": f"Parameter '{param}' is vulnerable to SQL injection (resumed from session)",
|
|
217
|
+
"technique": technique_str,
|
|
218
|
+
"dbms": "Unknown", # Will be updated later if found
|
|
193
219
|
}
|
|
194
220
|
)
|
|
195
221
|
|
|
@@ -199,7 +225,7 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
199
225
|
"parameter": param,
|
|
200
226
|
"method": method,
|
|
201
227
|
"post_data": effective_post_data,
|
|
202
|
-
"techniques":
|
|
228
|
+
"techniques": techniques,
|
|
203
229
|
}
|
|
204
230
|
if not any(
|
|
205
231
|
ip["url"] == injection_point["url"]
|
|
@@ -498,6 +524,8 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
498
524
|
"injectable": True,
|
|
499
525
|
"severity": "critical",
|
|
500
526
|
"description": f"Parameter '{param}' is vulnerable to SQL injection",
|
|
527
|
+
"technique": "sqli",
|
|
528
|
+
"dbms": result.get("dbms", "Unknown"),
|
|
501
529
|
}
|
|
502
530
|
)
|
|
503
531
|
|
|
@@ -829,6 +857,12 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
829
857
|
if best["post_data"]:
|
|
830
858
|
result["injectable_post_data"] = best["post_data"]
|
|
831
859
|
|
|
860
|
+
# POST-PROCESSING: Update vulnerabilities with actual DBMS if it was parsed later
|
|
861
|
+
if result.get("dbms"):
|
|
862
|
+
for vuln in result["vulnerabilities"]:
|
|
863
|
+
if vuln.get("dbms") == "Unknown" or vuln.get("dbms") is None:
|
|
864
|
+
vuln["dbms"] = result["dbms"]
|
|
865
|
+
|
|
832
866
|
return result
|
|
833
867
|
|
|
834
868
|
|
|
@@ -4,8 +4,9 @@ souleyez.parsers.theharvester_parser
|
|
|
4
4
|
|
|
5
5
|
Parses theHarvester OSINT output into structured data.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import re
|
|
8
|
-
from typing import
|
|
9
|
+
from typing import Any, Dict
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def parse_theharvester_output(output: str, target: str = "") -> Dict[str, Any]:
|
souleyez/parsers/whois_parser.py
CHANGED
|
@@ -4,8 +4,9 @@ souleyez.parsers.whois_parser
|
|
|
4
4
|
|
|
5
5
|
Parses WHOIS domain information output into structured OSINT data.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import re
|
|
8
|
-
from typing import Dict, List
|
|
9
|
+
from typing import Any, Dict, List
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def parse_whois_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
@@ -4,9 +4,10 @@ souleyez.parsers.wpscan_parser
|
|
|
4
4
|
|
|
5
5
|
Parses WPScan WordPress vulnerability scan output into structured findings.
|
|
6
6
|
"""
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
import json
|
|
9
|
-
|
|
9
|
+
import re
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def parse_wpscan_output(output: str, target: str = "") -> Dict[str, Any]:
|
souleyez/plugins/afp.py
CHANGED
|
@@ -5,12 +5,14 @@ souleyez.plugins.afp
|
|
|
5
5
|
AFP (Apple Filing Protocol) enumeration plugin.
|
|
6
6
|
Discovers AFP shares on macOS systems.
|
|
7
7
|
"""
|
|
8
|
+
|
|
8
9
|
import subprocess
|
|
9
10
|
import time
|
|
10
11
|
from typing import List
|
|
11
12
|
|
|
13
|
+
from souleyez.security.validation import ValidationError, validate_target
|
|
14
|
+
|
|
12
15
|
from .plugin_base import PluginBase
|
|
13
|
-
from souleyez.security.validation import validate_target, ValidationError
|
|
14
16
|
|
|
15
17
|
HELP = {
|
|
16
18
|
"name": "AFP — Apple File Sharing Enumeration",
|
souleyez/plugins/afp_brute.py
CHANGED
|
@@ -5,12 +5,14 @@ souleyez.plugins.afp_brute
|
|
|
5
5
|
AFP brute force plugin using Hydra.
|
|
6
6
|
Attacks AFP file sharing on macOS systems.
|
|
7
7
|
"""
|
|
8
|
+
|
|
8
9
|
import subprocess
|
|
9
10
|
import time
|
|
10
11
|
from typing import List
|
|
11
12
|
|
|
13
|
+
from souleyez.security.validation import ValidationError, validate_target
|
|
14
|
+
|
|
12
15
|
from .plugin_base import PluginBase
|
|
13
|
-
from souleyez.security.validation import validate_target, ValidationError
|
|
14
16
|
|
|
15
17
|
HELP = {
|
|
16
18
|
"name": "AFP Brute — Apple File Sharing Attack",
|
souleyez/plugins/ard.py
CHANGED
|
@@ -5,12 +5,14 @@ souleyez.plugins.ard
|
|
|
5
5
|
ARD/VNC (Apple Remote Desktop) enumeration plugin.
|
|
6
6
|
Discovers VNC/ARD services on macOS systems.
|
|
7
7
|
"""
|
|
8
|
+
|
|
8
9
|
import subprocess
|
|
9
10
|
import time
|
|
10
11
|
from typing import List
|
|
11
12
|
|
|
13
|
+
from souleyez.security.validation import ValidationError, validate_target
|
|
14
|
+
|
|
12
15
|
from .plugin_base import PluginBase
|
|
13
|
-
from souleyez.security.validation import validate_target, ValidationError
|
|
14
16
|
|
|
15
17
|
HELP = {
|
|
16
18
|
"name": "ARD — Apple Remote Desktop/VNC Enumeration",
|
souleyez/plugins/bloodhound.py
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
"""
|
|
3
3
|
Bloodhound plugin - Active Directory attack path mapping.
|
|
4
4
|
"""
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
import json
|
|
7
|
-
|
|
7
|
+
import subprocess
|
|
8
8
|
from datetime import datetime
|
|
9
|
+
from pathlib import Path
|
|
9
10
|
|
|
10
11
|
HELP = {
|
|
11
12
|
"name": "Bloodhound - Active Directory Attack Path Mapping",
|
souleyez/plugins/certipy.py
CHANGED
souleyez/plugins/crackmapexec.py
CHANGED
|
@@ -4,12 +4,14 @@ souleyez.plugins.crackmapexec - Swiss army knife for Windows/AD pentesting
|
|
|
4
4
|
|
|
5
5
|
Note: Uses NetExec (netexec/nxc), the successor to CrackMapExec
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import subprocess
|
|
8
9
|
import time
|
|
9
10
|
from typing import List
|
|
10
11
|
|
|
12
|
+
from souleyez.security.validation import ValidationError, validate_target
|
|
13
|
+
|
|
11
14
|
from .plugin_base import PluginBase
|
|
12
|
-
from souleyez.security.validation import validate_target, ValidationError
|
|
13
15
|
|
|
14
16
|
HELP = {
|
|
15
17
|
"name": "CrackMapExec (CME) - Windows/AD Pentesting Tool",
|
|
@@ -248,6 +250,8 @@ class CrackMapExecPlugin(PluginBase):
|
|
|
248
250
|
elif protocol not in args:
|
|
249
251
|
cmd.extend(["--shares"])
|
|
250
252
|
|
|
253
|
+
# For anonymous enumeration, use null session with empty domain
|
|
254
|
+
# netexec 1.5.0 requires -d '' for proper null session (1.4.0 didn't need it)
|
|
251
255
|
has_creds = any(arg in cmd for arg in ["-u", "--username", "-p", "--password"])
|
|
252
256
|
has_enum = any(
|
|
253
257
|
arg in cmd
|
|
@@ -267,6 +271,8 @@ class CrackMapExecPlugin(PluginBase):
|
|
|
267
271
|
cmd.insert(insert_pos + 1, "")
|
|
268
272
|
cmd.insert(insert_pos + 2, "-p")
|
|
269
273
|
cmd.insert(insert_pos + 3, "")
|
|
274
|
+
cmd.insert(insert_pos + 4, "-d")
|
|
275
|
+
cmd.insert(insert_pos + 5, "")
|
|
270
276
|
|
|
271
277
|
return {"cmd": cmd, "timeout": 1800}
|
|
272
278
|
|
|
@@ -341,8 +347,8 @@ class CrackMapExecPlugin(PluginBase):
|
|
|
341
347
|
# No protocol specified in args, add default behavior
|
|
342
348
|
cmd.extend(["--shares"])
|
|
343
349
|
|
|
344
|
-
#
|
|
345
|
-
#
|
|
350
|
+
# For anonymous enumeration, use null session with empty domain
|
|
351
|
+
# netexec 1.5.0 requires -d '' for proper null session (1.4.0 didn't need it)
|
|
346
352
|
has_creds = any(arg in cmd for arg in ["-u", "--username", "-p", "--password"])
|
|
347
353
|
has_enum = any(
|
|
348
354
|
arg in cmd
|
|
@@ -357,15 +363,13 @@ class CrackMapExecPlugin(PluginBase):
|
|
|
357
363
|
)
|
|
358
364
|
|
|
359
365
|
if has_enum and not has_creds:
|
|
360
|
-
# Add null session credentials after ALL targets but before flags
|
|
361
|
-
# Position: 1 (netexec) + 1 (protocol) + len(target_list)
|
|
362
|
-
# Example: ['netexec', 'smb', '10.0.0.14', '10.0.0.82', '--shares']
|
|
363
|
-
# Insert at position 4: ['netexec', 'smb', '10.0.0.14', '10.0.0.82', '-u', '', '-p', '', '--shares']
|
|
364
366
|
insert_pos = 2 + len(target_list)
|
|
365
367
|
cmd.insert(insert_pos, "-u")
|
|
366
368
|
cmd.insert(insert_pos + 1, "")
|
|
367
369
|
cmd.insert(insert_pos + 2, "-p")
|
|
368
370
|
cmd.insert(insert_pos + 3, "")
|
|
371
|
+
cmd.insert(insert_pos + 4, "-d")
|
|
372
|
+
cmd.insert(insert_pos + 5, "")
|
|
369
373
|
|
|
370
374
|
if not log_path:
|
|
371
375
|
try:
|
souleyez/plugins/dalfox.py
CHANGED
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
"""
|
|
3
3
|
souleyez.plugins.dalfox - XSS vulnerability scanner
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
6
9
|
import subprocess
|
|
7
10
|
import time
|
|
8
|
-
import json
|
|
9
11
|
from typing import List
|
|
10
12
|
|
|
13
|
+
from souleyez.security.validation import ValidationError, validate_url
|
|
14
|
+
|
|
11
15
|
from .plugin_base import PluginBase
|
|
12
|
-
from souleyez.security.validation import validate_url, ValidationError
|
|
13
16
|
|
|
14
17
|
HELP = {
|
|
15
18
|
"name": "Dalfox - XSS Vulnerability Scanner",
|
souleyez/plugins/dns_hijack.py
CHANGED
|
@@ -5,12 +5,14 @@ souleyez.plugins.dns_hijack
|
|
|
5
5
|
DNS hijacking detection plugin.
|
|
6
6
|
Checks if a router is performing DNS hijacking/redirection.
|
|
7
7
|
"""
|
|
8
|
+
|
|
8
9
|
import subprocess
|
|
9
10
|
import time
|
|
10
11
|
from typing import List
|
|
11
12
|
|
|
13
|
+
from souleyez.security.validation import ValidationError, validate_target
|
|
14
|
+
|
|
12
15
|
from .plugin_base import PluginBase
|
|
13
|
-
from souleyez.security.validation import validate_target, ValidationError
|
|
14
16
|
|
|
15
17
|
HELP = {
|
|
16
18
|
"name": "DNS Hijack — DNS Manipulation Detection",
|
souleyez/plugins/dnsrecon.py
CHANGED
|
@@ -4,12 +4,14 @@ souleyez.plugins.dnsrecon
|
|
|
4
4
|
|
|
5
5
|
DNSRecon DNS enumeration and reconnaissance plugin.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import subprocess
|
|
8
9
|
import time
|
|
9
10
|
from typing import List
|
|
10
11
|
|
|
12
|
+
from souleyez.security.validation import ValidationError, validate_target
|
|
13
|
+
|
|
11
14
|
from .plugin_base import PluginBase
|
|
12
|
-
from souleyez.security.validation import validate_target, ValidationError
|
|
13
15
|
|
|
14
16
|
HELP = {
|
|
15
17
|
"name": "DNSRecon — DNS Enumeration Tool",
|
souleyez/plugins/enum4linux.py
CHANGED
|
@@ -4,12 +4,14 @@ souleyez.plugins.enum4linux
|
|
|
4
4
|
|
|
5
5
|
Enum4linux SMB enumeration plugin with unified interface.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import subprocess
|
|
8
9
|
import time
|
|
9
10
|
from typing import List
|
|
10
11
|
|
|
12
|
+
from souleyez.security.validation import ValidationError, validate_target
|
|
13
|
+
|
|
11
14
|
from .plugin_base import PluginBase
|
|
12
|
-
from souleyez.security.validation import validate_target, ValidationError
|
|
13
15
|
|
|
14
16
|
HELP = {
|
|
15
17
|
"name": "enum4linux (SMB Enumeration)",
|
souleyez/plugins/evil_winrm.py
CHANGED
souleyez/plugins/ffuf.py
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
"""
|
|
3
3
|
souleyez.plugins.ffuf - Fast web fuzzer
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
import subprocess
|
|
6
7
|
from typing import List
|
|
7
8
|
|
|
9
|
+
from souleyez.security.validation import ValidationError, validate_url
|
|
10
|
+
|
|
8
11
|
from .plugin_base import PluginBase
|
|
9
|
-
from souleyez.security.validation import validate_url, ValidationError
|
|
10
12
|
|
|
11
13
|
HELP = {
|
|
12
14
|
"name": "ffuf - Fast Web Fuzzer",
|
|
@@ -5,10 +5,11 @@ souleyez.plugins.firmware_extract
|
|
|
5
5
|
Firmware extraction and analysis plugin using binwalk.
|
|
6
6
|
Extracts and analyzes router firmware images.
|
|
7
7
|
"""
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
import os
|
|
9
10
|
import shutil
|
|
11
|
+
import subprocess
|
|
10
12
|
import time
|
|
11
|
-
import os
|
|
12
13
|
from typing import List
|
|
13
14
|
|
|
14
15
|
from .plugin_base import PluginBase
|
souleyez/plugins/gobuster.py
CHANGED
|
@@ -2,18 +2,21 @@
|
|
|
2
2
|
"""
|
|
3
3
|
souleyez.plugins.gobuster
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import re
|
|
6
9
|
import subprocess
|
|
7
10
|
import time
|
|
8
|
-
import re
|
|
9
11
|
import uuid
|
|
10
|
-
from typing import
|
|
12
|
+
from typing import Dict, List, Optional
|
|
11
13
|
from urllib.parse import urlparse
|
|
12
14
|
|
|
13
15
|
import requests
|
|
14
16
|
|
|
17
|
+
from souleyez.security.validation import ValidationError, validate_url
|
|
18
|
+
|
|
15
19
|
from .plugin_base import PluginBase
|
|
16
|
-
from souleyez.security.validation import validate_url, ValidationError
|
|
17
20
|
|
|
18
21
|
HELP = {
|
|
19
22
|
"name": "Gobuster — Directory, File & DNS/VHost Brute-Force Tool",
|
souleyez/plugins/gpp_extract.py
CHANGED
souleyez/plugins/hashcat.py
CHANGED
|
@@ -339,7 +339,8 @@ class HttpFingerprintPlugin(PluginBase):
|
|
|
339
339
|
try:
|
|
340
340
|
# Use thread-based hard timeout to prevent indefinite hangs
|
|
341
341
|
# urllib timeouts don't always work if server accepts connection but stalls
|
|
342
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
342
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
343
|
+
from concurrent.futures import TimeoutError as FuturesTimeout
|
|
343
344
|
|
|
344
345
|
hard_timeout = timeout * 3 # 30 seconds max for entire probe operation
|
|
345
346
|
|
|
@@ -446,7 +447,9 @@ class HttpFingerprintPlugin(PluginBase):
|
|
|
446
447
|
host = parsed.hostname
|
|
447
448
|
port = parsed.port or (443 if parsed.scheme == "https" else 80)
|
|
448
449
|
try:
|
|
449
|
-
with socket.create_connection(
|
|
450
|
+
with socket.create_connection(
|
|
451
|
+
(host, port), timeout=min(timeout, 5)
|
|
452
|
+
) as sock:
|
|
450
453
|
pass # Just checking if we can connect
|
|
451
454
|
except (socket.timeout, socket.error, OSError) as e:
|
|
452
455
|
# Port not responding - return error result immediately
|
|
@@ -1080,9 +1083,60 @@ class HttpFingerprintPlugin(PluginBase):
|
|
|
1080
1083
|
# Track CMS detections to avoid duplicates
|
|
1081
1084
|
cms_detected = {}
|
|
1082
1085
|
|
|
1086
|
+
# CMS-specific content markers to verify detection (prevents SPA false positives)
|
|
1087
|
+
cms_content_markers = {
|
|
1088
|
+
"WordPress": [
|
|
1089
|
+
b"wp-login",
|
|
1090
|
+
b"wp-includes",
|
|
1091
|
+
b"wp-content",
|
|
1092
|
+
b"wordpress",
|
|
1093
|
+
b"wlwmanifest",
|
|
1094
|
+
b"xmlrpc.php",
|
|
1095
|
+
],
|
|
1096
|
+
"Joomla": [b"joomla", b"com_content", b"/administrator/"],
|
|
1097
|
+
"Drupal": [b"drupal", b"sites/default", b"sites/all"],
|
|
1098
|
+
"TYPO3": [b"typo3", b"typo3conf"],
|
|
1099
|
+
"Sitecore": [b"sitecore"],
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1083
1102
|
for path, category, subtype in paths_to_check:
|
|
1084
1103
|
try:
|
|
1085
1104
|
url = base.rstrip("/") + path
|
|
1105
|
+
|
|
1106
|
+
# For CMS detection, use GET to verify content (prevents SPA false positives)
|
|
1107
|
+
# SPAs return 200 for all routes but with same content
|
|
1108
|
+
if category == "cms":
|
|
1109
|
+
req = urllib.request.Request(
|
|
1110
|
+
url,
|
|
1111
|
+
method="GET",
|
|
1112
|
+
headers={
|
|
1113
|
+
"User-Agent": "Mozilla/5.0 (compatible; SoulEyez/1.0)"
|
|
1114
|
+
},
|
|
1115
|
+
)
|
|
1116
|
+
try:
|
|
1117
|
+
with urllib.request.urlopen(
|
|
1118
|
+
req, timeout=timeout, context=ctx
|
|
1119
|
+
) as response:
|
|
1120
|
+
status = response.getcode()
|
|
1121
|
+
# Read first 4KB to check for CMS markers
|
|
1122
|
+
content = response.read(4096).lower()
|
|
1123
|
+
except urllib.error.HTTPError as e:
|
|
1124
|
+
status = e.code
|
|
1125
|
+
content = b""
|
|
1126
|
+
|
|
1127
|
+
# Verify response contains CMS-specific content
|
|
1128
|
+
if status in (200, 301, 302, 401, 403):
|
|
1129
|
+
markers = cms_content_markers.get(subtype, [])
|
|
1130
|
+
has_cms_content = any(marker in content for marker in markers)
|
|
1131
|
+
if has_cms_content:
|
|
1132
|
+
if subtype not in cms_detected:
|
|
1133
|
+
cms_detected[subtype] = []
|
|
1134
|
+
cms_detected[subtype].append(
|
|
1135
|
+
{"path": path, "status": status}
|
|
1136
|
+
)
|
|
1137
|
+
continue
|
|
1138
|
+
|
|
1139
|
+
# For admin/API detection, HEAD is fine (just checking existence)
|
|
1086
1140
|
req = urllib.request.Request(
|
|
1087
1141
|
url,
|
|
1088
1142
|
method="HEAD",
|
|
@@ -1099,11 +1153,7 @@ class HttpFingerprintPlugin(PluginBase):
|
|
|
1099
1153
|
|
|
1100
1154
|
# Consider 2xx, 3xx, 401, 403 as "exists"
|
|
1101
1155
|
if status in (200, 201, 204, 301, 302, 303, 307, 308, 401, 403):
|
|
1102
|
-
if category == "
|
|
1103
|
-
if subtype not in cms_detected:
|
|
1104
|
-
cms_detected[subtype] = []
|
|
1105
|
-
cms_detected[subtype].append({"path": path, "status": status})
|
|
1106
|
-
elif category == "admin":
|
|
1156
|
+
if category == "admin":
|
|
1107
1157
|
result["admin_panels"].append(
|
|
1108
1158
|
{
|
|
1109
1159
|
"path": path,
|
souleyez/plugins/hydra.py
CHANGED
|
@@ -4,13 +4,15 @@ souleyez.plugins.hydra
|
|
|
4
4
|
|
|
5
5
|
Hydra network login brute-forcer plugin.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import subprocess
|
|
8
9
|
import time
|
|
9
10
|
from typing import List
|
|
10
11
|
from urllib.parse import urlparse
|
|
11
12
|
|
|
13
|
+
from souleyez.security.validation import ValidationError, validate_target
|
|
14
|
+
|
|
12
15
|
from .plugin_base import PluginBase
|
|
13
|
-
from souleyez.security.validation import validate_target, ValidationError
|
|
14
16
|
|
|
15
17
|
HELP = {
|
|
16
18
|
"name": "Hydra — Network Login Brute-Forcer",
|
|
@@ -840,8 +842,8 @@ class HydraPlugin(PluginBase):
|
|
|
840
842
|
return None
|
|
841
843
|
|
|
842
844
|
# If multiple targets, create a temporary file and use -M flag
|
|
843
|
-
import tempfile
|
|
844
845
|
import os
|
|
846
|
+
import tempfile
|
|
845
847
|
|
|
846
848
|
# Hydra syntax: hydra [OPTIONS] target service [SERVICE-OPTIONS]
|
|
847
849
|
# Need to split args into: global options, service type, and service options
|
|
@@ -983,8 +985,8 @@ class HydraPlugin(PluginBase):
|
|
|
983
985
|
raise ValueError(f"Invalid target: {e}")
|
|
984
986
|
|
|
985
987
|
# If multiple targets, create a temporary file and use -M flag
|
|
986
|
-
import tempfile
|
|
987
988
|
import os
|
|
989
|
+
import tempfile
|
|
988
990
|
|
|
989
991
|
if len(validated_targets) > 1:
|
|
990
992
|
# Create temp file with targets
|