souleyez 2.43.26__py3-none-any.whl → 2.43.34__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of souleyez might be problematic. Click here for more details.
- souleyez/__init__.py +1 -2
- souleyez/ai/__init__.py +21 -15
- souleyez/ai/action_mapper.py +249 -150
- souleyez/ai/chain_advisor.py +116 -100
- souleyez/ai/claude_provider.py +29 -28
- souleyez/ai/context_builder.py +80 -62
- souleyez/ai/executor.py +158 -117
- souleyez/ai/feedback_handler.py +136 -121
- souleyez/ai/llm_factory.py +27 -20
- souleyez/ai/llm_provider.py +4 -2
- souleyez/ai/ollama_provider.py +6 -9
- souleyez/ai/ollama_service.py +44 -37
- souleyez/ai/path_scorer.py +91 -76
- souleyez/ai/recommender.py +176 -144
- souleyez/ai/report_context.py +74 -73
- souleyez/ai/report_service.py +84 -66
- souleyez/ai/result_parser.py +222 -229
- souleyez/ai/safety.py +67 -44
- souleyez/auth/__init__.py +23 -22
- souleyez/auth/audit.py +36 -26
- souleyez/auth/engagement_access.py +65 -48
- souleyez/auth/permissions.py +14 -3
- souleyez/auth/session_manager.py +54 -37
- souleyez/auth/user_manager.py +109 -64
- souleyez/commands/audit.py +40 -43
- souleyez/commands/auth.py +35 -15
- souleyez/commands/deliverables.py +55 -50
- souleyez/commands/engagement.py +47 -28
- souleyez/commands/license.py +32 -23
- souleyez/commands/screenshots.py +36 -32
- souleyez/commands/user.py +82 -36
- souleyez/config.py +52 -44
- souleyez/core/credential_tester.py +87 -81
- souleyez/core/cve_mappings.py +179 -192
- souleyez/core/cve_matcher.py +162 -148
- souleyez/core/msf_auto_mapper.py +100 -83
- souleyez/core/msf_chain_engine.py +294 -256
- souleyez/core/msf_database.py +153 -70
- souleyez/core/msf_integration.py +679 -673
- souleyez/core/msf_rpc_client.py +40 -42
- souleyez/core/msf_rpc_manager.py +77 -79
- souleyez/core/msf_sync_manager.py +241 -181
- souleyez/core/network_utils.py +22 -15
- souleyez/core/parser_handler.py +34 -25
- souleyez/core/pending_chains.py +114 -63
- souleyez/core/templates.py +158 -107
- souleyez/core/tool_chaining.py +9526 -2879
- souleyez/core/version_utils.py +79 -94
- souleyez/core/vuln_correlation.py +136 -89
- souleyez/core/web_utils.py +33 -32
- souleyez/data/wordlists/ad_users.txt +378 -0
- souleyez/data/wordlists/api_endpoints_large.txt +769 -0
- souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
- souleyez/data/wordlists/lfi_payloads.txt +82 -0
- souleyez/data/wordlists/passwords_brute.txt +1548 -0
- souleyez/data/wordlists/passwords_crack.txt +2479 -0
- souleyez/data/wordlists/passwords_spray.txt +386 -0
- souleyez/data/wordlists/subdomains_large.txt +5057 -0
- souleyez/data/wordlists/usernames_common.txt +694 -0
- souleyez/data/wordlists/web_dirs_large.txt +4769 -0
- souleyez/detection/__init__.py +1 -1
- souleyez/detection/attack_signatures.py +12 -17
- souleyez/detection/mitre_mappings.py +61 -55
- souleyez/detection/validator.py +97 -86
- souleyez/devtools.py +23 -10
- souleyez/docs/README.md +4 -4
- souleyez/docs/api-reference/cli-commands.md +2 -2
- souleyez/docs/developer-guide/adding-new-tools.md +562 -0
- souleyez/docs/user-guide/auto-chaining.md +30 -8
- souleyez/docs/user-guide/getting-started.md +1 -1
- souleyez/docs/user-guide/installation.md +26 -3
- souleyez/docs/user-guide/metasploit-integration.md +2 -2
- souleyez/docs/user-guide/rbac.md +1 -1
- souleyez/docs/user-guide/scope-management.md +1 -1
- souleyez/docs/user-guide/siem-integration.md +1 -1
- souleyez/docs/user-guide/tools-reference.md +1 -8
- souleyez/docs/user-guide/worker-management.md +1 -1
- souleyez/engine/background.py +1239 -535
- souleyez/engine/base.py +4 -1
- souleyez/engine/job_status.py +17 -49
- souleyez/engine/log_sanitizer.py +103 -77
- souleyez/engine/manager.py +38 -7
- souleyez/engine/result_handler.py +2200 -1550
- souleyez/engine/worker_manager.py +50 -41
- souleyez/export/evidence_bundle.py +72 -62
- souleyez/feature_flags/features.py +16 -20
- souleyez/feature_flags.py +5 -9
- souleyez/handlers/__init__.py +11 -0
- souleyez/handlers/base.py +188 -0
- souleyez/handlers/bash_handler.py +277 -0
- souleyez/handlers/bloodhound_handler.py +243 -0
- souleyez/handlers/certipy_handler.py +311 -0
- souleyez/handlers/crackmapexec_handler.py +486 -0
- souleyez/handlers/dnsrecon_handler.py +344 -0
- souleyez/handlers/enum4linux_handler.py +400 -0
- souleyez/handlers/evil_winrm_handler.py +493 -0
- souleyez/handlers/ffuf_handler.py +815 -0
- souleyez/handlers/gobuster_handler.py +1114 -0
- souleyez/handlers/gpp_extract_handler.py +334 -0
- souleyez/handlers/hashcat_handler.py +444 -0
- souleyez/handlers/hydra_handler.py +563 -0
- souleyez/handlers/impacket_getuserspns_handler.py +343 -0
- souleyez/handlers/impacket_psexec_handler.py +222 -0
- souleyez/handlers/impacket_secretsdump_handler.py +426 -0
- souleyez/handlers/john_handler.py +286 -0
- souleyez/handlers/katana_handler.py +425 -0
- souleyez/handlers/kerbrute_handler.py +298 -0
- souleyez/handlers/ldapsearch_handler.py +636 -0
- souleyez/handlers/lfi_extract_handler.py +464 -0
- souleyez/handlers/msf_auxiliary_handler.py +408 -0
- souleyez/handlers/msf_exploit_handler.py +380 -0
- souleyez/handlers/nikto_handler.py +413 -0
- souleyez/handlers/nmap_handler.py +821 -0
- souleyez/handlers/nuclei_handler.py +359 -0
- souleyez/handlers/nxc_handler.py +371 -0
- souleyez/handlers/rdp_sec_check_handler.py +353 -0
- souleyez/handlers/registry.py +292 -0
- souleyez/handlers/responder_handler.py +232 -0
- souleyez/handlers/service_explorer_handler.py +434 -0
- souleyez/handlers/smbclient_handler.py +344 -0
- souleyez/handlers/smbmap_handler.py +510 -0
- souleyez/handlers/smbpasswd_handler.py +296 -0
- souleyez/handlers/sqlmap_handler.py +1116 -0
- souleyez/handlers/theharvester_handler.py +601 -0
- souleyez/handlers/web_login_test_handler.py +327 -0
- souleyez/handlers/whois_handler.py +277 -0
- souleyez/handlers/wpscan_handler.py +554 -0
- souleyez/history.py +32 -16
- souleyez/importers/msf_importer.py +106 -75
- souleyez/importers/smart_importer.py +208 -147
- souleyez/integrations/siem/__init__.py +10 -10
- souleyez/integrations/siem/base.py +17 -18
- souleyez/integrations/siem/elastic.py +108 -122
- souleyez/integrations/siem/factory.py +207 -80
- souleyez/integrations/siem/googlesecops.py +146 -154
- souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
- souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
- souleyez/integrations/siem/sentinel.py +107 -109
- souleyez/integrations/siem/splunk.py +246 -212
- souleyez/integrations/siem/wazuh.py +65 -71
- souleyez/integrations/wazuh/__init__.py +5 -5
- souleyez/integrations/wazuh/client.py +70 -93
- souleyez/integrations/wazuh/config.py +85 -57
- souleyez/integrations/wazuh/host_mapper.py +28 -36
- souleyez/integrations/wazuh/sync.py +78 -68
- souleyez/intelligence/__init__.py +4 -5
- souleyez/intelligence/correlation_analyzer.py +309 -295
- souleyez/intelligence/exploit_knowledge.py +661 -623
- souleyez/intelligence/exploit_suggestions.py +159 -139
- souleyez/intelligence/gap_analyzer.py +132 -97
- souleyez/intelligence/gap_detector.py +251 -214
- souleyez/intelligence/sensitive_tables.py +266 -129
- souleyez/intelligence/service_parser.py +137 -123
- souleyez/intelligence/surface_analyzer.py +407 -268
- souleyez/intelligence/target_parser.py +159 -162
- souleyez/licensing/__init__.py +6 -6
- souleyez/licensing/validator.py +17 -19
- souleyez/log_config.py +79 -54
- souleyez/main.py +1505 -687
- souleyez/migrations/fix_job_counter.py +16 -14
- souleyez/parsers/bloodhound_parser.py +41 -39
- souleyez/parsers/crackmapexec_parser.py +178 -111
- souleyez/parsers/dalfox_parser.py +72 -77
- souleyez/parsers/dnsrecon_parser.py +103 -91
- souleyez/parsers/enum4linux_parser.py +183 -153
- souleyez/parsers/ffuf_parser.py +29 -25
- souleyez/parsers/gobuster_parser.py +301 -41
- souleyez/parsers/hashcat_parser.py +324 -79
- souleyez/parsers/http_fingerprint_parser.py +350 -103
- souleyez/parsers/hydra_parser.py +131 -111
- souleyez/parsers/impacket_parser.py +231 -178
- souleyez/parsers/john_parser.py +98 -86
- souleyez/parsers/katana_parser.py +316 -0
- souleyez/parsers/msf_parser.py +943 -498
- souleyez/parsers/nikto_parser.py +346 -65
- souleyez/parsers/nmap_parser.py +262 -174
- souleyez/parsers/nuclei_parser.py +40 -44
- souleyez/parsers/responder_parser.py +26 -26
- souleyez/parsers/searchsploit_parser.py +74 -74
- souleyez/parsers/service_explorer_parser.py +279 -0
- souleyez/parsers/smbmap_parser.py +180 -124
- souleyez/parsers/sqlmap_parser.py +434 -308
- souleyez/parsers/theharvester_parser.py +75 -57
- souleyez/parsers/whois_parser.py +135 -94
- souleyez/parsers/wpscan_parser.py +278 -190
- souleyez/plugins/afp.py +44 -36
- souleyez/plugins/afp_brute.py +114 -46
- souleyez/plugins/ard.py +48 -37
- souleyez/plugins/bloodhound.py +95 -61
- souleyez/plugins/certipy.py +303 -0
- souleyez/plugins/crackmapexec.py +186 -85
- souleyez/plugins/dalfox.py +120 -59
- souleyez/plugins/dns_hijack.py +146 -41
- souleyez/plugins/dnsrecon.py +97 -61
- souleyez/plugins/enum4linux.py +91 -66
- souleyez/plugins/evil_winrm.py +291 -0
- souleyez/plugins/ffuf.py +166 -90
- souleyez/plugins/firmware_extract.py +133 -29
- souleyez/plugins/gobuster.py +387 -190
- souleyez/plugins/gpp_extract.py +393 -0
- souleyez/plugins/hashcat.py +100 -73
- souleyez/plugins/http_fingerprint.py +854 -267
- souleyez/plugins/hydra.py +566 -200
- souleyez/plugins/impacket_getnpusers.py +117 -69
- souleyez/plugins/impacket_psexec.py +84 -64
- souleyez/plugins/impacket_secretsdump.py +103 -69
- souleyez/plugins/impacket_smbclient.py +89 -75
- souleyez/plugins/john.py +86 -69
- souleyez/plugins/katana.py +313 -0
- souleyez/plugins/kerbrute.py +237 -0
- souleyez/plugins/lfi_extract.py +541 -0
- souleyez/plugins/macos_ssh.py +117 -48
- souleyez/plugins/mdns.py +35 -30
- souleyez/plugins/msf_auxiliary.py +253 -130
- souleyez/plugins/msf_exploit.py +239 -161
- souleyez/plugins/nikto.py +134 -78
- souleyez/plugins/nmap.py +275 -91
- souleyez/plugins/nuclei.py +180 -89
- souleyez/plugins/nxc.py +285 -0
- souleyez/plugins/plugin_base.py +35 -36
- souleyez/plugins/plugin_template.py +13 -5
- souleyez/plugins/rdp_sec_check.py +130 -0
- souleyez/plugins/responder.py +112 -71
- souleyez/plugins/router_http_brute.py +76 -65
- souleyez/plugins/router_ssh_brute.py +118 -41
- souleyez/plugins/router_telnet_brute.py +124 -42
- souleyez/plugins/routersploit.py +91 -59
- souleyez/plugins/routersploit_exploit.py +77 -55
- souleyez/plugins/searchsploit.py +91 -77
- souleyez/plugins/service_explorer.py +1160 -0
- souleyez/plugins/smbmap.py +122 -72
- souleyez/plugins/smbpasswd.py +215 -0
- souleyez/plugins/sqlmap.py +301 -113
- souleyez/plugins/theharvester.py +127 -75
- souleyez/plugins/tr069.py +79 -57
- souleyez/plugins/upnp.py +65 -47
- souleyez/plugins/upnp_abuse.py +73 -55
- souleyez/plugins/vnc_access.py +129 -42
- souleyez/plugins/vnc_brute.py +109 -38
- souleyez/plugins/web_login_test.py +417 -0
- souleyez/plugins/whois.py +77 -58
- souleyez/plugins/wpscan.py +173 -69
- souleyez/reporting/__init__.py +2 -1
- souleyez/reporting/attack_chain.py +411 -346
- souleyez/reporting/charts.py +436 -501
- souleyez/reporting/compliance_mappings.py +334 -201
- souleyez/reporting/detection_report.py +126 -125
- souleyez/reporting/formatters.py +828 -591
- souleyez/reporting/generator.py +386 -302
- souleyez/reporting/metrics.py +72 -75
- souleyez/scanner.py +35 -29
- souleyez/security/__init__.py +37 -11
- souleyez/security/scope_validator.py +175 -106
- souleyez/security/validation.py +223 -149
- souleyez/security.py +22 -6
- souleyez/storage/credentials.py +247 -186
- souleyez/storage/crypto.py +296 -129
- souleyez/storage/database.py +73 -50
- souleyez/storage/db.py +58 -36
- souleyez/storage/deliverable_evidence.py +177 -128
- souleyez/storage/deliverable_exporter.py +282 -246
- souleyez/storage/deliverable_templates.py +134 -116
- souleyez/storage/deliverables.py +135 -130
- souleyez/storage/engagements.py +109 -56
- souleyez/storage/evidence.py +181 -152
- souleyez/storage/execution_log.py +31 -17
- souleyez/storage/exploit_attempts.py +93 -57
- souleyez/storage/exploits.py +67 -36
- souleyez/storage/findings.py +48 -61
- souleyez/storage/hosts.py +176 -144
- souleyez/storage/migrate_to_engagements.py +43 -19
- souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
- souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
- souleyez/storage/migrations/_003_add_execution_log.py +14 -8
- souleyez/storage/migrations/_005_screenshots.py +13 -5
- souleyez/storage/migrations/_006_deliverables.py +13 -5
- souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
- souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
- souleyez/storage/migrations/_010_evidence_linking.py +17 -10
- souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
- souleyez/storage/migrations/_012_team_collaboration.py +34 -21
- souleyez/storage/migrations/_013_add_host_tags.py +12 -6
- souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
- souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
- souleyez/storage/migrations/_016_add_domain_field.py +10 -4
- souleyez/storage/migrations/_017_msf_sessions.py +16 -8
- souleyez/storage/migrations/_018_add_osint_target.py +10 -6
- souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
- souleyez/storage/migrations/_020_add_rbac.py +36 -15
- souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
- souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
- souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
- souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
- souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
- souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
- souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
- souleyez/storage/migrations/__init__.py +26 -26
- souleyez/storage/migrations/migration_manager.py +19 -19
- souleyez/storage/msf_sessions.py +100 -65
- souleyez/storage/osint.py +17 -24
- souleyez/storage/recommendation_engine.py +269 -235
- souleyez/storage/screenshots.py +33 -32
- souleyez/storage/smb_shares.py +136 -92
- souleyez/storage/sqlmap_data.py +183 -128
- souleyez/storage/team_collaboration.py +135 -141
- souleyez/storage/timeline_tracker.py +122 -94
- souleyez/storage/wazuh_vulns.py +64 -66
- souleyez/storage/web_paths.py +33 -37
- souleyez/testing/credential_tester.py +221 -205
- souleyez/ui/__init__.py +1 -1
- souleyez/ui/ai_quotes.py +12 -12
- souleyez/ui/attack_surface.py +2439 -1516
- souleyez/ui/chain_rules_view.py +914 -382
- souleyez/ui/correlation_view.py +312 -230
- souleyez/ui/dashboard.py +2382 -1130
- souleyez/ui/deliverables_view.py +148 -62
- souleyez/ui/design_system.py +13 -13
- souleyez/ui/errors.py +49 -49
- souleyez/ui/evidence_linking_view.py +284 -179
- souleyez/ui/evidence_vault.py +393 -285
- souleyez/ui/exploit_suggestions_view.py +555 -349
- souleyez/ui/export_view.py +100 -66
- souleyez/ui/gap_analysis_view.py +315 -171
- souleyez/ui/help_system.py +105 -97
- souleyez/ui/intelligence_view.py +436 -293
- souleyez/ui/interactive.py +23434 -10286
- souleyez/ui/interactive_selector.py +75 -68
- souleyez/ui/log_formatter.py +47 -39
- souleyez/ui/menu_components.py +22 -13
- souleyez/ui/msf_auxiliary_menu.py +184 -133
- souleyez/ui/pending_chains_view.py +336 -172
- souleyez/ui/progress_indicators.py +5 -3
- souleyez/ui/recommendations_view.py +195 -137
- souleyez/ui/rule_builder.py +343 -225
- souleyez/ui/setup_wizard.py +678 -284
- souleyez/ui/shortcuts.py +217 -165
- souleyez/ui/splunk_gap_analysis_view.py +452 -270
- souleyez/ui/splunk_vulns_view.py +139 -86
- souleyez/ui/team_dashboard.py +498 -335
- souleyez/ui/template_selector.py +196 -105
- souleyez/ui/terminal.py +6 -6
- souleyez/ui/timeline_view.py +198 -127
- souleyez/ui/tool_setup.py +264 -164
- souleyez/ui/tutorial.py +202 -72
- souleyez/ui/tutorial_state.py +40 -40
- souleyez/ui/wazuh_vulns_view.py +235 -141
- souleyez/ui/wordlist_browser.py +260 -107
- souleyez/ui.py +464 -312
- souleyez/utils/tool_checker.py +427 -367
- souleyez/utils.py +33 -29
- souleyez/wordlists.py +134 -167
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/METADATA +1 -1
- souleyez-2.43.34.dist-info/RECORD +443 -0
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
- souleyez-2.43.26.dist-info/RECORD +0 -379
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/top_level.txt +0 -0
|
@@ -11,7 +11,7 @@ from typing import Dict, Any
|
|
|
11
11
|
def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
12
12
|
"""
|
|
13
13
|
Parse SQLMap output and extract SQL injection vulnerabilities.
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
VERSION: 2.0 - Fixed injectable_url for auto-chaining
|
|
16
16
|
|
|
17
17
|
SQLMap output contains:
|
|
@@ -44,40 +44,37 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
44
44
|
}
|
|
45
45
|
"""
|
|
46
46
|
result = {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
"target_url": target,
|
|
48
|
+
"urls_tested": [],
|
|
49
|
+
"vulnerabilities": [],
|
|
50
|
+
"databases": [],
|
|
51
|
+
"tables": {}, # {database: [table1, table2, ...]}
|
|
52
|
+
"columns": {}, # {database.table: [col1, col2, ...]}
|
|
53
|
+
"dumped_data": {}, # {db.table: {'rows': [...], 'csv_path': '...', 'columns': [...]}}
|
|
54
|
+
"dbms": None, # Database type (MySQL, PostgreSQL, etc.)
|
|
56
55
|
# NEW: Add SQL injection confirmation flags
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
"sql_injection_confirmed": False,
|
|
57
|
+
"injectable_parameter": "",
|
|
58
|
+
"injectable_url": target,
|
|
59
|
+
"injectable_post_data": None, # POST data for POST injections
|
|
60
|
+
"injectable_method": "GET", # GET or POST
|
|
61
|
+
"all_injection_points": [], # ALL injection points found (for fallback)
|
|
64
62
|
# NEW: Web stack information
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
"web_server_os": None,
|
|
64
|
+
"web_app_technology": [],
|
|
65
|
+
"injection_techniques": [],
|
|
69
66
|
# NEW: Post-exploitation flags for auto-chaining
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
"is_dba": False,
|
|
68
|
+
"privileges": [],
|
|
69
|
+
"current_user": None,
|
|
70
|
+
"file_read_success": False,
|
|
71
|
+
"os_command_success": False,
|
|
75
72
|
}
|
|
76
73
|
|
|
77
|
-
lines = output.split(
|
|
74
|
+
lines = output.split("\n")
|
|
78
75
|
current_url = None
|
|
79
76
|
current_param = None
|
|
80
|
-
current_method =
|
|
77
|
+
current_method = "GET"
|
|
81
78
|
current_post_data = None
|
|
82
79
|
|
|
83
80
|
# Track POST form URLs separately to prevent GET URL testing from overwriting them
|
|
@@ -90,31 +87,33 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
90
87
|
|
|
91
88
|
# Extract URL being tested (GET requests typically)
|
|
92
89
|
# Format variations: "testing URL 'http://...'" or 'testing URL "http://..."' or testing URL http://...
|
|
93
|
-
if
|
|
90
|
+
if "testing URL" in line or "testing url" in line.lower():
|
|
94
91
|
# Try single quotes first
|
|
95
|
-
url_match = re.search(
|
|
92
|
+
url_match = re.search(
|
|
93
|
+
r"testing URL ['\"]?([^'\"]+)['\"]?", line, re.IGNORECASE
|
|
94
|
+
)
|
|
96
95
|
if url_match:
|
|
97
96
|
current_url = url_match.group(1).strip()
|
|
98
|
-
if current_url and current_url not in result[
|
|
99
|
-
result[
|
|
97
|
+
if current_url and current_url not in result["urls_tested"]:
|
|
98
|
+
result["urls_tested"].append(current_url)
|
|
100
99
|
|
|
101
100
|
# Extract POST/GET URLs from form testing (crawl mode)
|
|
102
101
|
# Format: "POST http://testphp.vulnweb.com/search.php?test=query"
|
|
103
|
-
if re.match(r
|
|
104
|
-
url_match = re.search(r
|
|
102
|
+
if re.match(r"^(POST|GET)\s+http", line):
|
|
103
|
+
url_match = re.search(r"^(POST|GET)\s+(https?://[^\s]+)", line)
|
|
105
104
|
if url_match:
|
|
106
105
|
current_method = url_match.group(1)
|
|
107
106
|
current_url = url_match.group(2)
|
|
108
|
-
if current_url not in result[
|
|
109
|
-
result[
|
|
107
|
+
if current_url not in result["urls_tested"]:
|
|
108
|
+
result["urls_tested"].append(current_url)
|
|
110
109
|
# Save POST form URL separately for later use
|
|
111
|
-
if current_method ==
|
|
110
|
+
if current_method == "POST":
|
|
112
111
|
last_post_form_url = current_url
|
|
113
112
|
|
|
114
113
|
# Extract POST data (appears after "POST http://..." line)
|
|
115
114
|
# Format: "POST data: username=&password=&submit=Login"
|
|
116
|
-
if line.startswith(
|
|
117
|
-
post_data_match = re.search(r
|
|
115
|
+
if line.startswith("POST data:"):
|
|
116
|
+
post_data_match = re.search(r"^POST data:\s*(.+)$", line)
|
|
118
117
|
if post_data_match:
|
|
119
118
|
current_post_data = post_data_match.group(1).strip()
|
|
120
119
|
# Associate POST data with the POST form URL
|
|
@@ -123,67 +122,96 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
123
122
|
# Handle resumed injection points from stored session
|
|
124
123
|
# Pattern: "sqlmap resumed the following injection point(s) from stored session:"
|
|
125
124
|
# Followed by: "Parameter: User-Agent (User-Agent)" or similar
|
|
126
|
-
if
|
|
125
|
+
if "resumed the following injection point" in line:
|
|
127
126
|
# Look ahead for Parameter line
|
|
128
127
|
for j in range(i + 1, min(i + 10, len(lines))):
|
|
129
128
|
next_line = lines[j].strip()
|
|
130
129
|
# Pattern: "Parameter: username (POST)" or "Parameter: User-Agent (User-Agent)"
|
|
131
130
|
param_match = re.search(
|
|
132
|
-
r
|
|
131
|
+
r"^Parameter:\s*([^\s(]+)\s*\(([^)]+)\)", next_line
|
|
133
132
|
)
|
|
134
133
|
if param_match:
|
|
135
134
|
param = param_match.group(1)
|
|
136
135
|
param_location = param_match.group(2)
|
|
137
136
|
|
|
138
137
|
# Determine method from parameter location
|
|
139
|
-
if param_location in (
|
|
138
|
+
if param_location in ("POST", "GET"):
|
|
140
139
|
method = param_location
|
|
141
140
|
else:
|
|
142
141
|
method = current_method # Use current context
|
|
143
142
|
|
|
144
143
|
# For POST parameters, use the saved POST form URL instead of current_url
|
|
145
|
-
if method ==
|
|
144
|
+
if method == "POST" and last_post_form_url:
|
|
146
145
|
effective_url = last_post_form_url
|
|
147
146
|
effective_post_data = last_post_form_data or current_post_data
|
|
148
147
|
else:
|
|
149
148
|
effective_url = current_url or target
|
|
150
|
-
effective_post_data =
|
|
149
|
+
effective_post_data = (
|
|
150
|
+
current_post_data if method == "POST" else None
|
|
151
|
+
)
|
|
151
152
|
|
|
152
153
|
# Mark as confirmed injection
|
|
153
|
-
result[
|
|
154
|
-
result[
|
|
155
|
-
result[
|
|
156
|
-
result[
|
|
154
|
+
result["sql_injection_confirmed"] = True
|
|
155
|
+
result["injectable_parameter"] = param
|
|
156
|
+
result["injectable_url"] = effective_url
|
|
157
|
+
result["injectable_method"] = method
|
|
158
|
+
|
|
159
|
+
# For POST injections, extract POST data from Payload line if not already set
|
|
160
|
+
if method == "POST" and not effective_post_data:
|
|
161
|
+
# Look ahead for Payload line that contains POST data
|
|
162
|
+
for k in range(j + 1, min(j + 15, len(lines))):
|
|
163
|
+
payload_line = lines[k].strip()
|
|
164
|
+
if payload_line.startswith("Payload:"):
|
|
165
|
+
# Extract the POST data from payload
|
|
166
|
+
# Format: "Payload: csrf-token=...¶m=value&..."
|
|
167
|
+
payload_match = re.search(
|
|
168
|
+
r"^Payload:\s*(.+)$", payload_line
|
|
169
|
+
)
|
|
170
|
+
if payload_match:
|
|
171
|
+
payload_data = payload_match.group(1).strip()
|
|
172
|
+
# Remove the injection payload part to get clean POST data
|
|
173
|
+
# The payload contains the base POST data with injection appended
|
|
174
|
+
effective_post_data = payload_data
|
|
175
|
+
result["injectable_post_data"] = effective_post_data
|
|
176
|
+
break
|
|
177
|
+
if payload_line.startswith(
|
|
178
|
+
"---"
|
|
179
|
+
) or payload_line.startswith("["):
|
|
180
|
+
break
|
|
181
|
+
elif method == "POST" and effective_post_data:
|
|
182
|
+
result["injectable_post_data"] = effective_post_data
|
|
157
183
|
|
|
158
184
|
# Add vulnerability entry
|
|
159
|
-
result[
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
185
|
+
result["vulnerabilities"].append(
|
|
186
|
+
{
|
|
187
|
+
"url": effective_url,
|
|
188
|
+
"parameter": param,
|
|
189
|
+
"vuln_type": "sqli",
|
|
190
|
+
"injectable": True,
|
|
191
|
+
"severity": "critical",
|
|
192
|
+
"description": f"Parameter '{param}' is vulnerable to SQL injection (resumed from session)",
|
|
193
|
+
}
|
|
194
|
+
)
|
|
167
195
|
|
|
168
196
|
# Collect injection point
|
|
169
197
|
injection_point = {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
198
|
+
"url": effective_url,
|
|
199
|
+
"parameter": param,
|
|
200
|
+
"method": method,
|
|
201
|
+
"post_data": effective_post_data,
|
|
202
|
+
"techniques": [],
|
|
175
203
|
}
|
|
176
204
|
if not any(
|
|
177
|
-
ip[
|
|
178
|
-
ip[
|
|
179
|
-
for ip in result[
|
|
205
|
+
ip["url"] == injection_point["url"]
|
|
206
|
+
and ip["parameter"] == injection_point["parameter"]
|
|
207
|
+
for ip in result["all_injection_points"]
|
|
180
208
|
):
|
|
181
|
-
result[
|
|
209
|
+
result["all_injection_points"].append(injection_point)
|
|
182
210
|
break
|
|
183
211
|
# Stop if we hit a delimiter
|
|
184
|
-
if next_line ==
|
|
212
|
+
if next_line == "---":
|
|
185
213
|
continue
|
|
186
|
-
if next_line.startswith(
|
|
214
|
+
if next_line.startswith("[") or next_line.startswith("back-end"):
|
|
187
215
|
break
|
|
188
216
|
|
|
189
217
|
# Extract DBMS type with full version info
|
|
@@ -191,120 +219,150 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
191
219
|
# "back-end DBMS: MySQL >= 5.0.12"
|
|
192
220
|
# "back-end DBMS: Microsoft SQL Server 2019"
|
|
193
221
|
# "back-end DBMS: PostgreSQL"
|
|
194
|
-
if
|
|
222
|
+
if "back-end DBMS:" in line or "back-end dbms:" in line.lower():
|
|
195
223
|
dbms_match = re.search(r"back-end DBMS:\s*(.+)", line, re.IGNORECASE)
|
|
196
|
-
if dbms_match and not result[
|
|
224
|
+
if dbms_match and not result["dbms"]:
|
|
197
225
|
dbms_full = dbms_match.group(1).strip()
|
|
198
226
|
# Extract just the DBMS name for the main field (first word)
|
|
199
227
|
# but store full version in a separate field
|
|
200
|
-
result[
|
|
201
|
-
result[
|
|
228
|
+
result["dbms"] = dbms_full.split()[0] if dbms_full else None
|
|
229
|
+
result["dbms_full"] = dbms_full # Keep full string
|
|
202
230
|
|
|
203
231
|
# Extract web server OS
|
|
204
|
-
if
|
|
205
|
-
os_match = re.search(
|
|
232
|
+
if "web server operating system:" in line.lower():
|
|
233
|
+
os_match = re.search(
|
|
234
|
+
r"web server operating system:\s*(.+)", line, re.IGNORECASE
|
|
235
|
+
)
|
|
206
236
|
if os_match:
|
|
207
|
-
result[
|
|
237
|
+
result["web_server_os"] = os_match.group(1).strip()
|
|
208
238
|
|
|
209
239
|
# Extract web application technology
|
|
210
|
-
if
|
|
211
|
-
tech_match = re.search(
|
|
240
|
+
if "web application technology:" in line.lower():
|
|
241
|
+
tech_match = re.search(
|
|
242
|
+
r"web application technology:\s*(.+)", line, re.IGNORECASE
|
|
243
|
+
)
|
|
212
244
|
if tech_match:
|
|
213
245
|
# Parse comma-separated technologies (e.g., "PHP 5.6.40, Nginx 1.19.0")
|
|
214
246
|
tech_str = tech_match.group(1).strip()
|
|
215
|
-
result[
|
|
247
|
+
result["web_app_technology"] = [t.strip() for t in tech_str.split(",")]
|
|
216
248
|
|
|
217
249
|
# === POST-EXPLOITATION FLAGS ===
|
|
218
250
|
|
|
219
251
|
# Detect DBA status: "current user is DBA: True" or "current user is DBA: False"
|
|
220
|
-
if
|
|
221
|
-
if
|
|
222
|
-
result[
|
|
252
|
+
if "current user is dba:" in line.lower():
|
|
253
|
+
if "true" in line.lower():
|
|
254
|
+
result["is_dba"] = True
|
|
223
255
|
# False is default, no need to set
|
|
224
256
|
|
|
225
257
|
# Detect current user: "current user: 'root@localhost'"
|
|
226
|
-
if
|
|
258
|
+
if "current user:" in line.lower() and "is dba" not in line.lower():
|
|
227
259
|
user_match = re.search(r"current user:\s*'?([^']+)'?", line, re.IGNORECASE)
|
|
228
260
|
if user_match:
|
|
229
|
-
result[
|
|
261
|
+
result["current_user"] = user_match.group(1).strip()
|
|
230
262
|
|
|
231
263
|
# Detect privileges: Look for privilege enumeration output
|
|
232
264
|
# SQLMap shows: "database management system users privileges:"
|
|
233
265
|
# followed by "[*] 'user'@'host' [1]:" and privilege lists
|
|
234
|
-
if
|
|
266
|
+
if "database management system users privileges" in line.lower():
|
|
235
267
|
# Parse privileges from following lines
|
|
236
268
|
j = i + 1
|
|
237
269
|
while j < len(lines):
|
|
238
270
|
priv_line = lines[j].strip()
|
|
239
271
|
# Look for privilege entries like "privilege: FILE" or just "FILE"
|
|
240
|
-
if priv_line.startswith(
|
|
272
|
+
if priv_line.startswith("[*]") and "@" in priv_line:
|
|
241
273
|
# User entry like "[*] 'root'@'localhost' [1]:"
|
|
242
274
|
j += 1
|
|
243
275
|
continue
|
|
244
|
-
elif priv_line.startswith(
|
|
245
|
-
priv = priv_line.replace(
|
|
246
|
-
if priv and priv not in result[
|
|
247
|
-
result[
|
|
248
|
-
elif
|
|
276
|
+
elif priv_line.startswith("privilege:"):
|
|
277
|
+
priv = priv_line.replace("privilege:", "").strip()
|
|
278
|
+
if priv and priv not in result["privileges"]:
|
|
279
|
+
result["privileges"].append(priv)
|
|
280
|
+
elif (
|
|
281
|
+
priv_line
|
|
282
|
+
and not priv_line.startswith("[")
|
|
283
|
+
and not priv_line.startswith("-")
|
|
284
|
+
):
|
|
249
285
|
# Direct privilege name
|
|
250
|
-
if priv_line.isupper() or priv_line in [
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
286
|
+
if priv_line.isupper() or priv_line in [
|
|
287
|
+
"FILE",
|
|
288
|
+
"SUPER",
|
|
289
|
+
"PROCESS",
|
|
290
|
+
"SHUTDOWN",
|
|
291
|
+
]:
|
|
292
|
+
if priv_line not in result["privileges"]:
|
|
293
|
+
result["privileges"].append(priv_line)
|
|
294
|
+
elif (
|
|
295
|
+
not priv_line
|
|
296
|
+
or priv_line.startswith("[INFO]")
|
|
297
|
+
or priv_line.startswith("---")
|
|
298
|
+
):
|
|
254
299
|
break
|
|
255
300
|
j += 1
|
|
256
301
|
|
|
257
302
|
# Detect file read success: "do you want to retrieve the content" followed by actual content
|
|
258
303
|
# or "the file has been saved to:" indicating successful read
|
|
259
|
-
if
|
|
260
|
-
if
|
|
261
|
-
|
|
304
|
+
if "the file has been saved to:" in line.lower() or "retrieved" in line.lower():
|
|
305
|
+
if (
|
|
306
|
+
"/etc/passwd" in output
|
|
307
|
+
or "/etc/shadow" in output
|
|
308
|
+
or "win.ini" in output.lower()
|
|
309
|
+
):
|
|
310
|
+
result["file_read_success"] = True
|
|
262
311
|
|
|
263
312
|
# Detect OS command success: Look for command output
|
|
264
313
|
# SQLMap shows: "command standard output:" followed by output
|
|
265
|
-
if
|
|
266
|
-
result[
|
|
314
|
+
if "command standard output:" in line.lower():
|
|
315
|
+
result["os_command_success"] = True
|
|
267
316
|
|
|
268
317
|
# === END POST-EXPLOITATION FLAGS ===
|
|
269
318
|
|
|
270
319
|
# Extract parameter being tested
|
|
271
|
-
if
|
|
272
|
-
param_match = re.search(
|
|
320
|
+
if "testing if" in line and "parameter" in line:
|
|
321
|
+
param_match = re.search(
|
|
322
|
+
r"(?:GET|POST|Cookie|User-Agent|Referer) parameter '([^']+)'", line
|
|
323
|
+
)
|
|
273
324
|
if param_match:
|
|
274
325
|
current_param = param_match.group(1)
|
|
275
326
|
|
|
276
327
|
# Detect XSS vulnerability hint
|
|
277
|
-
if
|
|
328
|
+
if "might be vulnerable to cross-site scripting (XSS)" in line:
|
|
278
329
|
param_match = re.search(r"parameter '([^']+)'", line)
|
|
279
330
|
if param_match or current_param:
|
|
280
331
|
param = param_match.group(1) if param_match else current_param
|
|
281
|
-
result[
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
332
|
+
result["vulnerabilities"].append(
|
|
333
|
+
{
|
|
334
|
+
"url": current_url or target,
|
|
335
|
+
"parameter": param,
|
|
336
|
+
"vuln_type": "xss",
|
|
337
|
+
"injectable": False,
|
|
338
|
+
"severity": "medium",
|
|
339
|
+
"description": f"Parameter '{param}' might be vulnerable to XSS",
|
|
340
|
+
}
|
|
341
|
+
)
|
|
289
342
|
|
|
290
343
|
# Detect File Inclusion vulnerability hint
|
|
291
|
-
if
|
|
344
|
+
if "might be vulnerable to file inclusion (FI)" in line:
|
|
292
345
|
param_match = re.search(r"parameter '([^']+)'", line)
|
|
293
346
|
if param_match or current_param:
|
|
294
347
|
param = param_match.group(1) if param_match else current_param
|
|
295
|
-
result[
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
348
|
+
result["vulnerabilities"].append(
|
|
349
|
+
{
|
|
350
|
+
"url": current_url or target,
|
|
351
|
+
"parameter": param,
|
|
352
|
+
"vuln_type": "file_inclusion",
|
|
353
|
+
"injectable": False,
|
|
354
|
+
"severity": "high",
|
|
355
|
+
"description": f"Parameter '{param}' might be vulnerable to File Inclusion",
|
|
356
|
+
}
|
|
357
|
+
)
|
|
303
358
|
|
|
304
359
|
# NEW: Detect SQL injection from resumed session (Parameter: X (GET/POST))
|
|
305
360
|
# This catches cases where SQLMap resumes and shows injection point without saying "is vulnerable"
|
|
306
|
-
if re.match(
|
|
307
|
-
|
|
361
|
+
if re.match(
|
|
362
|
+
r"^Parameter:\s+[\w\-]+\s+\((GET|POST|COOKIE|URI|User-Agent|Referer|Host)\)",
|
|
363
|
+
line,
|
|
364
|
+
):
|
|
365
|
+
param_match = re.search(r"^Parameter:\s+([\w\-]+)\s+\(([\w\-]+)\)", line)
|
|
308
366
|
if param_match:
|
|
309
367
|
param = param_match.group(1)
|
|
310
368
|
param_method = param_match.group(2)
|
|
@@ -316,22 +374,28 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
316
374
|
next_line = lines[j].strip()
|
|
317
375
|
|
|
318
376
|
# Check for Type: line
|
|
319
|
-
if next_line.startswith(
|
|
320
|
-
technique = {
|
|
321
|
-
technique[
|
|
377
|
+
if next_line.startswith("Type:"):
|
|
378
|
+
technique = {"type": "", "title": "", "payload": ""}
|
|
379
|
+
technique["type"] = next_line.replace("Type:", "").strip()
|
|
322
380
|
|
|
323
381
|
# Look for Title: on next lines
|
|
324
|
-
if j + 1 < len(lines) and
|
|
325
|
-
technique[
|
|
382
|
+
if j + 1 < len(lines) and "Title:" in lines[j + 1]:
|
|
383
|
+
technique["title"] = (
|
|
384
|
+
lines[j + 1].replace("Title:", "").strip()
|
|
385
|
+
)
|
|
326
386
|
|
|
327
387
|
# Look for Payload: on next lines
|
|
328
|
-
if j + 2 < len(lines) and
|
|
329
|
-
technique[
|
|
388
|
+
if j + 2 < len(lines) and "Payload:" in lines[j + 2]:
|
|
389
|
+
technique["payload"] = (
|
|
390
|
+
lines[j + 2].replace("Payload:", "").strip()
|
|
391
|
+
)
|
|
330
392
|
|
|
331
393
|
techniques.append(technique)
|
|
332
394
|
j += 3
|
|
333
395
|
# Stop when we hit next major section
|
|
334
|
-
elif next_line.startswith(
|
|
396
|
+
elif next_line.startswith("---") or next_line.startswith(
|
|
397
|
+
"do you want"
|
|
398
|
+
):
|
|
335
399
|
break
|
|
336
400
|
else:
|
|
337
401
|
j += 1
|
|
@@ -340,131 +404,154 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
340
404
|
if techniques:
|
|
341
405
|
# Check if we already added this parameter (avoid duplicates)
|
|
342
406
|
already_added = any(
|
|
343
|
-
v[
|
|
344
|
-
for v in result[
|
|
407
|
+
v["parameter"] == param and v["vuln_type"] == "sqli"
|
|
408
|
+
for v in result["vulnerabilities"]
|
|
345
409
|
)
|
|
346
410
|
|
|
347
411
|
if not already_added:
|
|
348
412
|
# For POST parameters, use the saved POST form URL instead of current_url
|
|
349
413
|
# This prevents bug where GET URL testing overwrites the correct POST form URL
|
|
350
|
-
if param_method ==
|
|
414
|
+
if param_method == "POST" and last_post_form_url:
|
|
351
415
|
effective_url = last_post_form_url
|
|
352
|
-
effective_post_data =
|
|
416
|
+
effective_post_data = (
|
|
417
|
+
last_post_form_data or current_post_data
|
|
418
|
+
)
|
|
353
419
|
else:
|
|
354
420
|
effective_url = current_url or target
|
|
355
|
-
effective_post_data =
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
421
|
+
effective_post_data = (
|
|
422
|
+
current_post_data if param_method == "POST" else None
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
result["vulnerabilities"].append(
|
|
426
|
+
{
|
|
427
|
+
"url": effective_url,
|
|
428
|
+
"parameter": param,
|
|
429
|
+
"vuln_type": "sqli",
|
|
430
|
+
"injectable": True,
|
|
431
|
+
"severity": "critical",
|
|
432
|
+
"description": f"Parameter '{param}' is vulnerable to SQL injection",
|
|
433
|
+
"technique": "multiple", # SQLMap found multiple techniques
|
|
434
|
+
"dbms": result.get("dbms", "Unknown"),
|
|
435
|
+
}
|
|
436
|
+
)
|
|
367
437
|
|
|
368
438
|
# Set confirmation flags
|
|
369
|
-
result[
|
|
370
|
-
result[
|
|
371
|
-
result[
|
|
372
|
-
result[
|
|
373
|
-
if param_method ==
|
|
374
|
-
result[
|
|
439
|
+
result["sql_injection_confirmed"] = True
|
|
440
|
+
result["injectable_parameter"] = param
|
|
441
|
+
result["injectable_url"] = effective_url
|
|
442
|
+
result["injectable_method"] = param_method # GET, POST, etc.
|
|
443
|
+
if param_method == "POST" and effective_post_data:
|
|
444
|
+
result["injectable_post_data"] = effective_post_data
|
|
375
445
|
|
|
376
446
|
# Collect ALL injection points for fallback
|
|
377
447
|
injection_point = {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
448
|
+
"url": effective_url,
|
|
449
|
+
"parameter": param,
|
|
450
|
+
"method": param_method,
|
|
451
|
+
"post_data": effective_post_data,
|
|
452
|
+
"techniques": techniques,
|
|
383
453
|
}
|
|
384
454
|
# Avoid duplicates
|
|
385
|
-
if not any(
|
|
386
|
-
|
|
387
|
-
|
|
455
|
+
if not any(
|
|
456
|
+
ip["url"] == injection_point["url"]
|
|
457
|
+
and ip["parameter"] == injection_point["parameter"]
|
|
458
|
+
for ip in result["all_injection_points"]
|
|
459
|
+
):
|
|
460
|
+
result["all_injection_points"].append(injection_point)
|
|
388
461
|
|
|
389
462
|
# Add detailed injection techniques
|
|
390
|
-
result[
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
463
|
+
result["injection_techniques"].append(
|
|
464
|
+
{
|
|
465
|
+
"parameter": param,
|
|
466
|
+
"method": param_method,
|
|
467
|
+
"techniques": techniques,
|
|
468
|
+
}
|
|
469
|
+
)
|
|
395
470
|
|
|
396
471
|
# Detect SQL injection vulnerability
|
|
397
472
|
# Pattern: "POST parameter 'username' is vulnerable" or "GET parameter 'id' is vulnerable"
|
|
398
|
-
if
|
|
399
|
-
param_match = re.search(
|
|
473
|
+
if "parameter" in line and "is vulnerable" in line:
|
|
474
|
+
param_match = re.search(
|
|
475
|
+
r"(GET|POST|Cookie|User-Agent|Referer|Host)?\s*parameter '([^']+)' is vulnerable",
|
|
476
|
+
line,
|
|
477
|
+
)
|
|
400
478
|
if param_match:
|
|
401
479
|
method = param_match.group(1) or current_method
|
|
402
480
|
param = param_match.group(2)
|
|
403
481
|
|
|
404
482
|
# For POST parameters, use the saved POST form URL instead of current_url
|
|
405
483
|
# This prevents bug where GET URL testing overwrites the correct POST form URL
|
|
406
|
-
if method ==
|
|
484
|
+
if method == "POST" and last_post_form_url:
|
|
407
485
|
effective_url = last_post_form_url
|
|
408
486
|
effective_post_data = last_post_form_data or current_post_data
|
|
409
487
|
else:
|
|
410
488
|
effective_url = current_url or target
|
|
411
|
-
effective_post_data =
|
|
489
|
+
effective_post_data = (
|
|
490
|
+
current_post_data if method == "POST" else None
|
|
491
|
+
)
|
|
412
492
|
|
|
413
|
-
result[
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
493
|
+
result["vulnerabilities"].append(
|
|
494
|
+
{
|
|
495
|
+
"url": effective_url,
|
|
496
|
+
"parameter": param,
|
|
497
|
+
"vuln_type": "sqli",
|
|
498
|
+
"injectable": True,
|
|
499
|
+
"severity": "critical",
|
|
500
|
+
"description": f"Parameter '{param}' is vulnerable to SQL injection",
|
|
501
|
+
}
|
|
502
|
+
)
|
|
421
503
|
|
|
422
504
|
# Set confirmation flags
|
|
423
|
-
result[
|
|
424
|
-
result[
|
|
425
|
-
result[
|
|
426
|
-
result[
|
|
427
|
-
if method ==
|
|
428
|
-
result[
|
|
505
|
+
result["sql_injection_confirmed"] = True
|
|
506
|
+
result["injectable_parameter"] = param
|
|
507
|
+
result["injectable_url"] = effective_url
|
|
508
|
+
result["injectable_method"] = method
|
|
509
|
+
if method == "POST" and effective_post_data:
|
|
510
|
+
result["injectable_post_data"] = effective_post_data
|
|
429
511
|
|
|
430
512
|
# Collect ALL injection points for fallback
|
|
431
513
|
injection_point = {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
514
|
+
"url": effective_url,
|
|
515
|
+
"parameter": param,
|
|
516
|
+
"method": method,
|
|
517
|
+
"post_data": effective_post_data,
|
|
518
|
+
"techniques": [], # Technique details not available at this detection point
|
|
437
519
|
}
|
|
438
520
|
# Avoid duplicates
|
|
439
|
-
if not any(
|
|
440
|
-
|
|
441
|
-
|
|
521
|
+
if not any(
|
|
522
|
+
ip["url"] == injection_point["url"]
|
|
523
|
+
and ip["parameter"] == injection_point["parameter"]
|
|
524
|
+
for ip in result["all_injection_points"]
|
|
525
|
+
):
|
|
526
|
+
result["all_injection_points"].append(injection_point)
|
|
442
527
|
|
|
443
528
|
# Detect not injectable result
|
|
444
|
-
if
|
|
445
|
-
param_match = re.search(
|
|
529
|
+
if "does not seem to be injectable" in line:
|
|
530
|
+
param_match = re.search(
|
|
531
|
+
r"parameter '([^']+)' does not seem to be injectable", line
|
|
532
|
+
)
|
|
446
533
|
# We skip these - only store actual vulnerabilities
|
|
447
534
|
|
|
448
535
|
# Extract databases (if enumerated)
|
|
449
|
-
if
|
|
536
|
+
if "available databases" in line.lower():
|
|
450
537
|
# Next few lines will contain database names
|
|
451
538
|
j = i + 1
|
|
452
|
-
while j < len(lines) and lines[j].strip().startswith(
|
|
539
|
+
while j < len(lines) and lines[j].strip().startswith("[*]"):
|
|
453
540
|
db_line = lines[j].strip()
|
|
454
|
-
if db_line.startswith(
|
|
455
|
-
db_name = db_line.replace(
|
|
456
|
-
if db_name and not db_name.startswith(
|
|
457
|
-
result[
|
|
541
|
+
if db_line.startswith("[*]"):
|
|
542
|
+
db_name = db_line.replace("[*]", "").strip()
|
|
543
|
+
if db_name and not db_name.startswith("INFO"):
|
|
544
|
+
result["databases"].append(db_name)
|
|
458
545
|
j += 1
|
|
459
546
|
|
|
460
547
|
# Extract tables for a database (from "Database: dbname" followed by table list)
|
|
461
|
-
if re.match(r
|
|
548
|
+
if re.match(r"^Database:\s*\w+", line):
|
|
462
549
|
# Pattern: "Database: dbname"
|
|
463
550
|
db_match = re.search(r"Database:\s*(\w+)", line)
|
|
464
551
|
if db_match:
|
|
465
552
|
current_db = db_match.group(1)
|
|
466
553
|
# Check if next line says "[X table]" or "[X tables]"
|
|
467
|
-
if i + 1 < len(lines) and
|
|
554
|
+
if i + 1 < len(lines) and "table" in lines[i + 1]:
|
|
468
555
|
# Look for ASCII table with | borders
|
|
469
556
|
j = i + 2
|
|
470
557
|
in_table = False
|
|
@@ -472,34 +559,42 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
472
559
|
table_line = lines[j].strip()
|
|
473
560
|
|
|
474
561
|
# Start of table (first +---+ border)
|
|
475
|
-
if table_line.startswith(
|
|
562
|
+
if table_line.startswith("+") and "-" in table_line:
|
|
476
563
|
in_table = True
|
|
477
564
|
j += 1
|
|
478
565
|
continue
|
|
479
566
|
|
|
480
567
|
# End of table
|
|
481
|
-
if in_table and (
|
|
568
|
+
if in_table and (
|
|
569
|
+
not table_line
|
|
570
|
+
or table_line.startswith("[")
|
|
571
|
+
or table_line.startswith("SQL")
|
|
572
|
+
):
|
|
482
573
|
break
|
|
483
574
|
|
|
484
575
|
# Extract table name from | table_name |
|
|
485
|
-
if
|
|
576
|
+
if (
|
|
577
|
+
in_table
|
|
578
|
+
and table_line.startswith("|")
|
|
579
|
+
and table_line != "|"
|
|
580
|
+
):
|
|
486
581
|
# Skip header rows and separator rows
|
|
487
|
-
if not all(c in
|
|
582
|
+
if not all(c in "|+- " for c in table_line):
|
|
488
583
|
# Extract content between pipes
|
|
489
|
-
parts = [p.strip() for p in table_line.split(
|
|
584
|
+
parts = [p.strip() for p in table_line.split("|")]
|
|
490
585
|
for part in parts:
|
|
491
|
-
if part and part not in [
|
|
492
|
-
if current_db not in result[
|
|
493
|
-
result[
|
|
494
|
-
if part not in result[
|
|
495
|
-
result[
|
|
586
|
+
if part and part not in ["", "table", "tables"]:
|
|
587
|
+
if current_db not in result["tables"]:
|
|
588
|
+
result["tables"][current_db] = []
|
|
589
|
+
if part not in result["tables"][current_db]:
|
|
590
|
+
result["tables"][current_db].append(part)
|
|
496
591
|
j += 1
|
|
497
592
|
|
|
498
593
|
# Extract tables for SQLite (no database prefix, just "[X tables]" followed by table list)
|
|
499
594
|
# SQLite format: "[21 tables]" then "+---+" then "| TableName |" rows
|
|
500
|
-
if re.match(r
|
|
595
|
+
if re.match(r"^\[\d+\s+tables?\]$", line):
|
|
501
596
|
# SQLite uses implicit database name
|
|
502
|
-
sqlite_db =
|
|
597
|
+
sqlite_db = "SQLite_masterdb"
|
|
503
598
|
# Look for ASCII table with | borders starting after this line
|
|
504
599
|
j = i + 1
|
|
505
600
|
in_table = False
|
|
@@ -507,31 +602,35 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
507
602
|
table_line = lines[j].strip()
|
|
508
603
|
|
|
509
604
|
# Start of table (first +---+ border)
|
|
510
|
-
if table_line.startswith(
|
|
605
|
+
if table_line.startswith("+") and "-" in table_line:
|
|
511
606
|
in_table = True
|
|
512
607
|
j += 1
|
|
513
608
|
continue
|
|
514
609
|
|
|
515
610
|
# End of table
|
|
516
|
-
if in_table and (
|
|
611
|
+
if in_table and (
|
|
612
|
+
not table_line
|
|
613
|
+
or table_line.startswith("[")
|
|
614
|
+
or table_line.startswith("SQL")
|
|
615
|
+
):
|
|
517
616
|
break
|
|
518
617
|
|
|
519
618
|
# Extract table name from | table_name |
|
|
520
|
-
if in_table and table_line.startswith(
|
|
619
|
+
if in_table and table_line.startswith("|") and table_line != "|":
|
|
521
620
|
# Skip separator rows
|
|
522
|
-
if not all(c in
|
|
621
|
+
if not all(c in "|+- " for c in table_line):
|
|
523
622
|
# Extract content between pipes
|
|
524
|
-
parts = [p.strip() for p in table_line.split(
|
|
623
|
+
parts = [p.strip() for p in table_line.split("|")]
|
|
525
624
|
for part in parts:
|
|
526
|
-
if part and part not in [
|
|
527
|
-
if sqlite_db not in result[
|
|
528
|
-
result[
|
|
529
|
-
if part not in result[
|
|
530
|
-
result[
|
|
625
|
+
if part and part not in ["", "table", "tables"]:
|
|
626
|
+
if sqlite_db not in result["tables"]:
|
|
627
|
+
result["tables"][sqlite_db] = []
|
|
628
|
+
if part not in result["tables"][sqlite_db]:
|
|
629
|
+
result["tables"][sqlite_db].append(part)
|
|
531
630
|
j += 1
|
|
532
631
|
|
|
533
632
|
# Extract columns for a table (from "Table: tablename" with column list)
|
|
534
|
-
if re.match(r
|
|
633
|
+
if re.match(r"^(Database:.*)?Table:\s*\w+", line):
|
|
535
634
|
# Pattern: "Table: tablename" or "Database: db\nTable: table"
|
|
536
635
|
table_match = re.search(r"Table:\s*(\w+)", line)
|
|
537
636
|
db_match = re.search(r"Database:\s*(\w+)", line)
|
|
@@ -546,7 +645,7 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
546
645
|
db_name = db_match.group(1) if db_match else None
|
|
547
646
|
|
|
548
647
|
# Check if next line says "[X columns]"
|
|
549
|
-
if i + 1 < len(lines) and
|
|
648
|
+
if i + 1 < len(lines) and "column" in lines[i + 1]:
|
|
550
649
|
# Look for ASCII table with | Column | Type | borders
|
|
551
650
|
j = i + 2
|
|
552
651
|
in_table = False
|
|
@@ -556,22 +655,26 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
556
655
|
col_line = lines[j].strip()
|
|
557
656
|
|
|
558
657
|
# Start of table (first +---+ border)
|
|
559
|
-
if col_line.startswith(
|
|
658
|
+
if col_line.startswith("+") and "-" in col_line:
|
|
560
659
|
in_table = True
|
|
561
660
|
j += 1
|
|
562
661
|
continue
|
|
563
662
|
|
|
564
663
|
# End of table
|
|
565
|
-
if in_table and (
|
|
664
|
+
if in_table and (
|
|
665
|
+
not col_line
|
|
666
|
+
or col_line.startswith("[")
|
|
667
|
+
or col_line.startswith("SQL")
|
|
668
|
+
):
|
|
566
669
|
break
|
|
567
670
|
|
|
568
671
|
# Extract column name from | column_name | type |
|
|
569
|
-
if in_table and col_line.startswith(
|
|
672
|
+
if in_table and col_line.startswith("|") and col_line != "|":
|
|
570
673
|
# Skip header rows "| Column | Type |" and separator rows
|
|
571
|
-
if
|
|
572
|
-
if not all(c in
|
|
674
|
+
if "Column" not in col_line and "Type" not in col_line:
|
|
675
|
+
if not all(c in "|+- " for c in col_line):
|
|
573
676
|
# Extract first column (column name)
|
|
574
|
-
parts = [p.strip() for p in col_line.split(
|
|
677
|
+
parts = [p.strip() for p in col_line.split("|")]
|
|
575
678
|
if len(parts) >= 2 and parts[1]:
|
|
576
679
|
col_name = parts[1]
|
|
577
680
|
if col_name not in columns:
|
|
@@ -581,138 +684,150 @@ def parse_sqlmap_output(output: str, target: str = "") -> Dict[str, Any]:
|
|
|
581
684
|
if columns:
|
|
582
685
|
# Store with db.table key if we have db, otherwise just table
|
|
583
686
|
key = f"{db_name}.{table_name}" if db_name else table_name
|
|
584
|
-
result[
|
|
585
|
-
|
|
687
|
+
result["columns"][key] = columns
|
|
688
|
+
|
|
586
689
|
# Extract dumped data (e.g., "Database: owasp10\nTable: credit_cards\n[5 entries]")
|
|
587
|
-
if (
|
|
690
|
+
if ("entries]" in line or "entry]" in line) and i > 0:
|
|
588
691
|
# Check for "Table: tablename" in previous lines
|
|
589
692
|
db_name = None
|
|
590
693
|
table_name = None
|
|
591
|
-
|
|
592
|
-
for k in range(max(0, i-3), i):
|
|
694
|
+
|
|
695
|
+
for k in range(max(0, i - 3), i):
|
|
593
696
|
prev_line = lines[k].strip()
|
|
594
|
-
if prev_line.startswith(
|
|
595
|
-
db_name = prev_line.split(
|
|
596
|
-
elif prev_line.startswith(
|
|
597
|
-
table_name = prev_line.split(
|
|
598
|
-
|
|
697
|
+
if prev_line.startswith("Database:"):
|
|
698
|
+
db_name = prev_line.split("Database:")[1].strip()
|
|
699
|
+
elif prev_line.startswith("Table:"):
|
|
700
|
+
table_name = prev_line.split("Table:")[1].strip()
|
|
701
|
+
|
|
599
702
|
if table_name:
|
|
600
703
|
# Parse the data table that follows
|
|
601
704
|
j = i + 1
|
|
602
705
|
in_data_table = False
|
|
603
706
|
table_columns = []
|
|
604
707
|
table_rows = []
|
|
605
|
-
|
|
708
|
+
|
|
606
709
|
while j < len(lines):
|
|
607
710
|
data_line = lines[j].strip()
|
|
608
|
-
|
|
711
|
+
|
|
609
712
|
# Start of table (first +---+ border)
|
|
610
|
-
if data_line.startswith(
|
|
713
|
+
if data_line.startswith("+") and "-" in data_line:
|
|
611
714
|
if not in_data_table:
|
|
612
715
|
in_data_table = True
|
|
613
716
|
j += 1
|
|
614
717
|
continue
|
|
615
|
-
|
|
718
|
+
|
|
616
719
|
# End of table
|
|
617
|
-
if in_data_table and (
|
|
618
|
-
|
|
720
|
+
if in_data_table and (
|
|
721
|
+
not data_line
|
|
722
|
+
or data_line.startswith("[")
|
|
723
|
+
or "dumped to CSV" in data_line
|
|
724
|
+
):
|
|
619
725
|
break
|
|
620
|
-
|
|
726
|
+
|
|
621
727
|
# Extract column headers from first | ... | ... | row
|
|
622
|
-
if
|
|
623
|
-
|
|
728
|
+
if (
|
|
729
|
+
in_data_table
|
|
730
|
+
and data_line.startswith("|")
|
|
731
|
+
and not table_columns
|
|
732
|
+
):
|
|
733
|
+
parts = [p.strip() for p in data_line.split("|")]
|
|
624
734
|
table_columns = [p for p in parts if p]
|
|
625
735
|
j += 1
|
|
626
736
|
continue
|
|
627
|
-
|
|
737
|
+
|
|
628
738
|
# Extract data rows
|
|
629
|
-
if in_data_table and table_columns and data_line.startswith(
|
|
630
|
-
parts = [p.strip() for p in data_line.split(
|
|
631
|
-
values = [p for p in parts if p !=
|
|
739
|
+
if in_data_table and table_columns and data_line.startswith("|"):
|
|
740
|
+
parts = [p.strip() for p in data_line.split("|")]
|
|
741
|
+
values = [p for p in parts if p != ""]
|
|
632
742
|
if len(values) == len(table_columns):
|
|
633
743
|
row_dict = dict(zip(table_columns, values))
|
|
634
744
|
table_rows.append(row_dict)
|
|
635
|
-
|
|
745
|
+
|
|
636
746
|
j += 1
|
|
637
|
-
|
|
747
|
+
|
|
638
748
|
# Look for CSV file path
|
|
639
749
|
csv_path = None
|
|
640
|
-
for k in range(j, min(j+5, len(lines))):
|
|
641
|
-
if
|
|
750
|
+
for k in range(j, min(j + 5, len(lines))):
|
|
751
|
+
if "dumped to CSV file" in lines[k]:
|
|
642
752
|
csv_match = re.search(r"'([^']+\.csv)'", lines[k])
|
|
643
753
|
if csv_match:
|
|
644
754
|
csv_path = csv_match.group(1)
|
|
645
755
|
break
|
|
646
|
-
|
|
756
|
+
|
|
647
757
|
if table_rows:
|
|
648
758
|
key = f"{db_name}.{table_name}" if db_name else table_name
|
|
649
|
-
result[
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
759
|
+
result["dumped_data"][key] = {
|
|
760
|
+
"rows": table_rows,
|
|
761
|
+
"csv_path": csv_path,
|
|
762
|
+
"columns": table_columns,
|
|
763
|
+
"row_count": len(table_rows),
|
|
654
764
|
}
|
|
655
765
|
|
|
656
766
|
# NEW: Detect SQL injection confirmation
|
|
657
767
|
# Look for "Parameter: X (GET)" and "Type: boolean-based|error-based|time-based|UNION"
|
|
658
|
-
injection_param_pattern =
|
|
768
|
+
injection_param_pattern = (
|
|
769
|
+
r"Parameter:\s+([\w\-]+)\s+\((GET|POST|COOKIE|URI|User-Agent|Referer|Host)\)"
|
|
770
|
+
)
|
|
659
771
|
injection_type_patterns = [
|
|
660
|
-
r
|
|
661
|
-
r
|
|
662
|
-
r
|
|
663
|
-
r
|
|
664
|
-
r
|
|
772
|
+
r"Type:\s+boolean-based blind",
|
|
773
|
+
r"Type:\s+error-based",
|
|
774
|
+
r"Type:\s+time-based blind",
|
|
775
|
+
r"Type:\s+UNION query",
|
|
776
|
+
r"Type:\s+stacked queries",
|
|
665
777
|
]
|
|
666
778
|
|
|
667
779
|
# Search for injection parameter
|
|
668
780
|
param_match = re.search(injection_param_pattern, output)
|
|
669
781
|
if param_match:
|
|
670
|
-
result[
|
|
671
|
-
|
|
782
|
+
result["injectable_parameter"] = param_match.group(1)
|
|
783
|
+
|
|
672
784
|
# Check if any injection type is confirmed
|
|
673
785
|
for pattern in injection_type_patterns:
|
|
674
786
|
if re.search(pattern, output, re.IGNORECASE):
|
|
675
|
-
result[
|
|
787
|
+
result["sql_injection_confirmed"] = True
|
|
676
788
|
break
|
|
677
789
|
|
|
678
790
|
# Add enumeration flags for tool chaining
|
|
679
|
-
if result[
|
|
680
|
-
result[
|
|
681
|
-
if result[
|
|
682
|
-
result[
|
|
683
|
-
if result[
|
|
684
|
-
result[
|
|
791
|
+
if result["databases"]:
|
|
792
|
+
result["databases_enumerated"] = True
|
|
793
|
+
if result["tables"]:
|
|
794
|
+
result["tables_enumerated"] = True
|
|
795
|
+
if result["columns"]:
|
|
796
|
+
result["columns_enumerated"] = True
|
|
685
797
|
|
|
686
798
|
# POST-PROCESSING: Select the BEST injection point
|
|
687
799
|
# Prefer error-based over UNION (error-based is more reliable with redirects)
|
|
688
|
-
if result[
|
|
800
|
+
if result["all_injection_points"] and len(result["all_injection_points"]) > 1:
|
|
801
|
+
|
|
689
802
|
def score_injection_point(ip):
|
|
690
803
|
"""Score injection points - higher is better."""
|
|
691
804
|
score = 0
|
|
692
|
-
techniques = ip.get(
|
|
805
|
+
techniques = ip.get("techniques", [])
|
|
693
806
|
for tech in techniques:
|
|
694
|
-
if
|
|
807
|
+
if "error-based" in tech:
|
|
695
808
|
score += 100 # Best - works despite redirects
|
|
696
|
-
elif
|
|
809
|
+
elif "boolean-based" in tech:
|
|
697
810
|
score += 50
|
|
698
|
-
elif
|
|
811
|
+
elif "stacked" in tech:
|
|
699
812
|
score += 40
|
|
700
|
-
elif
|
|
813
|
+
elif "time-based" in tech:
|
|
701
814
|
score += 20
|
|
702
|
-
elif
|
|
815
|
+
elif "UNION" in tech:
|
|
703
816
|
score += 10 # Least reliable with redirects
|
|
704
817
|
return score
|
|
705
818
|
|
|
706
819
|
# Sort by score (highest first) and pick the best
|
|
707
|
-
sorted_points = sorted(
|
|
820
|
+
sorted_points = sorted(
|
|
821
|
+
result["all_injection_points"], key=score_injection_point, reverse=True
|
|
822
|
+
)
|
|
708
823
|
best = sorted_points[0]
|
|
709
824
|
|
|
710
825
|
# Update the primary injection point to the best one
|
|
711
|
-
result[
|
|
712
|
-
result[
|
|
713
|
-
result[
|
|
714
|
-
if best[
|
|
715
|
-
result[
|
|
826
|
+
result["injectable_url"] = best["url"]
|
|
827
|
+
result["injectable_parameter"] = best["parameter"]
|
|
828
|
+
result["injectable_method"] = best["method"]
|
|
829
|
+
if best["post_data"]:
|
|
830
|
+
result["injectable_post_data"] = best["post_data"]
|
|
716
831
|
|
|
717
832
|
return result
|
|
718
833
|
|
|
@@ -724,24 +839,35 @@ def get_sqli_stats(parsed: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
724
839
|
Returns:
|
|
725
840
|
Dict with counts and summary info
|
|
726
841
|
"""
|
|
727
|
-
sqli_count = sum(
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
842
|
+
sqli_count = sum(
|
|
843
|
+
1
|
|
844
|
+
for v in parsed.get("vulnerabilities", [])
|
|
845
|
+
if v.get("vuln_type") == "sqli" and v.get("injectable")
|
|
846
|
+
)
|
|
847
|
+
|
|
848
|
+
xss_count = sum(
|
|
849
|
+
1 for v in parsed.get("vulnerabilities", []) if v.get("vuln_type") == "xss"
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
fi_count = sum(
|
|
853
|
+
1
|
|
854
|
+
for v in parsed.get("vulnerabilities", [])
|
|
855
|
+
if v.get("vuln_type") == "file_inclusion"
|
|
856
|
+
)
|
|
735
857
|
|
|
736
858
|
return {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
859
|
+
"total_vulns": len(parsed.get("vulnerabilities", [])),
|
|
860
|
+
"sqli_confirmed": sqli_count,
|
|
861
|
+
"xss_possible": xss_count,
|
|
862
|
+
"fi_possible": fi_count,
|
|
863
|
+
"urls_tested": len(parsed.get("urls_tested", [])),
|
|
864
|
+
"databases_found": len(parsed.get("databases", [])),
|
|
865
|
+
"tables_found": sum(
|
|
866
|
+
len(tables) for tables in parsed.get("tables", {}).values()
|
|
867
|
+
),
|
|
868
|
+
"columns_found": sum(len(cols) for cols in parsed.get("columns", {}).values()),
|
|
869
|
+
"dumped_tables": len(parsed.get("dumped_data", {})),
|
|
870
|
+
"dumped_rows": sum(
|
|
871
|
+
data.get("row_count", 0) for data in parsed.get("dumped_data", {}).values()
|
|
872
|
+
),
|
|
747
873
|
}
|