souleyez 2.43.29__py3-none-any.whl → 2.43.34__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- souleyez/__init__.py +1 -2
- souleyez/ai/__init__.py +21 -15
- souleyez/ai/action_mapper.py +249 -150
- souleyez/ai/chain_advisor.py +116 -100
- souleyez/ai/claude_provider.py +29 -28
- souleyez/ai/context_builder.py +80 -62
- souleyez/ai/executor.py +158 -117
- souleyez/ai/feedback_handler.py +136 -121
- souleyez/ai/llm_factory.py +27 -20
- souleyez/ai/llm_provider.py +4 -2
- souleyez/ai/ollama_provider.py +6 -9
- souleyez/ai/ollama_service.py +44 -37
- souleyez/ai/path_scorer.py +91 -76
- souleyez/ai/recommender.py +176 -144
- souleyez/ai/report_context.py +74 -73
- souleyez/ai/report_service.py +84 -66
- souleyez/ai/result_parser.py +222 -229
- souleyez/ai/safety.py +67 -44
- souleyez/auth/__init__.py +23 -22
- souleyez/auth/audit.py +36 -26
- souleyez/auth/engagement_access.py +65 -48
- souleyez/auth/permissions.py +14 -3
- souleyez/auth/session_manager.py +54 -37
- souleyez/auth/user_manager.py +109 -64
- souleyez/commands/audit.py +40 -43
- souleyez/commands/auth.py +35 -15
- souleyez/commands/deliverables.py +55 -50
- souleyez/commands/engagement.py +47 -28
- souleyez/commands/license.py +32 -23
- souleyez/commands/screenshots.py +36 -32
- souleyez/commands/user.py +82 -36
- souleyez/config.py +52 -44
- souleyez/core/credential_tester.py +87 -81
- souleyez/core/cve_mappings.py +179 -192
- souleyez/core/cve_matcher.py +162 -148
- souleyez/core/msf_auto_mapper.py +100 -83
- souleyez/core/msf_chain_engine.py +294 -256
- souleyez/core/msf_database.py +153 -70
- souleyez/core/msf_integration.py +679 -673
- souleyez/core/msf_rpc_client.py +40 -42
- souleyez/core/msf_rpc_manager.py +77 -79
- souleyez/core/msf_sync_manager.py +241 -181
- souleyez/core/network_utils.py +22 -15
- souleyez/core/parser_handler.py +34 -25
- souleyez/core/pending_chains.py +114 -63
- souleyez/core/templates.py +158 -107
- souleyez/core/tool_chaining.py +9526 -2879
- souleyez/core/version_utils.py +79 -94
- souleyez/core/vuln_correlation.py +136 -89
- souleyez/core/web_utils.py +33 -32
- souleyez/data/wordlists/ad_users.txt +378 -0
- souleyez/data/wordlists/api_endpoints_large.txt +769 -0
- souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
- souleyez/data/wordlists/lfi_payloads.txt +82 -0
- souleyez/data/wordlists/passwords_brute.txt +1548 -0
- souleyez/data/wordlists/passwords_crack.txt +2479 -0
- souleyez/data/wordlists/passwords_spray.txt +386 -0
- souleyez/data/wordlists/subdomains_large.txt +5057 -0
- souleyez/data/wordlists/usernames_common.txt +694 -0
- souleyez/data/wordlists/web_dirs_large.txt +4769 -0
- souleyez/detection/__init__.py +1 -1
- souleyez/detection/attack_signatures.py +12 -17
- souleyez/detection/mitre_mappings.py +61 -55
- souleyez/detection/validator.py +97 -86
- souleyez/devtools.py +23 -10
- souleyez/docs/README.md +4 -4
- souleyez/docs/api-reference/cli-commands.md +2 -2
- souleyez/docs/developer-guide/adding-new-tools.md +562 -0
- souleyez/docs/user-guide/auto-chaining.md +30 -8
- souleyez/docs/user-guide/getting-started.md +1 -1
- souleyez/docs/user-guide/installation.md +26 -3
- souleyez/docs/user-guide/metasploit-integration.md +2 -2
- souleyez/docs/user-guide/rbac.md +1 -1
- souleyez/docs/user-guide/scope-management.md +1 -1
- souleyez/docs/user-guide/siem-integration.md +1 -1
- souleyez/docs/user-guide/tools-reference.md +1 -8
- souleyez/docs/user-guide/worker-management.md +1 -1
- souleyez/engine/background.py +1239 -535
- souleyez/engine/base.py +4 -1
- souleyez/engine/job_status.py +17 -49
- souleyez/engine/log_sanitizer.py +103 -77
- souleyez/engine/manager.py +38 -7
- souleyez/engine/result_handler.py +2200 -1550
- souleyez/engine/worker_manager.py +50 -41
- souleyez/export/evidence_bundle.py +72 -62
- souleyez/feature_flags/features.py +16 -20
- souleyez/feature_flags.py +5 -9
- souleyez/handlers/__init__.py +11 -0
- souleyez/handlers/base.py +188 -0
- souleyez/handlers/bash_handler.py +277 -0
- souleyez/handlers/bloodhound_handler.py +243 -0
- souleyez/handlers/certipy_handler.py +311 -0
- souleyez/handlers/crackmapexec_handler.py +486 -0
- souleyez/handlers/dnsrecon_handler.py +344 -0
- souleyez/handlers/enum4linux_handler.py +400 -0
- souleyez/handlers/evil_winrm_handler.py +493 -0
- souleyez/handlers/ffuf_handler.py +815 -0
- souleyez/handlers/gobuster_handler.py +1114 -0
- souleyez/handlers/gpp_extract_handler.py +334 -0
- souleyez/handlers/hashcat_handler.py +444 -0
- souleyez/handlers/hydra_handler.py +563 -0
- souleyez/handlers/impacket_getuserspns_handler.py +343 -0
- souleyez/handlers/impacket_psexec_handler.py +222 -0
- souleyez/handlers/impacket_secretsdump_handler.py +426 -0
- souleyez/handlers/john_handler.py +286 -0
- souleyez/handlers/katana_handler.py +425 -0
- souleyez/handlers/kerbrute_handler.py +298 -0
- souleyez/handlers/ldapsearch_handler.py +636 -0
- souleyez/handlers/lfi_extract_handler.py +464 -0
- souleyez/handlers/msf_auxiliary_handler.py +408 -0
- souleyez/handlers/msf_exploit_handler.py +380 -0
- souleyez/handlers/nikto_handler.py +413 -0
- souleyez/handlers/nmap_handler.py +821 -0
- souleyez/handlers/nuclei_handler.py +359 -0
- souleyez/handlers/nxc_handler.py +371 -0
- souleyez/handlers/rdp_sec_check_handler.py +353 -0
- souleyez/handlers/registry.py +292 -0
- souleyez/handlers/responder_handler.py +232 -0
- souleyez/handlers/service_explorer_handler.py +434 -0
- souleyez/handlers/smbclient_handler.py +344 -0
- souleyez/handlers/smbmap_handler.py +510 -0
- souleyez/handlers/smbpasswd_handler.py +296 -0
- souleyez/handlers/sqlmap_handler.py +1116 -0
- souleyez/handlers/theharvester_handler.py +601 -0
- souleyez/handlers/web_login_test_handler.py +327 -0
- souleyez/handlers/whois_handler.py +277 -0
- souleyez/handlers/wpscan_handler.py +554 -0
- souleyez/history.py +32 -16
- souleyez/importers/msf_importer.py +106 -75
- souleyez/importers/smart_importer.py +208 -147
- souleyez/integrations/siem/__init__.py +10 -10
- souleyez/integrations/siem/base.py +17 -18
- souleyez/integrations/siem/elastic.py +108 -122
- souleyez/integrations/siem/factory.py +207 -80
- souleyez/integrations/siem/googlesecops.py +146 -154
- souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
- souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
- souleyez/integrations/siem/sentinel.py +107 -109
- souleyez/integrations/siem/splunk.py +246 -212
- souleyez/integrations/siem/wazuh.py +65 -71
- souleyez/integrations/wazuh/__init__.py +5 -5
- souleyez/integrations/wazuh/client.py +70 -93
- souleyez/integrations/wazuh/config.py +85 -57
- souleyez/integrations/wazuh/host_mapper.py +28 -36
- souleyez/integrations/wazuh/sync.py +78 -68
- souleyez/intelligence/__init__.py +4 -5
- souleyez/intelligence/correlation_analyzer.py +309 -295
- souleyez/intelligence/exploit_knowledge.py +661 -623
- souleyez/intelligence/exploit_suggestions.py +159 -139
- souleyez/intelligence/gap_analyzer.py +132 -97
- souleyez/intelligence/gap_detector.py +251 -214
- souleyez/intelligence/sensitive_tables.py +266 -129
- souleyez/intelligence/service_parser.py +137 -123
- souleyez/intelligence/surface_analyzer.py +407 -268
- souleyez/intelligence/target_parser.py +159 -162
- souleyez/licensing/__init__.py +6 -6
- souleyez/licensing/validator.py +17 -19
- souleyez/log_config.py +79 -54
- souleyez/main.py +1505 -687
- souleyez/migrations/fix_job_counter.py +16 -14
- souleyez/parsers/bloodhound_parser.py +41 -39
- souleyez/parsers/crackmapexec_parser.py +178 -111
- souleyez/parsers/dalfox_parser.py +72 -77
- souleyez/parsers/dnsrecon_parser.py +103 -91
- souleyez/parsers/enum4linux_parser.py +183 -153
- souleyez/parsers/ffuf_parser.py +29 -25
- souleyez/parsers/gobuster_parser.py +301 -41
- souleyez/parsers/hashcat_parser.py +324 -79
- souleyez/parsers/http_fingerprint_parser.py +350 -103
- souleyez/parsers/hydra_parser.py +131 -111
- souleyez/parsers/impacket_parser.py +231 -178
- souleyez/parsers/john_parser.py +98 -86
- souleyez/parsers/katana_parser.py +316 -0
- souleyez/parsers/msf_parser.py +943 -498
- souleyez/parsers/nikto_parser.py +346 -65
- souleyez/parsers/nmap_parser.py +262 -174
- souleyez/parsers/nuclei_parser.py +40 -44
- souleyez/parsers/responder_parser.py +26 -26
- souleyez/parsers/searchsploit_parser.py +74 -74
- souleyez/parsers/service_explorer_parser.py +279 -0
- souleyez/parsers/smbmap_parser.py +180 -124
- souleyez/parsers/sqlmap_parser.py +434 -308
- souleyez/parsers/theharvester_parser.py +75 -57
- souleyez/parsers/whois_parser.py +135 -94
- souleyez/parsers/wpscan_parser.py +278 -190
- souleyez/plugins/afp.py +44 -36
- souleyez/plugins/afp_brute.py +114 -46
- souleyez/plugins/ard.py +48 -37
- souleyez/plugins/bloodhound.py +95 -61
- souleyez/plugins/certipy.py +303 -0
- souleyez/plugins/crackmapexec.py +186 -85
- souleyez/plugins/dalfox.py +120 -59
- souleyez/plugins/dns_hijack.py +146 -41
- souleyez/plugins/dnsrecon.py +97 -61
- souleyez/plugins/enum4linux.py +91 -66
- souleyez/plugins/evil_winrm.py +291 -0
- souleyez/plugins/ffuf.py +166 -90
- souleyez/plugins/firmware_extract.py +133 -29
- souleyez/plugins/gobuster.py +387 -190
- souleyez/plugins/gpp_extract.py +393 -0
- souleyez/plugins/hashcat.py +100 -73
- souleyez/plugins/http_fingerprint.py +854 -267
- souleyez/plugins/hydra.py +566 -200
- souleyez/plugins/impacket_getnpusers.py +117 -69
- souleyez/plugins/impacket_psexec.py +84 -64
- souleyez/plugins/impacket_secretsdump.py +103 -69
- souleyez/plugins/impacket_smbclient.py +89 -75
- souleyez/plugins/john.py +86 -69
- souleyez/plugins/katana.py +313 -0
- souleyez/plugins/kerbrute.py +237 -0
- souleyez/plugins/lfi_extract.py +541 -0
- souleyez/plugins/macos_ssh.py +117 -48
- souleyez/plugins/mdns.py +35 -30
- souleyez/plugins/msf_auxiliary.py +253 -130
- souleyez/plugins/msf_exploit.py +239 -161
- souleyez/plugins/nikto.py +134 -78
- souleyez/plugins/nmap.py +275 -91
- souleyez/plugins/nuclei.py +180 -89
- souleyez/plugins/nxc.py +285 -0
- souleyez/plugins/plugin_base.py +35 -36
- souleyez/plugins/plugin_template.py +13 -5
- souleyez/plugins/rdp_sec_check.py +130 -0
- souleyez/plugins/responder.py +112 -71
- souleyez/plugins/router_http_brute.py +76 -65
- souleyez/plugins/router_ssh_brute.py +118 -41
- souleyez/plugins/router_telnet_brute.py +124 -42
- souleyez/plugins/routersploit.py +91 -59
- souleyez/plugins/routersploit_exploit.py +77 -55
- souleyez/plugins/searchsploit.py +91 -77
- souleyez/plugins/service_explorer.py +1160 -0
- souleyez/plugins/smbmap.py +122 -72
- souleyez/plugins/smbpasswd.py +215 -0
- souleyez/plugins/sqlmap.py +301 -113
- souleyez/plugins/theharvester.py +127 -75
- souleyez/plugins/tr069.py +79 -57
- souleyez/plugins/upnp.py +65 -47
- souleyez/plugins/upnp_abuse.py +73 -55
- souleyez/plugins/vnc_access.py +129 -42
- souleyez/plugins/vnc_brute.py +109 -38
- souleyez/plugins/web_login_test.py +417 -0
- souleyez/plugins/whois.py +77 -58
- souleyez/plugins/wpscan.py +173 -69
- souleyez/reporting/__init__.py +2 -1
- souleyez/reporting/attack_chain.py +411 -346
- souleyez/reporting/charts.py +436 -501
- souleyez/reporting/compliance_mappings.py +334 -201
- souleyez/reporting/detection_report.py +126 -125
- souleyez/reporting/formatters.py +828 -591
- souleyez/reporting/generator.py +386 -302
- souleyez/reporting/metrics.py +72 -75
- souleyez/scanner.py +35 -29
- souleyez/security/__init__.py +37 -11
- souleyez/security/scope_validator.py +175 -106
- souleyez/security/validation.py +223 -149
- souleyez/security.py +22 -6
- souleyez/storage/credentials.py +247 -186
- souleyez/storage/crypto.py +296 -129
- souleyez/storage/database.py +73 -50
- souleyez/storage/db.py +58 -36
- souleyez/storage/deliverable_evidence.py +177 -128
- souleyez/storage/deliverable_exporter.py +282 -246
- souleyez/storage/deliverable_templates.py +134 -116
- souleyez/storage/deliverables.py +135 -130
- souleyez/storage/engagements.py +109 -56
- souleyez/storage/evidence.py +181 -152
- souleyez/storage/execution_log.py +31 -17
- souleyez/storage/exploit_attempts.py +93 -57
- souleyez/storage/exploits.py +67 -36
- souleyez/storage/findings.py +48 -61
- souleyez/storage/hosts.py +176 -144
- souleyez/storage/migrate_to_engagements.py +43 -19
- souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
- souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
- souleyez/storage/migrations/_003_add_execution_log.py +14 -8
- souleyez/storage/migrations/_005_screenshots.py +13 -5
- souleyez/storage/migrations/_006_deliverables.py +13 -5
- souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
- souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
- souleyez/storage/migrations/_010_evidence_linking.py +17 -10
- souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
- souleyez/storage/migrations/_012_team_collaboration.py +34 -21
- souleyez/storage/migrations/_013_add_host_tags.py +12 -6
- souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
- souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
- souleyez/storage/migrations/_016_add_domain_field.py +10 -4
- souleyez/storage/migrations/_017_msf_sessions.py +16 -8
- souleyez/storage/migrations/_018_add_osint_target.py +10 -6
- souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
- souleyez/storage/migrations/_020_add_rbac.py +36 -15
- souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
- souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
- souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
- souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
- souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
- souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
- souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
- souleyez/storage/migrations/__init__.py +26 -26
- souleyez/storage/migrations/migration_manager.py +19 -19
- souleyez/storage/msf_sessions.py +100 -65
- souleyez/storage/osint.py +17 -24
- souleyez/storage/recommendation_engine.py +269 -235
- souleyez/storage/screenshots.py +33 -32
- souleyez/storage/smb_shares.py +136 -92
- souleyez/storage/sqlmap_data.py +183 -128
- souleyez/storage/team_collaboration.py +135 -141
- souleyez/storage/timeline_tracker.py +122 -94
- souleyez/storage/wazuh_vulns.py +64 -66
- souleyez/storage/web_paths.py +33 -37
- souleyez/testing/credential_tester.py +221 -205
- souleyez/ui/__init__.py +1 -1
- souleyez/ui/ai_quotes.py +12 -12
- souleyez/ui/attack_surface.py +2439 -1516
- souleyez/ui/chain_rules_view.py +914 -382
- souleyez/ui/correlation_view.py +312 -230
- souleyez/ui/dashboard.py +2382 -1130
- souleyez/ui/deliverables_view.py +148 -62
- souleyez/ui/design_system.py +13 -13
- souleyez/ui/errors.py +49 -49
- souleyez/ui/evidence_linking_view.py +284 -179
- souleyez/ui/evidence_vault.py +393 -285
- souleyez/ui/exploit_suggestions_view.py +555 -349
- souleyez/ui/export_view.py +100 -66
- souleyez/ui/gap_analysis_view.py +315 -171
- souleyez/ui/help_system.py +105 -97
- souleyez/ui/intelligence_view.py +436 -293
- souleyez/ui/interactive.py +22827 -10678
- souleyez/ui/interactive_selector.py +75 -68
- souleyez/ui/log_formatter.py +47 -39
- souleyez/ui/menu_components.py +22 -13
- souleyez/ui/msf_auxiliary_menu.py +184 -133
- souleyez/ui/pending_chains_view.py +336 -172
- souleyez/ui/progress_indicators.py +5 -3
- souleyez/ui/recommendations_view.py +195 -137
- souleyez/ui/rule_builder.py +343 -225
- souleyez/ui/setup_wizard.py +678 -284
- souleyez/ui/shortcuts.py +217 -165
- souleyez/ui/splunk_gap_analysis_view.py +452 -270
- souleyez/ui/splunk_vulns_view.py +139 -86
- souleyez/ui/team_dashboard.py +498 -335
- souleyez/ui/template_selector.py +196 -105
- souleyez/ui/terminal.py +6 -6
- souleyez/ui/timeline_view.py +198 -127
- souleyez/ui/tool_setup.py +264 -164
- souleyez/ui/tutorial.py +202 -72
- souleyez/ui/tutorial_state.py +40 -40
- souleyez/ui/wazuh_vulns_view.py +235 -141
- souleyez/ui/wordlist_browser.py +260 -107
- souleyez/ui.py +464 -312
- souleyez/utils/tool_checker.py +427 -367
- souleyez/utils.py +33 -29
- souleyez/wordlists.py +134 -167
- {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/METADATA +1 -1
- souleyez-2.43.34.dist-info/RECORD +443 -0
- {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
- souleyez-2.43.29.dist-info/RECORD +0 -379
- {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
- {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1116 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
SQLMap handler.
|
|
4
|
+
|
|
5
|
+
Consolidates parsing and display logic for SQLMap SQL injection scanner jobs.
|
|
6
|
+
"""
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
from typing import Any, Dict, Optional
|
|
11
|
+
from urllib.parse import urlparse
|
|
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 SQLMapHandler(BaseToolHandler):
|
|
22
|
+
"""Handler for SQLMap SQL injection scanner jobs."""
|
|
23
|
+
|
|
24
|
+
tool_name = "sqlmap"
|
|
25
|
+
display_name = "SQLMap"
|
|
26
|
+
|
|
27
|
+
# All handlers enabled
|
|
28
|
+
has_error_handler = True
|
|
29
|
+
has_warning_handler = True
|
|
30
|
+
has_no_results_handler = True
|
|
31
|
+
has_done_handler = True
|
|
32
|
+
|
|
33
|
+
def parse_job(
|
|
34
|
+
self,
|
|
35
|
+
engagement_id: int,
|
|
36
|
+
log_path: str,
|
|
37
|
+
job: Dict[str, Any],
|
|
38
|
+
host_manager: Optional[Any] = None,
|
|
39
|
+
findings_manager: Optional[Any] = None,
|
|
40
|
+
credentials_manager: Optional[Any] = None,
|
|
41
|
+
) -> Dict[str, Any]:
|
|
42
|
+
"""
|
|
43
|
+
Parse SQLMap job results.
|
|
44
|
+
|
|
45
|
+
Extracts SQL injection vulnerabilities, databases, tables, and dumped data.
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
from souleyez.parsers.sqlmap_parser import (
|
|
49
|
+
parse_sqlmap_output,
|
|
50
|
+
get_sqli_stats,
|
|
51
|
+
)
|
|
52
|
+
from souleyez.storage.sqlmap_data import SQLMapDataManager
|
|
53
|
+
from souleyez.engine.result_handler import detect_tool_error
|
|
54
|
+
import socket
|
|
55
|
+
|
|
56
|
+
# Import managers if not provided
|
|
57
|
+
if host_manager is None:
|
|
58
|
+
from souleyez.storage.hosts import HostManager
|
|
59
|
+
|
|
60
|
+
host_manager = HostManager()
|
|
61
|
+
if findings_manager is None:
|
|
62
|
+
from souleyez.storage.findings import FindingsManager
|
|
63
|
+
|
|
64
|
+
findings_manager = FindingsManager()
|
|
65
|
+
if credentials_manager is None:
|
|
66
|
+
from souleyez.storage.credentials import CredentialsManager
|
|
67
|
+
|
|
68
|
+
credentials_manager = CredentialsManager()
|
|
69
|
+
|
|
70
|
+
target = job.get("target", "")
|
|
71
|
+
|
|
72
|
+
# Read log file
|
|
73
|
+
with open(log_path, "r", encoding="utf-8", errors="replace") as f:
|
|
74
|
+
output = f.read()
|
|
75
|
+
|
|
76
|
+
parsed = parse_sqlmap_output(output, target)
|
|
77
|
+
stats = get_sqli_stats(parsed)
|
|
78
|
+
|
|
79
|
+
# Get or create host from target URL
|
|
80
|
+
host_id = None
|
|
81
|
+
target_port = None
|
|
82
|
+
|
|
83
|
+
if parsed.get("target_url"):
|
|
84
|
+
parsed_url = urlparse(parsed["target_url"])
|
|
85
|
+
hostname = parsed_url.hostname
|
|
86
|
+
target_port = parsed_url.port or (
|
|
87
|
+
443 if parsed_url.scheme == "https" else 80
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if hostname:
|
|
91
|
+
is_ip = re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", hostname)
|
|
92
|
+
|
|
93
|
+
if is_ip:
|
|
94
|
+
host = host_manager.get_host_by_ip(engagement_id, hostname)
|
|
95
|
+
if host:
|
|
96
|
+
host_id = host["id"]
|
|
97
|
+
else:
|
|
98
|
+
host_id = host_manager.add_or_update_host(
|
|
99
|
+
engagement_id, {"ip": hostname, "status": "up"}
|
|
100
|
+
)
|
|
101
|
+
else:
|
|
102
|
+
# Try to match by hostname
|
|
103
|
+
hosts = host_manager.list_hosts(engagement_id)
|
|
104
|
+
for h in hosts:
|
|
105
|
+
if (
|
|
106
|
+
h.get("hostname") == hostname
|
|
107
|
+
or h.get("ip") == hostname
|
|
108
|
+
or h.get("ip_address") == hostname
|
|
109
|
+
):
|
|
110
|
+
host_id = h["id"]
|
|
111
|
+
break
|
|
112
|
+
|
|
113
|
+
if not host_id:
|
|
114
|
+
try:
|
|
115
|
+
ip_address = socket.gethostbyname(hostname)
|
|
116
|
+
host_id = host_manager.add_or_update_host(
|
|
117
|
+
engagement_id,
|
|
118
|
+
{
|
|
119
|
+
"ip": ip_address,
|
|
120
|
+
"hostname": hostname,
|
|
121
|
+
"status": "up",
|
|
122
|
+
},
|
|
123
|
+
)
|
|
124
|
+
except (socket.gaierror, socket.herror):
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
# Store vulnerabilities as findings
|
|
128
|
+
findings_added = 0
|
|
129
|
+
|
|
130
|
+
for vuln in parsed.get("vulnerabilities", []):
|
|
131
|
+
vuln_type = vuln.get("vuln_type", "unknown")
|
|
132
|
+
if vuln_type == "sqli" and vuln.get("injectable"):
|
|
133
|
+
severity = "critical"
|
|
134
|
+
finding_type = "sql_injection"
|
|
135
|
+
title = f"SQL Injection in parameter '{vuln['parameter']}'"
|
|
136
|
+
elif vuln_type == "xss":
|
|
137
|
+
severity = vuln.get("severity", "medium")
|
|
138
|
+
finding_type = "xss"
|
|
139
|
+
title = f"Possible XSS in parameter '{vuln['parameter']}'"
|
|
140
|
+
elif vuln_type == "file_inclusion":
|
|
141
|
+
severity = vuln.get("severity", "high")
|
|
142
|
+
finding_type = "file_inclusion"
|
|
143
|
+
title = (
|
|
144
|
+
f"Possible File Inclusion in parameter '{vuln['parameter']}'"
|
|
145
|
+
)
|
|
146
|
+
else:
|
|
147
|
+
severity = "medium"
|
|
148
|
+
finding_type = "web_vulnerability"
|
|
149
|
+
title = f"Vulnerability in parameter '{vuln['parameter']}'"
|
|
150
|
+
|
|
151
|
+
description = vuln.get("description", "")
|
|
152
|
+
if vuln.get("technique"):
|
|
153
|
+
description += f"\nTechnique: {vuln['technique']}"
|
|
154
|
+
if vuln.get("dbms"):
|
|
155
|
+
description += f"\nDBMS: {vuln['dbms']}"
|
|
156
|
+
|
|
157
|
+
findings_manager.add_finding(
|
|
158
|
+
engagement_id=engagement_id,
|
|
159
|
+
host_id=host_id,
|
|
160
|
+
port=target_port,
|
|
161
|
+
title=title,
|
|
162
|
+
finding_type=finding_type,
|
|
163
|
+
severity=severity,
|
|
164
|
+
description=description,
|
|
165
|
+
tool="sqlmap",
|
|
166
|
+
path=vuln.get("url"),
|
|
167
|
+
)
|
|
168
|
+
findings_added += 1
|
|
169
|
+
|
|
170
|
+
# Exploitation findings - Database Enumeration
|
|
171
|
+
databases = parsed.get("databases", [])
|
|
172
|
+
if databases and host_id:
|
|
173
|
+
dbms_info = parsed.get("dbms", "Unknown")
|
|
174
|
+
db_count = len(databases)
|
|
175
|
+
db_list = databases[:10]
|
|
176
|
+
db_list_str = ", ".join(db_list)
|
|
177
|
+
if len(databases) > 10:
|
|
178
|
+
db_list_str += f" ... and {len(databases) - 10} more"
|
|
179
|
+
|
|
180
|
+
description = f"SQL injection was successfully exploited to enumerate {db_count} database(s).\n\n"
|
|
181
|
+
description += f"DBMS: {dbms_info}\n"
|
|
182
|
+
description += f"Databases: {db_list_str}"
|
|
183
|
+
|
|
184
|
+
findings_manager.add_finding(
|
|
185
|
+
engagement_id=engagement_id,
|
|
186
|
+
host_id=host_id,
|
|
187
|
+
port=target_port,
|
|
188
|
+
title=f"SQL Injection Exploited - {db_count} Database(s) Enumerated",
|
|
189
|
+
finding_type="sql_injection_exploitation",
|
|
190
|
+
severity="critical",
|
|
191
|
+
description=description,
|
|
192
|
+
tool="sqlmap",
|
|
193
|
+
path=parsed.get("target_url"),
|
|
194
|
+
)
|
|
195
|
+
findings_added += 1
|
|
196
|
+
|
|
197
|
+
# Exploitation findings - Table Enumeration
|
|
198
|
+
tables = parsed.get("tables", {})
|
|
199
|
+
if tables and host_id:
|
|
200
|
+
total_tables = sum(len(table_list) for table_list in tables.values())
|
|
201
|
+
if total_tables > 0:
|
|
202
|
+
description = f"SQL injection was exploited to enumerate {total_tables} table(s) across {len(tables)} database(s)."
|
|
203
|
+
|
|
204
|
+
findings_manager.add_finding(
|
|
205
|
+
engagement_id=engagement_id,
|
|
206
|
+
host_id=host_id,
|
|
207
|
+
port=target_port,
|
|
208
|
+
title=f"SQL Injection Exploited - {total_tables} Table(s) Enumerated",
|
|
209
|
+
finding_type="sql_injection_exploitation",
|
|
210
|
+
severity="high",
|
|
211
|
+
description=description,
|
|
212
|
+
tool="sqlmap",
|
|
213
|
+
path=parsed.get("target_url"),
|
|
214
|
+
)
|
|
215
|
+
findings_added += 1
|
|
216
|
+
|
|
217
|
+
# Exploitation findings - Data Dump
|
|
218
|
+
dumped_data = parsed.get("dumped_data", {})
|
|
219
|
+
credentials_added = 0
|
|
220
|
+
extracted_credentials = [] # Default empty list for chaining
|
|
221
|
+
total_users_found = 0 # Total users in credential tables (including those without passwords)
|
|
222
|
+
all_users = (
|
|
223
|
+
[]
|
|
224
|
+
) # All users including those without passwords (for expanded view)
|
|
225
|
+
if dumped_data and host_id:
|
|
226
|
+
total_rows = sum(
|
|
227
|
+
d.get("row_count", len(d.get("rows", [])))
|
|
228
|
+
for d in dumped_data.values()
|
|
229
|
+
)
|
|
230
|
+
table_names = list(dumped_data.keys())
|
|
231
|
+
|
|
232
|
+
if total_rows > 0:
|
|
233
|
+
description = f"SQL injection was exploited to dump {total_rows} row(s) from {len(table_names)} table(s).\n\n"
|
|
234
|
+
description += f"Tables dumped: {', '.join(table_names[:5])}"
|
|
235
|
+
if len(table_names) > 5:
|
|
236
|
+
description += f" ... and {len(table_names) - 5} more"
|
|
237
|
+
|
|
238
|
+
findings_manager.add_finding(
|
|
239
|
+
engagement_id=engagement_id,
|
|
240
|
+
host_id=host_id,
|
|
241
|
+
port=target_port,
|
|
242
|
+
title=f"SQL Injection Exploited - {total_rows} Row(s) Dumped",
|
|
243
|
+
finding_type="sql_injection_exploitation",
|
|
244
|
+
severity="critical",
|
|
245
|
+
description=description,
|
|
246
|
+
tool="sqlmap",
|
|
247
|
+
path=parsed.get("target_url"),
|
|
248
|
+
)
|
|
249
|
+
findings_added += 1
|
|
250
|
+
|
|
251
|
+
# Extract credentials from dumped data
|
|
252
|
+
(
|
|
253
|
+
credentials_added,
|
|
254
|
+
extracted_credentials,
|
|
255
|
+
total_users_found,
|
|
256
|
+
all_users,
|
|
257
|
+
) = self._extract_credentials_from_dump(
|
|
258
|
+
dumped_data=dumped_data,
|
|
259
|
+
engagement_id=engagement_id,
|
|
260
|
+
host_id=host_id,
|
|
261
|
+
credentials_manager=credentials_manager,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
if credentials_added > 0:
|
|
265
|
+
logger.info(
|
|
266
|
+
f"SQLMap: Extracted {credentials_added} credentials from dumped tables"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Store SQLMap database discoveries to SQLMapDataManager
|
|
270
|
+
has_data_to_store = (
|
|
271
|
+
parsed.get("databases")
|
|
272
|
+
or parsed.get("tables")
|
|
273
|
+
or parsed.get("dumped_data")
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
if host_id and has_data_to_store:
|
|
277
|
+
sdm = SQLMapDataManager()
|
|
278
|
+
dbms_type = parsed.get("dbms", "Unknown")
|
|
279
|
+
|
|
280
|
+
# Store databases
|
|
281
|
+
db_ids = {}
|
|
282
|
+
for db_name in parsed.get("databases", []):
|
|
283
|
+
db_id = sdm.add_database(engagement_id, host_id, db_name, dbms_type)
|
|
284
|
+
if db_id:
|
|
285
|
+
db_ids[db_name] = db_id
|
|
286
|
+
|
|
287
|
+
# Store tables
|
|
288
|
+
table_ids = {}
|
|
289
|
+
for db_table_key, table_list in parsed.get("tables", {}).items():
|
|
290
|
+
for table_name in table_list:
|
|
291
|
+
db_id = db_ids.get(db_table_key)
|
|
292
|
+
if not db_id and db_ids:
|
|
293
|
+
db_id = list(db_ids.values())[0]
|
|
294
|
+
|
|
295
|
+
if db_id:
|
|
296
|
+
table_id = sdm.add_table(db_id, table_name)
|
|
297
|
+
if table_id:
|
|
298
|
+
full_key = f"{db_table_key}.{table_name}"
|
|
299
|
+
table_ids[full_key] = table_id
|
|
300
|
+
|
|
301
|
+
# Store columns
|
|
302
|
+
for table_key, column_list in parsed.get("columns", {}).items():
|
|
303
|
+
table_id = table_ids.get(table_key)
|
|
304
|
+
if table_id:
|
|
305
|
+
columns = [{"name": col} for col in column_list]
|
|
306
|
+
sdm.add_columns(table_id, columns)
|
|
307
|
+
|
|
308
|
+
# Store dumped data
|
|
309
|
+
for data_key, dump_info in parsed.get("dumped_data", {}).items():
|
|
310
|
+
table_id = table_ids.get(data_key)
|
|
311
|
+
|
|
312
|
+
if not table_id and "." in data_key:
|
|
313
|
+
db_name, table_name = data_key.rsplit(".", 1)
|
|
314
|
+
|
|
315
|
+
db_id = db_ids.get(db_name)
|
|
316
|
+
if not db_id:
|
|
317
|
+
db_id = sdm.add_database(
|
|
318
|
+
engagement_id, host_id, db_name, dbms_type
|
|
319
|
+
)
|
|
320
|
+
if db_id:
|
|
321
|
+
db_ids[db_name] = db_id
|
|
322
|
+
|
|
323
|
+
if db_id:
|
|
324
|
+
row_count = dump_info.get(
|
|
325
|
+
"row_count", len(dump_info.get("rows", []))
|
|
326
|
+
)
|
|
327
|
+
table_id = sdm.add_table(db_id, table_name, row_count)
|
|
328
|
+
if table_id:
|
|
329
|
+
table_ids[data_key] = table_id
|
|
330
|
+
|
|
331
|
+
if dump_info.get("columns"):
|
|
332
|
+
columns = [
|
|
333
|
+
{"name": col} for col in dump_info["columns"]
|
|
334
|
+
]
|
|
335
|
+
sdm.add_columns(table_id, columns)
|
|
336
|
+
|
|
337
|
+
if table_id:
|
|
338
|
+
sdm.add_dumped_data(
|
|
339
|
+
table_id,
|
|
340
|
+
dump_info.get("rows", []),
|
|
341
|
+
dump_info.get("csv_path"),
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
# Check for sqlmap errors
|
|
345
|
+
sqlmap_error = detect_tool_error(output, "sqlmap")
|
|
346
|
+
|
|
347
|
+
# Determine status
|
|
348
|
+
if sqlmap_error:
|
|
349
|
+
status = STATUS_ERROR
|
|
350
|
+
elif stats["total_vulns"] > 0 or stats["databases_found"] > 0:
|
|
351
|
+
status = STATUS_DONE
|
|
352
|
+
else:
|
|
353
|
+
status = STATUS_NO_RESULTS
|
|
354
|
+
|
|
355
|
+
# Build summary for job queue display
|
|
356
|
+
summary_parts = []
|
|
357
|
+
if stats["total_vulns"] > 0:
|
|
358
|
+
summary_parts.append(f"{stats['total_vulns']} SQLi vuln(s)")
|
|
359
|
+
if stats["databases_found"] > 0:
|
|
360
|
+
summary_parts.append(f"{stats['databases_found']} DB(s)")
|
|
361
|
+
tables_count = sum(len(t) for t in tables.values()) if tables else 0
|
|
362
|
+
if tables_count > 0:
|
|
363
|
+
summary_parts.append(f"{tables_count} table(s)")
|
|
364
|
+
dumped_tables = stats.get("dumped_tables", 0)
|
|
365
|
+
dumped_rows = stats.get("dumped_rows", 0)
|
|
366
|
+
if dumped_rows > 0:
|
|
367
|
+
summary_parts.append(f"{dumped_rows} row(s) dumped")
|
|
368
|
+
if credentials_added > 0:
|
|
369
|
+
if total_users_found > 0 and total_users_found != credentials_added:
|
|
370
|
+
summary_parts.append(
|
|
371
|
+
f"{credentials_added}/{total_users_found} creds"
|
|
372
|
+
)
|
|
373
|
+
else:
|
|
374
|
+
summary_parts.append(f"{credentials_added} credential(s)")
|
|
375
|
+
summary = " | ".join(summary_parts) if summary_parts else "No findings"
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
"tool": "sqlmap",
|
|
379
|
+
"status": status,
|
|
380
|
+
"summary": summary,
|
|
381
|
+
"target": target,
|
|
382
|
+
"target_url": parsed.get("target_url"),
|
|
383
|
+
"dbms": parsed.get("dbms"),
|
|
384
|
+
"total_vulns": stats["total_vulns"],
|
|
385
|
+
"sqli_confirmed": stats["sqli_confirmed"],
|
|
386
|
+
"databases_found": stats["databases_found"],
|
|
387
|
+
"tables_found": sum(len(t) for t in tables.values()) if tables else 0,
|
|
388
|
+
"findings_added": findings_added,
|
|
389
|
+
# Additional stats from parser
|
|
390
|
+
"xss_possible": stats.get("xss_possible", 0),
|
|
391
|
+
"fi_possible": stats.get("fi_possible", 0),
|
|
392
|
+
"urls_tested": stats.get("urls_tested", 0),
|
|
393
|
+
"databases": parsed.get("databases", []),
|
|
394
|
+
"tables": parsed.get("tables", {}),
|
|
395
|
+
"columns": parsed.get("columns", {}),
|
|
396
|
+
"dumped_tables": stats.get("dumped_tables", 0),
|
|
397
|
+
"dumped_rows": stats.get("dumped_rows", 0),
|
|
398
|
+
"dumped_data": parsed.get("dumped_data", {}),
|
|
399
|
+
# CRITICAL: Chaining flags for auto-chain rules
|
|
400
|
+
"sql_injection_confirmed": parsed.get("sql_injection_confirmed", False),
|
|
401
|
+
"injectable_parameter": parsed.get("injectable_parameter", ""),
|
|
402
|
+
"injectable_url": parsed.get("injectable_url", target),
|
|
403
|
+
"injectable_post_data": parsed.get("injectable_post_data", ""),
|
|
404
|
+
"injectable_method": parsed.get("injectable_method", "GET"),
|
|
405
|
+
"all_injection_points": parsed.get("all_injection_points", []),
|
|
406
|
+
"databases_enumerated": len(parsed.get("databases", [])) > 0,
|
|
407
|
+
"tables_enumerated": len(parsed.get("tables", {})) > 0,
|
|
408
|
+
"columns_enumerated": len(parsed.get("columns", {})) > 0,
|
|
409
|
+
# Post-exploitation flags for advanced chaining
|
|
410
|
+
"is_dba": parsed.get("is_dba", False),
|
|
411
|
+
"privileges": parsed.get("privileges", []),
|
|
412
|
+
"current_user": parsed.get("current_user"),
|
|
413
|
+
"file_read_success": parsed.get("file_read_success", False),
|
|
414
|
+
"os_command_success": parsed.get("os_command_success", False),
|
|
415
|
+
# Credentials flag for cross-tool chaining
|
|
416
|
+
"credentials_dumped": credentials_added > 0,
|
|
417
|
+
"credentials_count": credentials_added,
|
|
418
|
+
"total_users_count": total_users_found, # All users found (including those without passwords)
|
|
419
|
+
"credentials": extracted_credentials, # For direct chaining without DB lookup
|
|
420
|
+
"all_users": all_users, # All users including those without passwords (for expanded view)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
except Exception as e:
|
|
424
|
+
logger.error(f"Error parsing sqlmap job: {e}")
|
|
425
|
+
return {"error": str(e)}
|
|
426
|
+
|
|
427
|
+
def _extract_credentials_from_dump(
|
|
428
|
+
self,
|
|
429
|
+
dumped_data: Dict[str, Any],
|
|
430
|
+
engagement_id: int,
|
|
431
|
+
host_id: int,
|
|
432
|
+
credentials_manager: Any,
|
|
433
|
+
) -> tuple:
|
|
434
|
+
"""
|
|
435
|
+
Extract credentials from SQLMap dumped data.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
dumped_data: Dict of {table_key: {rows, columns, row_count, csv_path}}
|
|
439
|
+
engagement_id: Engagement ID
|
|
440
|
+
host_id: Host ID
|
|
441
|
+
credentials_manager: CredentialsManager instance
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
tuple: (count of credentials added, list of credential dicts, total users count)
|
|
445
|
+
"""
|
|
446
|
+
from souleyez.intelligence.sensitive_tables import is_sensitive_table
|
|
447
|
+
|
|
448
|
+
credentials_added = 0
|
|
449
|
+
credentials_list = []
|
|
450
|
+
all_users_list = [] # All users including those without passwords
|
|
451
|
+
total_users = 0 # Track all users, not just those with passwords
|
|
452
|
+
|
|
453
|
+
for table_key, data_info in dumped_data.items():
|
|
454
|
+
# Parse table name
|
|
455
|
+
parts = table_key.split(".")
|
|
456
|
+
table_name = parts[-1]
|
|
457
|
+
|
|
458
|
+
# Check if this is a credentials table
|
|
459
|
+
is_sensitive, category, priority = is_sensitive_table(table_name)
|
|
460
|
+
|
|
461
|
+
if category != "credentials":
|
|
462
|
+
continue # Only extract from credential tables
|
|
463
|
+
|
|
464
|
+
rows = data_info.get("rows", [])
|
|
465
|
+
columns = data_info.get("columns", [])
|
|
466
|
+
|
|
467
|
+
if not rows or not columns:
|
|
468
|
+
continue
|
|
469
|
+
|
|
470
|
+
# Find username and password columns
|
|
471
|
+
username_col = None
|
|
472
|
+
password_col = None
|
|
473
|
+
|
|
474
|
+
username_patterns = [
|
|
475
|
+
"username",
|
|
476
|
+
"uname",
|
|
477
|
+
"user_name",
|
|
478
|
+
"login",
|
|
479
|
+
"account",
|
|
480
|
+
"email",
|
|
481
|
+
"user",
|
|
482
|
+
]
|
|
483
|
+
password_patterns = [
|
|
484
|
+
"password",
|
|
485
|
+
"passwd",
|
|
486
|
+
"pass",
|
|
487
|
+
"pwd",
|
|
488
|
+
"hash",
|
|
489
|
+
"pwd_hash",
|
|
490
|
+
"password_hash",
|
|
491
|
+
]
|
|
492
|
+
id_columns = ["id", "user_id", "userid", "account_id", "user_fk"]
|
|
493
|
+
|
|
494
|
+
# Try EXACT matches first for username
|
|
495
|
+
for pattern in username_patterns:
|
|
496
|
+
for col in columns:
|
|
497
|
+
if col.lower() == pattern and col.lower() not in id_columns:
|
|
498
|
+
username_col = col
|
|
499
|
+
break
|
|
500
|
+
if username_col:
|
|
501
|
+
break
|
|
502
|
+
|
|
503
|
+
# Fall back to SUBSTRING matches for username
|
|
504
|
+
if not username_col:
|
|
505
|
+
for pattern in username_patterns:
|
|
506
|
+
for col in columns:
|
|
507
|
+
if pattern in col.lower() and col.lower() not in id_columns:
|
|
508
|
+
username_col = col
|
|
509
|
+
break
|
|
510
|
+
if username_col:
|
|
511
|
+
break
|
|
512
|
+
|
|
513
|
+
# Try EXACT matches first for password
|
|
514
|
+
for pattern in password_patterns:
|
|
515
|
+
for col in columns:
|
|
516
|
+
if col.lower() == pattern and col.lower() not in id_columns:
|
|
517
|
+
password_col = col
|
|
518
|
+
break
|
|
519
|
+
if password_col:
|
|
520
|
+
break
|
|
521
|
+
|
|
522
|
+
# Fall back to SUBSTRING matches for password
|
|
523
|
+
if not password_col:
|
|
524
|
+
for pattern in password_patterns:
|
|
525
|
+
for col in columns:
|
|
526
|
+
if pattern in col.lower() and col.lower() not in id_columns:
|
|
527
|
+
password_col = col
|
|
528
|
+
break
|
|
529
|
+
if password_col:
|
|
530
|
+
break
|
|
531
|
+
|
|
532
|
+
if not username_col or not password_col:
|
|
533
|
+
logger.debug(f"Skipping {table_key}: missing username/password columns")
|
|
534
|
+
continue
|
|
535
|
+
|
|
536
|
+
# Extract credentials from rows
|
|
537
|
+
for row in rows:
|
|
538
|
+
username = str(row.get(username_col, "")).strip()
|
|
539
|
+
password = str(row.get(password_col, "")).strip()
|
|
540
|
+
|
|
541
|
+
# Handle SQLMap's <blank> placeholder for NULL/empty values
|
|
542
|
+
if username in ["<blank>", "NULL", "None", ""]:
|
|
543
|
+
email = str(row.get("email", "")).strip()
|
|
544
|
+
if email and email not in ["<blank>", "NULL", "None", ""]:
|
|
545
|
+
username = email
|
|
546
|
+
else:
|
|
547
|
+
continue
|
|
548
|
+
|
|
549
|
+
# Validate username is not scanner garbage / injection payload
|
|
550
|
+
if not self._is_valid_username(username):
|
|
551
|
+
logger.debug(
|
|
552
|
+
f"Skipping invalid username (scanner artifact): {username[:50]}..."
|
|
553
|
+
)
|
|
554
|
+
continue
|
|
555
|
+
|
|
556
|
+
# Count this as a valid user (has username/email)
|
|
557
|
+
total_users += 1
|
|
558
|
+
|
|
559
|
+
# Check if user has a password
|
|
560
|
+
has_password = password and password not in [
|
|
561
|
+
"NULL",
|
|
562
|
+
"None",
|
|
563
|
+
"",
|
|
564
|
+
"<blank>",
|
|
565
|
+
]
|
|
566
|
+
|
|
567
|
+
# Add to all users list (for expanded view)
|
|
568
|
+
all_users_list.append(
|
|
569
|
+
{
|
|
570
|
+
"username": username,
|
|
571
|
+
"password": password if has_password else None,
|
|
572
|
+
"has_password": has_password,
|
|
573
|
+
}
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
# Skip users without passwords for credential cracking
|
|
577
|
+
if not has_password:
|
|
578
|
+
continue
|
|
579
|
+
|
|
580
|
+
# Detect hash type
|
|
581
|
+
credential_type = self._detect_hash_type(password)
|
|
582
|
+
|
|
583
|
+
# Add to credentials database
|
|
584
|
+
try:
|
|
585
|
+
credentials_manager.add_credential(
|
|
586
|
+
engagement_id=engagement_id,
|
|
587
|
+
host_id=host_id,
|
|
588
|
+
username=username,
|
|
589
|
+
password=password,
|
|
590
|
+
credential_type=credential_type,
|
|
591
|
+
tool="sqlmap",
|
|
592
|
+
service="web",
|
|
593
|
+
)
|
|
594
|
+
credentials_added += 1
|
|
595
|
+
# Add to list for chaining and 🔓 indicator
|
|
596
|
+
credentials_list.append(
|
|
597
|
+
{
|
|
598
|
+
"username": username,
|
|
599
|
+
"password": password,
|
|
600
|
+
"credential_type": credential_type,
|
|
601
|
+
}
|
|
602
|
+
)
|
|
603
|
+
except Exception as e:
|
|
604
|
+
logger.warning(f"Failed to add credential {username}: {e}")
|
|
605
|
+
|
|
606
|
+
return credentials_added, credentials_list, total_users, all_users_list
|
|
607
|
+
|
|
608
|
+
def _is_valid_username(self, username: str) -> bool:
|
|
609
|
+
"""
|
|
610
|
+
Validate that a username is legitimate and not scanner garbage.
|
|
611
|
+
|
|
612
|
+
Rejects:
|
|
613
|
+
- Injection payloads (netsparker, burp, etc.)
|
|
614
|
+
- Scanner artifacts ({{, ${, %27, etc.)
|
|
615
|
+
- Path patterns (/etc/passwd, .asp, .axd, etc.)
|
|
616
|
+
- Command injection attempts (ping, whoami, etc.)
|
|
617
|
+
- Overly long values (>100 chars)
|
|
618
|
+
|
|
619
|
+
Args:
|
|
620
|
+
username: Username string to validate
|
|
621
|
+
|
|
622
|
+
Returns:
|
|
623
|
+
bool: True if username appears legitimate
|
|
624
|
+
"""
|
|
625
|
+
if not username or len(username) > 100:
|
|
626
|
+
return False
|
|
627
|
+
|
|
628
|
+
username_lower = username.lower()
|
|
629
|
+
|
|
630
|
+
# Injection tool signatures
|
|
631
|
+
scanner_patterns = [
|
|
632
|
+
"netsparker",
|
|
633
|
+
"burpsuite",
|
|
634
|
+
"burp",
|
|
635
|
+
"acunetix",
|
|
636
|
+
"nikto",
|
|
637
|
+
"sqlmap",
|
|
638
|
+
"havij",
|
|
639
|
+
"w3af",
|
|
640
|
+
"owasp",
|
|
641
|
+
"zap",
|
|
642
|
+
"wvs",
|
|
643
|
+
]
|
|
644
|
+
for pattern in scanner_patterns:
|
|
645
|
+
if pattern in username_lower:
|
|
646
|
+
return False
|
|
647
|
+
|
|
648
|
+
# Template injection / expression patterns
|
|
649
|
+
injection_patterns = [
|
|
650
|
+
"{{",
|
|
651
|
+
"}}",
|
|
652
|
+
"${",
|
|
653
|
+
"}$",
|
|
654
|
+
"<%",
|
|
655
|
+
"%>",
|
|
656
|
+
"{%",
|
|
657
|
+
"%}",
|
|
658
|
+
"${7*7}",
|
|
659
|
+
"{{7*7}}",
|
|
660
|
+
"sleep(",
|
|
661
|
+
"benchmark(",
|
|
662
|
+
"waitfor delay",
|
|
663
|
+
"pg_sleep",
|
|
664
|
+
]
|
|
665
|
+
for pattern in injection_patterns:
|
|
666
|
+
if pattern in username_lower:
|
|
667
|
+
return False
|
|
668
|
+
|
|
669
|
+
# Path traversal / file patterns
|
|
670
|
+
path_patterns = [
|
|
671
|
+
"/etc/",
|
|
672
|
+
"\\etc\\",
|
|
673
|
+
"/passwd",
|
|
674
|
+
"/shadow",
|
|
675
|
+
"/windows/",
|
|
676
|
+
"c:\\",
|
|
677
|
+
".asp",
|
|
678
|
+
".aspx",
|
|
679
|
+
".axd",
|
|
680
|
+
".php",
|
|
681
|
+
".jsp",
|
|
682
|
+
".pl",
|
|
683
|
+
"../",
|
|
684
|
+
"..\\",
|
|
685
|
+
"file://",
|
|
686
|
+
"php://",
|
|
687
|
+
"data://",
|
|
688
|
+
"::1/",
|
|
689
|
+
"[::1]",
|
|
690
|
+
"/elmah",
|
|
691
|
+
"/trace",
|
|
692
|
+
"127.0.0.1/",
|
|
693
|
+
]
|
|
694
|
+
for pattern in path_patterns:
|
|
695
|
+
if pattern in username_lower:
|
|
696
|
+
return False
|
|
697
|
+
|
|
698
|
+
# Command injection patterns
|
|
699
|
+
cmd_patterns = [
|
|
700
|
+
"& ping ",
|
|
701
|
+
"| ping ",
|
|
702
|
+
"; ping ",
|
|
703
|
+
"ping -",
|
|
704
|
+
"& whoami",
|
|
705
|
+
"| whoami",
|
|
706
|
+
"; whoami",
|
|
707
|
+
"`whoami`",
|
|
708
|
+
"$(whoami)",
|
|
709
|
+
"cmd.exe",
|
|
710
|
+
"/bin/sh",
|
|
711
|
+
"& dir",
|
|
712
|
+
"| dir",
|
|
713
|
+
"; dir",
|
|
714
|
+
"& ls",
|
|
715
|
+
"| ls",
|
|
716
|
+
"; ls",
|
|
717
|
+
"nc -",
|
|
718
|
+
"ncat ",
|
|
719
|
+
"netcat ",
|
|
720
|
+
]
|
|
721
|
+
for pattern in cmd_patterns:
|
|
722
|
+
if pattern in username_lower:
|
|
723
|
+
return False
|
|
724
|
+
|
|
725
|
+
# SQL injection patterns
|
|
726
|
+
sql_patterns = [
|
|
727
|
+
"' or ",
|
|
728
|
+
"' and ",
|
|
729
|
+
"1=1",
|
|
730
|
+
"1'='1",
|
|
731
|
+
"' union ",
|
|
732
|
+
"select ",
|
|
733
|
+
"insert ",
|
|
734
|
+
"update ",
|
|
735
|
+
"delete ",
|
|
736
|
+
"drop ",
|
|
737
|
+
"concat(",
|
|
738
|
+
"char(",
|
|
739
|
+
"chr(",
|
|
740
|
+
"0x00",
|
|
741
|
+
"@@version",
|
|
742
|
+
]
|
|
743
|
+
for pattern in sql_patterns:
|
|
744
|
+
if pattern in username_lower:
|
|
745
|
+
return False
|
|
746
|
+
|
|
747
|
+
# URL encoding patterns that indicate payloads
|
|
748
|
+
if "%27" in username or "%22" in username or "%3c" in username_lower:
|
|
749
|
+
return False
|
|
750
|
+
|
|
751
|
+
# Hex patterns (0x...) that indicate payloads
|
|
752
|
+
if re.search(r"0x[0-9a-f]{4,}", username_lower):
|
|
753
|
+
return False
|
|
754
|
+
|
|
755
|
+
# If it starts/ends with special injection chars, reject
|
|
756
|
+
injection_chars = ['"', "'", ";", "|", "&", "`", "(", ")", "<", ">"]
|
|
757
|
+
if username[0] in injection_chars or username[-1] in injection_chars:
|
|
758
|
+
return False
|
|
759
|
+
|
|
760
|
+
# Too many special characters (normal usernames don't have many)
|
|
761
|
+
special_count = sum(1 for c in username if c in "{}[]()$%^&*|\\/<>\"`'")
|
|
762
|
+
if special_count > 3:
|
|
763
|
+
return False
|
|
764
|
+
|
|
765
|
+
# If mostly digits/hex and long, likely a payload
|
|
766
|
+
if len(username) > 20:
|
|
767
|
+
alnum_only = re.sub(r"[^a-zA-Z0-9]", "", username)
|
|
768
|
+
if len(alnum_only) > 0:
|
|
769
|
+
digit_ratio = sum(1 for c in alnum_only if c.isdigit()) / len(
|
|
770
|
+
alnum_only
|
|
771
|
+
)
|
|
772
|
+
if digit_ratio > 0.7:
|
|
773
|
+
return False
|
|
774
|
+
|
|
775
|
+
return True
|
|
776
|
+
|
|
777
|
+
def _detect_hash_type(self, password: str) -> str:
|
|
778
|
+
"""
|
|
779
|
+
Detect hash type from password string.
|
|
780
|
+
|
|
781
|
+
Args:
|
|
782
|
+
password: Password or hash string
|
|
783
|
+
|
|
784
|
+
Returns:
|
|
785
|
+
str: Credential type ('password', 'hash:bcrypt', 'hash:md5', etc.)
|
|
786
|
+
"""
|
|
787
|
+
# Check for hash prefixes
|
|
788
|
+
if (
|
|
789
|
+
password.startswith("$2y$")
|
|
790
|
+
or password.startswith("$2a$")
|
|
791
|
+
or password.startswith("$2b$")
|
|
792
|
+
):
|
|
793
|
+
return "hash:bcrypt"
|
|
794
|
+
elif password.startswith("$1$"):
|
|
795
|
+
return "hash:md5crypt"
|
|
796
|
+
elif password.startswith("$5$"):
|
|
797
|
+
return "hash:sha256crypt"
|
|
798
|
+
elif password.startswith("$6$"):
|
|
799
|
+
return "hash:sha512crypt"
|
|
800
|
+
elif password.startswith("$apr1$"):
|
|
801
|
+
return "hash:apr1"
|
|
802
|
+
elif password.startswith("{SHA}") or password.startswith("{SSHA}"):
|
|
803
|
+
return "hash:ldap"
|
|
804
|
+
|
|
805
|
+
# Check hex patterns
|
|
806
|
+
if re.match(r"^[a-fA-F0-9]{32}$", password):
|
|
807
|
+
return "hash:md5"
|
|
808
|
+
elif re.match(r"^[a-fA-F0-9]{40}$", password):
|
|
809
|
+
return "hash:sha1"
|
|
810
|
+
elif re.match(r"^[a-fA-F0-9]{64}$", password):
|
|
811
|
+
return "hash:sha256"
|
|
812
|
+
elif re.match(r"^[a-fA-F0-9]{128}$", password):
|
|
813
|
+
return "hash:sha512"
|
|
814
|
+
|
|
815
|
+
# Check for NTLM hash
|
|
816
|
+
if re.match(r"^[a-fA-F0-9]{32}:[a-fA-F0-9]{32}$", password):
|
|
817
|
+
return "hash:ntlm"
|
|
818
|
+
|
|
819
|
+
# Likely plaintext if short and no special patterns
|
|
820
|
+
if len(password) < 20 and not re.search(r"[\$\{\}]", password):
|
|
821
|
+
return "password"
|
|
822
|
+
|
|
823
|
+
# Default to generic hash
|
|
824
|
+
return "hash"
|
|
825
|
+
|
|
826
|
+
def display_done(
|
|
827
|
+
self,
|
|
828
|
+
job: Dict[str, Any],
|
|
829
|
+
log_path: str,
|
|
830
|
+
show_all: bool = False,
|
|
831
|
+
show_passwords: bool = False,
|
|
832
|
+
) -> None:
|
|
833
|
+
"""Display successful SQLMap results."""
|
|
834
|
+
try:
|
|
835
|
+
from souleyez.parsers.sqlmap_parser import (
|
|
836
|
+
parse_sqlmap_output,
|
|
837
|
+
get_sqli_stats,
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
if not log_path or not os.path.exists(log_path):
|
|
841
|
+
return
|
|
842
|
+
|
|
843
|
+
with open(log_path, "r", encoding="utf-8", errors="replace") as f:
|
|
844
|
+
log_content = f.read()
|
|
845
|
+
parsed = parse_sqlmap_output(log_content, job.get("target", ""))
|
|
846
|
+
stats = get_sqli_stats(parsed)
|
|
847
|
+
|
|
848
|
+
# Header
|
|
849
|
+
click.echo(click.style("=" * 70, fg="cyan"))
|
|
850
|
+
click.echo(click.style("SQL INJECTION SCAN", bold=True, fg="cyan"))
|
|
851
|
+
click.echo(click.style("=" * 70, fg="cyan"))
|
|
852
|
+
click.echo()
|
|
853
|
+
|
|
854
|
+
click.echo(
|
|
855
|
+
click.style(f"Target: {job.get('target', 'unknown')}", bold=True)
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
if stats["total_vulns"] == 0 and stats["databases_found"] == 0:
|
|
859
|
+
self.display_no_results(job, log_path)
|
|
860
|
+
return
|
|
861
|
+
|
|
862
|
+
click.echo(
|
|
863
|
+
click.style(
|
|
864
|
+
f"Result: {stats['total_vulns']} vulnerability(ies) found",
|
|
865
|
+
fg="red",
|
|
866
|
+
bold=True,
|
|
867
|
+
)
|
|
868
|
+
)
|
|
869
|
+
click.echo()
|
|
870
|
+
|
|
871
|
+
# Injection details with techniques and payloads
|
|
872
|
+
if parsed.get("injection_techniques"):
|
|
873
|
+
for inj in parsed["injection_techniques"]:
|
|
874
|
+
click.echo(
|
|
875
|
+
click.style(
|
|
876
|
+
f"[+] SQL Injection: {inj['parameter']} ({inj['method']})",
|
|
877
|
+
fg="red",
|
|
878
|
+
bold=True,
|
|
879
|
+
)
|
|
880
|
+
)
|
|
881
|
+
click.echo()
|
|
882
|
+
|
|
883
|
+
tech_limit = len(inj["techniques"]) if show_all else 4
|
|
884
|
+
click.echo(
|
|
885
|
+
click.style(
|
|
886
|
+
f" Injection Techniques Found: {len(inj['techniques'])}",
|
|
887
|
+
bold=True,
|
|
888
|
+
)
|
|
889
|
+
)
|
|
890
|
+
for tech in inj["techniques"][:tech_limit]:
|
|
891
|
+
click.echo(click.style(f" - {tech['type']}", fg="yellow"))
|
|
892
|
+
if tech.get("title"):
|
|
893
|
+
click.echo(f" Title: {tech['title']}")
|
|
894
|
+
if tech.get("payload"):
|
|
895
|
+
payload = tech["payload"]
|
|
896
|
+
if not show_all and len(payload) > 80:
|
|
897
|
+
payload = payload[:77] + "..."
|
|
898
|
+
click.echo(f" Payload: {payload}")
|
|
899
|
+
click.echo()
|
|
900
|
+
|
|
901
|
+
if not show_all and len(inj["techniques"]) > 4:
|
|
902
|
+
click.echo(
|
|
903
|
+
f" ... and {len(inj['techniques']) - 4} more techniques"
|
|
904
|
+
)
|
|
905
|
+
click.echo()
|
|
906
|
+
elif stats["sqli_confirmed"] > 0:
|
|
907
|
+
click.echo(
|
|
908
|
+
click.style(
|
|
909
|
+
f"[+] SQL Injection Found: {stats['sqli_confirmed']} parameter(s)",
|
|
910
|
+
fg="red",
|
|
911
|
+
bold=True,
|
|
912
|
+
)
|
|
913
|
+
)
|
|
914
|
+
click.echo()
|
|
915
|
+
|
|
916
|
+
# XSS and File Inclusion warnings
|
|
917
|
+
if stats.get("xss_possible", 0) > 0:
|
|
918
|
+
click.echo(
|
|
919
|
+
click.style(
|
|
920
|
+
f"[!] Possible XSS: {stats['xss_possible']} parameter(s)",
|
|
921
|
+
fg="yellow",
|
|
922
|
+
)
|
|
923
|
+
)
|
|
924
|
+
if stats.get("fi_possible", 0) > 0:
|
|
925
|
+
click.echo(
|
|
926
|
+
click.style(
|
|
927
|
+
f"[!] Possible File Inclusion: {stats['fi_possible']} parameter(s)",
|
|
928
|
+
fg="yellow",
|
|
929
|
+
)
|
|
930
|
+
)
|
|
931
|
+
|
|
932
|
+
if stats.get("xss_possible", 0) > 0 or stats.get("fi_possible", 0) > 0:
|
|
933
|
+
click.echo()
|
|
934
|
+
|
|
935
|
+
# Web stack information
|
|
936
|
+
if parsed.get("web_server_os"):
|
|
937
|
+
click.echo(
|
|
938
|
+
click.style("Web Server OS: ", bold=True) + parsed["web_server_os"]
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
if parsed.get("web_app_technology"):
|
|
942
|
+
click.echo(
|
|
943
|
+
click.style("Web Technology: ", bold=True)
|
|
944
|
+
+ ", ".join(parsed["web_app_technology"])
|
|
945
|
+
)
|
|
946
|
+
|
|
947
|
+
if parsed.get("dbms"):
|
|
948
|
+
click.echo(click.style("Database: ", bold=True) + parsed["dbms"])
|
|
949
|
+
|
|
950
|
+
if (
|
|
951
|
+
parsed.get("web_server_os")
|
|
952
|
+
or parsed.get("web_app_technology")
|
|
953
|
+
or parsed.get("dbms")
|
|
954
|
+
):
|
|
955
|
+
click.echo()
|
|
956
|
+
|
|
957
|
+
# Databases enumerated
|
|
958
|
+
if parsed.get("databases"):
|
|
959
|
+
click.echo(
|
|
960
|
+
click.style(
|
|
961
|
+
f"Databases Enumerated ({len(parsed['databases'])}):",
|
|
962
|
+
bold=True,
|
|
963
|
+
fg="green",
|
|
964
|
+
)
|
|
965
|
+
)
|
|
966
|
+
max_dbs = None if show_all else 10
|
|
967
|
+
display_dbs = (
|
|
968
|
+
parsed["databases"]
|
|
969
|
+
if max_dbs is None
|
|
970
|
+
else parsed["databases"][:max_dbs]
|
|
971
|
+
)
|
|
972
|
+
for db in display_dbs:
|
|
973
|
+
click.echo(f" - {db}")
|
|
974
|
+
if max_dbs and len(parsed["databases"]) > max_dbs:
|
|
975
|
+
click.echo(f" ... and {len(parsed['databases']) - max_dbs} more")
|
|
976
|
+
click.echo()
|
|
977
|
+
|
|
978
|
+
# Tables enumerated
|
|
979
|
+
if parsed.get("tables"):
|
|
980
|
+
total_tables = sum(len(tables) for tables in parsed["tables"].values())
|
|
981
|
+
click.echo(
|
|
982
|
+
click.style(
|
|
983
|
+
f"Tables Enumerated ({total_tables}):", bold=True, fg="green"
|
|
984
|
+
)
|
|
985
|
+
)
|
|
986
|
+
max_tables = None if show_all else 15
|
|
987
|
+
for db_name, tables in parsed["tables"].items():
|
|
988
|
+
if len(parsed["tables"]) > 1:
|
|
989
|
+
click.echo(f" [{db_name}]")
|
|
990
|
+
display_tables = tables if show_all else tables[:15]
|
|
991
|
+
for table in display_tables:
|
|
992
|
+
click.echo(f" - {table}")
|
|
993
|
+
if not show_all and len(tables) > 15:
|
|
994
|
+
click.echo(f" ... and {len(tables) - 15} more")
|
|
995
|
+
click.echo()
|
|
996
|
+
|
|
997
|
+
# Dumped data
|
|
998
|
+
if parsed.get("dumped_data"):
|
|
999
|
+
click.echo(click.style("Data Dumped:", bold=True, fg="red"))
|
|
1000
|
+
for table_key, data in parsed["dumped_data"].items():
|
|
1001
|
+
row_count = data.get("row_count", len(data.get("rows", [])))
|
|
1002
|
+
columns = data.get("columns", [])
|
|
1003
|
+
click.echo(f" - {table_key}: {row_count} row(s)")
|
|
1004
|
+
if columns:
|
|
1005
|
+
col_limit = None if show_all else 8
|
|
1006
|
+
display_cols = columns if show_all else columns[:col_limit]
|
|
1007
|
+
click.echo(f" Columns: {', '.join(display_cols)}")
|
|
1008
|
+
if not show_all and len(columns) > 8:
|
|
1009
|
+
click.echo(f" ... and {len(columns) - 8} more")
|
|
1010
|
+
# Skip raw data rows - shown cleaner in PARSED RESULTS
|
|
1011
|
+
click.echo()
|
|
1012
|
+
|
|
1013
|
+
click.echo(click.style("=" * 70, fg="cyan"))
|
|
1014
|
+
click.echo()
|
|
1015
|
+
|
|
1016
|
+
except Exception as e:
|
|
1017
|
+
logger.debug(f"Error in display_done: {e}")
|
|
1018
|
+
|
|
1019
|
+
def display_warning(
|
|
1020
|
+
self,
|
|
1021
|
+
job: Dict[str, Any],
|
|
1022
|
+
log_path: str,
|
|
1023
|
+
log_content: Optional[str] = None,
|
|
1024
|
+
) -> None:
|
|
1025
|
+
"""Display warning status for SQLMap."""
|
|
1026
|
+
click.echo(click.style("=" * 70, fg="yellow"))
|
|
1027
|
+
click.echo(click.style("[WARNING] SQLMAP SCAN", bold=True, fg="yellow"))
|
|
1028
|
+
click.echo(click.style("=" * 70, fg="yellow"))
|
|
1029
|
+
click.echo()
|
|
1030
|
+
click.echo(" Scan completed with warnings. Check raw logs for details.")
|
|
1031
|
+
click.echo(" Press [r] to view raw logs.")
|
|
1032
|
+
click.echo()
|
|
1033
|
+
click.echo(click.style("=" * 70, fg="yellow"))
|
|
1034
|
+
click.echo()
|
|
1035
|
+
|
|
1036
|
+
def display_error(
|
|
1037
|
+
self,
|
|
1038
|
+
job: Dict[str, Any],
|
|
1039
|
+
log_path: str,
|
|
1040
|
+
log_content: Optional[str] = None,
|
|
1041
|
+
) -> None:
|
|
1042
|
+
"""Display error status for SQLMap."""
|
|
1043
|
+
# Read log if not provided
|
|
1044
|
+
if log_content is None and log_path and os.path.exists(log_path):
|
|
1045
|
+
try:
|
|
1046
|
+
with open(log_path, "r", encoding="utf-8", errors="replace") as f:
|
|
1047
|
+
log_content = f.read()
|
|
1048
|
+
except Exception:
|
|
1049
|
+
log_content = ""
|
|
1050
|
+
|
|
1051
|
+
click.echo(click.style("=" * 70, fg="red"))
|
|
1052
|
+
click.echo(click.style("[ERROR] SQLMAP SCAN FAILED", bold=True, fg="red"))
|
|
1053
|
+
click.echo(click.style("=" * 70, fg="red"))
|
|
1054
|
+
click.echo()
|
|
1055
|
+
|
|
1056
|
+
# Check for common sqlmap errors
|
|
1057
|
+
error_msg = None
|
|
1058
|
+
if log_content:
|
|
1059
|
+
if "connection timed out" in log_content.lower():
|
|
1060
|
+
error_msg = "Connection timed out - target may be slow or filtering"
|
|
1061
|
+
elif "unable to connect" in log_content.lower():
|
|
1062
|
+
error_msg = "Unable to connect to target URL"
|
|
1063
|
+
elif "page not found" in log_content.lower() or "404" in log_content:
|
|
1064
|
+
error_msg = "Target page not found (404)"
|
|
1065
|
+
elif "invalid target url" in log_content.lower():
|
|
1066
|
+
error_msg = "Invalid target URL - check the URL format"
|
|
1067
|
+
elif (
|
|
1068
|
+
"blocked by WAF/IPS" in log_content
|
|
1069
|
+
or "rejected by the target" in log_content.lower()
|
|
1070
|
+
):
|
|
1071
|
+
# Only flag actual WAF blocks, not heuristic "might be protected" checks
|
|
1072
|
+
error_msg = "WAF/IPS detected - try --tamper scripts"
|
|
1073
|
+
elif "all tested parameters do not appear" in log_content.lower():
|
|
1074
|
+
error_msg = "No injectable parameters found"
|
|
1075
|
+
elif "[CRITICAL]" in log_content:
|
|
1076
|
+
match = re.search(r"\[CRITICAL\]\s*(.+?)(?:\n|$)", log_content)
|
|
1077
|
+
if match:
|
|
1078
|
+
error_msg = match.group(1).strip()[:100]
|
|
1079
|
+
|
|
1080
|
+
if error_msg:
|
|
1081
|
+
click.echo(f" {error_msg}")
|
|
1082
|
+
else:
|
|
1083
|
+
click.echo(" Scan failed - see raw logs for details (press 'r')")
|
|
1084
|
+
|
|
1085
|
+
click.echo()
|
|
1086
|
+
click.echo(click.style("=" * 70, fg="red"))
|
|
1087
|
+
click.echo()
|
|
1088
|
+
|
|
1089
|
+
def display_no_results(
|
|
1090
|
+
self,
|
|
1091
|
+
job: Dict[str, Any],
|
|
1092
|
+
log_path: str,
|
|
1093
|
+
) -> None:
|
|
1094
|
+
"""Display no_results status for SQLMap."""
|
|
1095
|
+
click.echo(click.style("=" * 70, fg="cyan"))
|
|
1096
|
+
click.echo(click.style("SQL INJECTION SCAN", bold=True, fg="cyan"))
|
|
1097
|
+
click.echo(click.style("=" * 70, fg="cyan"))
|
|
1098
|
+
click.echo()
|
|
1099
|
+
|
|
1100
|
+
if job.get("target"):
|
|
1101
|
+
click.echo(click.style(f"Target: {job.get('target')}", bold=True))
|
|
1102
|
+
click.echo()
|
|
1103
|
+
|
|
1104
|
+
click.echo(
|
|
1105
|
+
click.style("Result: No SQL injection vulnerabilities found", fg="yellow")
|
|
1106
|
+
)
|
|
1107
|
+
click.echo()
|
|
1108
|
+
click.echo(" The target was tested but no injectable parameters were found.")
|
|
1109
|
+
click.echo()
|
|
1110
|
+
click.echo(click.style("Tips:", dim=True))
|
|
1111
|
+
click.echo(" - Try increasing --level and --risk for deeper testing")
|
|
1112
|
+
click.echo(" - Test with authenticated session cookies")
|
|
1113
|
+
click.echo(" - Try different injection techniques (--technique=BEUST)")
|
|
1114
|
+
click.echo()
|
|
1115
|
+
click.echo(click.style("=" * 70, fg="cyan"))
|
|
1116
|
+
click.echo()
|