streamlit-launcher 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- streamlit_launcher/.env +5 -0
- streamlit_launcher/.pypirc +8 -0
- streamlit_launcher/LICENSE +31 -0
- streamlit_launcher/README.md +281 -0
- streamlit_launcher/Screenshot 2025-09-12 185242.png +0 -0
- streamlit_launcher/Screenshot 2025-09-12 185705.png +0 -0
- streamlit_launcher/Screenshot 2025-09-12 185725.png +0 -0
- streamlit_launcher/Screenshot 2025-09-12 185806.png +0 -0
- streamlit_launcher/__init__.py +2 -0
- streamlit_launcher/anime.png +0 -0
- streamlit_launcher/cli.py +8 -0
- streamlit_launcher/dashboard.py +848 -0
- streamlit_launcher/gui.py +368 -0
- streamlit_launcher/img.ico +0 -0
- streamlit_launcher/kemas.png +0 -0
- streamlit_launcher/mas.py +1938 -0
- streamlit_launcher/requirements.txt +3 -0
- streamlit_launcher/setup.py +45 -0
- streamlit_launcher/treamlit.jpg +0 -0
- streamlit_launcher/ui.png +0 -0
- streamlit_launcher/upload_to_pypi.bat +18 -0
- streamlit_launcher-2.0.0.dist-info/METADATA +313 -0
- streamlit_launcher-2.0.0.dist-info/RECORD +27 -0
- streamlit_launcher-2.0.0.dist-info/WHEEL +5 -0
- streamlit_launcher-2.0.0.dist-info/entry_points.txt +2 -0
- streamlit_launcher-2.0.0.dist-info/licenses/LICENSE +31 -0
- streamlit_launcher-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1938 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
from tkinter import ttk, messagebox, scrolledtext, filedialog
|
|
3
|
+
import requests
|
|
4
|
+
from urllib.parse import urlparse, parse_qs, urljoin
|
|
5
|
+
import socket
|
|
6
|
+
import ssl
|
|
7
|
+
import datetime
|
|
8
|
+
from bs4 import BeautifulSoup
|
|
9
|
+
import threading
|
|
10
|
+
import re
|
|
11
|
+
import os
|
|
12
|
+
from PIL import Image, ImageTk
|
|
13
|
+
import sys
|
|
14
|
+
import subprocess
|
|
15
|
+
import webbrowser
|
|
16
|
+
import json
|
|
17
|
+
import time
|
|
18
|
+
import xml.etree.ElementTree as ET
|
|
19
|
+
from http.client import HTTPConnection
|
|
20
|
+
import ipaddress
|
|
21
|
+
import whois
|
|
22
|
+
import nmap
|
|
23
|
+
import socket
|
|
24
|
+
import dns
|
|
25
|
+
|
|
26
|
+
class AdvancedVulnerabilityScanner:
|
|
27
|
+
def __init__(self, root):
|
|
28
|
+
self.root = root
|
|
29
|
+
self.root.title("Advanced Vulnerability Scanner Pro")
|
|
30
|
+
self.root.geometry("900x700")
|
|
31
|
+
self.root.configure(bg='#f0f0f0')
|
|
32
|
+
self.icon_path = os.path.join(os.path.dirname(__file__), "icon.ico")
|
|
33
|
+
if os.path.exists(self.icon_path):
|
|
34
|
+
self.root.iconbitmap(self.icon_path)
|
|
35
|
+
|
|
36
|
+
# Scan configuration
|
|
37
|
+
self.scan_config = {
|
|
38
|
+
'timeout': 10,
|
|
39
|
+
'max_pages': 20,
|
|
40
|
+
'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
|
41
|
+
'threads': 5
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Configure styles
|
|
45
|
+
self.style = ttk.Style()
|
|
46
|
+
self.style.configure('TFrame', background='#f0f0f0')
|
|
47
|
+
self.style.configure('TLabel', background='#f0f0f0', font=('Arial', 9))
|
|
48
|
+
self.style.configure('TButton', font=('Arial', 9))
|
|
49
|
+
self.style.configure('Treeview', font=('Arial', 9), rowheight=25)
|
|
50
|
+
self.style.configure('TNotebook', background='#f0f0f0')
|
|
51
|
+
self.style.configure('TNotebook.Tab', padding=[10, 5])
|
|
52
|
+
|
|
53
|
+
# Main UI components
|
|
54
|
+
self.create_widgets()
|
|
55
|
+
self.create_menu()
|
|
56
|
+
|
|
57
|
+
# Initialize nmap scanner
|
|
58
|
+
self.nm = nmap.PortScanner() if self.check_nmap_installed() else None
|
|
59
|
+
|
|
60
|
+
def check_nmap_installed(self):
|
|
61
|
+
try:
|
|
62
|
+
subprocess.run(["nmap", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
63
|
+
return True
|
|
64
|
+
except:
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
def create_menu(self):
|
|
68
|
+
menubar = tk.Menu(self.root)
|
|
69
|
+
|
|
70
|
+
# File menu
|
|
71
|
+
file_menu = tk.Menu(menubar, tearoff=0)
|
|
72
|
+
file_menu.add_command(label="Save Report", command=self.save_report)
|
|
73
|
+
file_menu.add_command(label="Load Config", command=self.load_config)
|
|
74
|
+
file_menu.add_command(label="Save Config", command=self.save_config)
|
|
75
|
+
file_menu.add_separator()
|
|
76
|
+
file_menu.add_command(label="Exit", command=self.root.quit)
|
|
77
|
+
menubar.add_cascade(label="File", menu=file_menu)
|
|
78
|
+
|
|
79
|
+
# Tools menu
|
|
80
|
+
tools_menu = tk.Menu(menubar, tearoff=0)
|
|
81
|
+
tools_menu.add_command(label="Port Scanner", command=self.open_port_scanner)
|
|
82
|
+
tools_menu.add_command(label="WHOIS Lookup", command=self.open_whois_tool)
|
|
83
|
+
tools_menu.add_command(label="DNS Lookup", command=self.open_dns_tool)
|
|
84
|
+
tools_menu.add_command(label="Ping Tool", command=self.open_ping_tool)
|
|
85
|
+
menubar.add_cascade(label="Tools", menu=tools_menu)
|
|
86
|
+
|
|
87
|
+
# Help menu
|
|
88
|
+
help_menu = tk.Menu(menubar, tearoff=0)
|
|
89
|
+
help_menu.add_command(label="Documentation", command=self.open_documentation)
|
|
90
|
+
help_menu.add_command(label="About", command=self.show_about)
|
|
91
|
+
menubar.add_cascade(label="Help", menu=help_menu)
|
|
92
|
+
|
|
93
|
+
self.root.config(menu=menubar)
|
|
94
|
+
|
|
95
|
+
def create_widgets(self):
|
|
96
|
+
# Header
|
|
97
|
+
header_frame = ttk.Frame(self.root)
|
|
98
|
+
header_frame.pack(pady=5, fill=tk.X)
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
icon_path = os.path.join(os.path.dirname(__file__), "icon.ico")
|
|
102
|
+
if os.path.exists(icon_path):
|
|
103
|
+
img = Image.open(icon_path)
|
|
104
|
+
img = img.resize((32, 32), Image.LANCZOS)
|
|
105
|
+
self.tk_icon = ImageTk.PhotoImage(img)
|
|
106
|
+
icon_label = ttk.Label(header_frame, image=self.tk_icon)
|
|
107
|
+
icon_label.pack(side=tk.LEFT, padx=(0, 8))
|
|
108
|
+
except Exception:
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
ttk.Label(header_frame, text="Advanced Vulnerability Scanner Pro",
|
|
112
|
+
font=('Arial', 14, 'bold')).pack(side=tk.LEFT)
|
|
113
|
+
|
|
114
|
+
# Input section
|
|
115
|
+
input_frame = ttk.Frame(self.root)
|
|
116
|
+
input_frame.pack(pady=5, fill=tk.X, padx=10)
|
|
117
|
+
|
|
118
|
+
ttk.Label(input_frame, text="Target URL/IP:").grid(row=0, column=0, sticky=tk.W)
|
|
119
|
+
self.url_entry = ttk.Entry(input_frame, width=50)
|
|
120
|
+
self.url_entry.grid(row=0, column=1, padx=5)
|
|
121
|
+
self.url_entry.insert(0, "https://")
|
|
122
|
+
|
|
123
|
+
self.scan_btn = ttk.Button(input_frame, text="Scan", command=self.start_scan)
|
|
124
|
+
self.scan_btn.grid(row=0, column=2, padx=5)
|
|
125
|
+
|
|
126
|
+
# Quick tools buttons
|
|
127
|
+
self.wsl_btn = ttk.Button(input_frame, text="WSL", command=self.open_wsl_linux)
|
|
128
|
+
self.wsl_btn.grid(row=0, column=3, padx=5)
|
|
129
|
+
|
|
130
|
+
self.terminal_btn = ttk.Button(input_frame, text="Terminal", command=self.open_terminal)
|
|
131
|
+
self.terminal_btn.grid(row=0, column=4, padx=5)
|
|
132
|
+
|
|
133
|
+
# Scan options notebook
|
|
134
|
+
options_notebook = ttk.Notebook(self.root)
|
|
135
|
+
options_notebook.pack(pady=5, fill=tk.X, padx=10)
|
|
136
|
+
|
|
137
|
+
# Basic scan options
|
|
138
|
+
basic_frame = ttk.Frame(options_notebook)
|
|
139
|
+
self.sql_var = tk.BooleanVar(value=True)
|
|
140
|
+
self.xss_var = tk.BooleanVar(value=True)
|
|
141
|
+
self.headers_var = tk.BooleanVar(value=True)
|
|
142
|
+
self.ssl_var = tk.BooleanVar(value=True)
|
|
143
|
+
self.crawl_var = tk.BooleanVar(value=True)
|
|
144
|
+
|
|
145
|
+
ttk.Checkbutton(basic_frame, text="SQL Injection", variable=self.sql_var).grid(row=0, column=0, sticky=tk.W)
|
|
146
|
+
ttk.Checkbutton(basic_frame, text="XSS", variable=self.xss_var).grid(row=0, column=1, sticky=tk.W)
|
|
147
|
+
ttk.Checkbutton(basic_frame, text="Headers", variable=self.headers_var).grid(row=0, column=2, sticky=tk.W)
|
|
148
|
+
ttk.Checkbutton(basic_frame, text="SSL/TLS", variable=self.ssl_var).grid(row=1, column=0, sticky=tk.W)
|
|
149
|
+
ttk.Checkbutton(basic_frame, text="Crawl Pages", variable=self.crawl_var).grid(row=1, column=1, sticky=tk.W)
|
|
150
|
+
|
|
151
|
+
options_notebook.add(basic_frame, text="Basic")
|
|
152
|
+
|
|
153
|
+
# Advanced scan options
|
|
154
|
+
adv_frame = ttk.Frame(options_notebook)
|
|
155
|
+
self.csrf_var = tk.BooleanVar(value=True)
|
|
156
|
+
self.dir_trav_var = tk.BooleanVar(value=True)
|
|
157
|
+
self.cmdi_var = tk.BooleanVar(value=True)
|
|
158
|
+
self.file_exp_var = tk.BooleanVar(value=True)
|
|
159
|
+
self.ssrf_var = tk.BooleanVar(value=False)
|
|
160
|
+
self.xxe_var = tk.BooleanVar(value=False)
|
|
161
|
+
self.idor_var = tk.BooleanVar(value=False)
|
|
162
|
+
|
|
163
|
+
ttk.Checkbutton(adv_frame, text="CSRF", variable=self.csrf_var).grid(row=0, column=0, sticky=tk.W)
|
|
164
|
+
ttk.Checkbutton(adv_frame, text="Directory Traversal", variable=self.dir_trav_var).grid(row=0, column=1, sticky=tk.W)
|
|
165
|
+
ttk.Checkbutton(adv_frame, text="Command Injection", variable=self.cmdi_var).grid(row=0, column=2, sticky=tk.W)
|
|
166
|
+
ttk.Checkbutton(adv_frame, text="Exposed Files", variable=self.file_exp_var).grid(row=1, column=0, sticky=tk.W)
|
|
167
|
+
ttk.Checkbutton(adv_frame, text="SSRF", variable=self.ssrf_var).grid(row=1, column=1, sticky=tk.W)
|
|
168
|
+
ttk.Checkbutton(adv_frame, text="XXE", variable=self.xxe_var).grid(row=1, column=2, sticky=tk.W)
|
|
169
|
+
ttk.Checkbutton(adv_frame, text="IDOR", variable=self.idor_var).grid(row=2, column=0, sticky=tk.W)
|
|
170
|
+
|
|
171
|
+
options_notebook.add(adv_frame, text="Advanced")
|
|
172
|
+
|
|
173
|
+
# Network scan options
|
|
174
|
+
net_frame = ttk.Frame(options_notebook)
|
|
175
|
+
self.port_scan_var = tk.BooleanVar(value=False)
|
|
176
|
+
self.os_detect_var = tk.BooleanVar(value=False)
|
|
177
|
+
self.service_scan_var = tk.BooleanVar(value=False)
|
|
178
|
+
|
|
179
|
+
ttk.Checkbutton(net_frame, text="Port Scan", variable=self.port_scan_var).grid(row=0, column=0, sticky=tk.W)
|
|
180
|
+
ttk.Checkbutton(net_frame, text="OS Detection", variable=self.os_detect_var).grid(row=0, column=1, sticky=tk.W)
|
|
181
|
+
ttk.Checkbutton(net_frame, text="Service Scan", variable=self.service_scan_var).grid(row=0, column=2, sticky=tk.W)
|
|
182
|
+
|
|
183
|
+
options_notebook.add(net_frame, text="Network")
|
|
184
|
+
|
|
185
|
+
# Progress bar
|
|
186
|
+
self.progress = ttk.Progressbar(self.root, orient=tk.HORIZONTAL, mode='determinate')
|
|
187
|
+
self.progress.pack(pady=5, fill=tk.X, padx=10)
|
|
188
|
+
self.status = ttk.Label(self.root, text="Ready to scan")
|
|
189
|
+
self.status.pack()
|
|
190
|
+
|
|
191
|
+
# Results display
|
|
192
|
+
notebook = ttk.Notebook(self.root)
|
|
193
|
+
notebook.pack(pady=5, fill=tk.BOTH, expand=True, padx=10)
|
|
194
|
+
|
|
195
|
+
# Vulnerabilities tab
|
|
196
|
+
vuln_frame = ttk.Frame(notebook)
|
|
197
|
+
self.results_tree = ttk.Treeview(vuln_frame, columns=('severity', 'type', 'details'), show='headings')
|
|
198
|
+
self.results_tree.heading('severity', text='Severity')
|
|
199
|
+
self.results_tree.heading('type', text='Vulnerability')
|
|
200
|
+
self.results_tree.heading('details', text='Details')
|
|
201
|
+
self.results_tree.column('severity', width=80, anchor=tk.CENTER)
|
|
202
|
+
self.results_tree.column('type', width=150)
|
|
203
|
+
self.results_tree.column('details', width=500)
|
|
204
|
+
|
|
205
|
+
scrollbar = ttk.Scrollbar(vuln_frame, orient=tk.VERTICAL, command=self.results_tree.yview)
|
|
206
|
+
self.results_tree.configure(yscroll=scrollbar.set)
|
|
207
|
+
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
|
208
|
+
self.results_tree.pack(fill=tk.BOTH, expand=True)
|
|
209
|
+
|
|
210
|
+
# Add context menu for vulnerabilities
|
|
211
|
+
self.vuln_menu = tk.Menu(self.root, tearoff=0)
|
|
212
|
+
self.vuln_menu.add_command(label="View Details", command=self.show_vuln_details)
|
|
213
|
+
self.vuln_menu.add_command(label="Copy Details", command=self.copy_vuln_details)
|
|
214
|
+
self.vuln_menu.add_command(label="Export to Report", command=self.export_vuln_to_report)
|
|
215
|
+
self.results_tree.bind("<Button-3>", self.show_vuln_context_menu)
|
|
216
|
+
|
|
217
|
+
notebook.add(vuln_frame, text='Vulnerabilities')
|
|
218
|
+
|
|
219
|
+
# Details tab
|
|
220
|
+
details_frame = ttk.Frame(notebook)
|
|
221
|
+
self.details_text = scrolledtext.ScrolledText(details_frame, wrap=tk.WORD, font=('Consolas', 9))
|
|
222
|
+
self.details_text.pack(fill=tk.BOTH, expand=True)
|
|
223
|
+
notebook.add(details_frame, text='Scan Details')
|
|
224
|
+
|
|
225
|
+
# Exposed Files tab
|
|
226
|
+
files_frame = ttk.Frame(notebook)
|
|
227
|
+
self.files_tree = ttk.Treeview(files_frame, columns=('type', 'url', 'status', 'size'), show='headings')
|
|
228
|
+
self.files_tree.heading('type', text='Type')
|
|
229
|
+
self.files_tree.heading('url', text='URL')
|
|
230
|
+
self.files_tree.heading('status', text='Status')
|
|
231
|
+
self.files_tree.heading('size', text='Size')
|
|
232
|
+
self.files_tree.column('type', width=100)
|
|
233
|
+
self.files_tree.column('url', width=350)
|
|
234
|
+
self.files_tree.column('status', width=80)
|
|
235
|
+
self.files_tree.column('size', width=80)
|
|
236
|
+
|
|
237
|
+
files_scrollbar = ttk.Scrollbar(files_frame, orient=tk.VERTICAL, command=self.files_tree.yview)
|
|
238
|
+
self.files_tree.configure(yscroll=files_scrollbar.set)
|
|
239
|
+
files_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
|
240
|
+
self.files_tree.pack(fill=tk.BOTH, expand=True)
|
|
241
|
+
|
|
242
|
+
# Add context menu for files tree
|
|
243
|
+
self.files_menu = tk.Menu(self.root, tearoff=0)
|
|
244
|
+
self.files_menu.add_command(label="Open in Browser", command=self.open_selected_file)
|
|
245
|
+
self.files_menu.add_command(label="Copy URL", command=self.copy_file_url)
|
|
246
|
+
self.files_menu.add_command(label="Download File", command=self.download_selected_file)
|
|
247
|
+
self.files_tree.bind("<Button-3>", self.show_files_context_menu)
|
|
248
|
+
|
|
249
|
+
notebook.add(files_frame, text='Exposed Files')
|
|
250
|
+
|
|
251
|
+
# Network Info tab
|
|
252
|
+
network_frame = ttk.Frame(notebook)
|
|
253
|
+
self.network_tree = ttk.Treeview(network_frame, columns=('service', 'port', 'state', 'version'), show='headings')
|
|
254
|
+
self.network_tree.heading('service', text='Service')
|
|
255
|
+
self.network_tree.heading('port', text='Port')
|
|
256
|
+
self.network_tree.heading('state', text='State')
|
|
257
|
+
self.network_tree.heading('version', text='Version')
|
|
258
|
+
self.network_tree.column('service', width=150)
|
|
259
|
+
self.network_tree.column('port', width=80)
|
|
260
|
+
self.network_tree.column('state', width=80)
|
|
261
|
+
self.network_tree.column('version', width=200)
|
|
262
|
+
|
|
263
|
+
network_scrollbar = ttk.Scrollbar(network_frame, orient=tk.VERTICAL, command=self.network_tree.yview)
|
|
264
|
+
self.network_tree.configure(yscroll=network_scrollbar.set)
|
|
265
|
+
network_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
|
266
|
+
self.network_tree.pack(fill=tk.BOTH, expand=True)
|
|
267
|
+
|
|
268
|
+
notebook.add(network_frame, text='Network Info')
|
|
269
|
+
|
|
270
|
+
# Configure tags for severity colors
|
|
271
|
+
self.results_tree.tag_configure('critical', background='#ff9999', foreground='#800000')
|
|
272
|
+
self.results_tree.tag_configure('high', background='#ffcccc')
|
|
273
|
+
self.results_tree.tag_configure('medium', background='#fff3cd')
|
|
274
|
+
self.results_tree.tag_configure('low', background='#d4edda')
|
|
275
|
+
self.results_tree.tag_configure('info', background='#d1ecf1')
|
|
276
|
+
|
|
277
|
+
# Configure tags for network info
|
|
278
|
+
self.network_tree.tag_configure('open', background='#ffcccc')
|
|
279
|
+
self.network_tree.tag_configure('filtered', background='#fff3cd')
|
|
280
|
+
self.network_tree.tag_configure('closed', background='#d4edda')
|
|
281
|
+
|
|
282
|
+
def show_vuln_context_menu(self, event):
|
|
283
|
+
item = self.results_tree.identify_row(event.y)
|
|
284
|
+
if item:
|
|
285
|
+
self.results_tree.selection_set(item)
|
|
286
|
+
self.vuln_menu.post(event.x_root, event.y_root)
|
|
287
|
+
|
|
288
|
+
def show_vuln_details(self):
|
|
289
|
+
selected = self.results_tree.selection()
|
|
290
|
+
if selected:
|
|
291
|
+
details = self.results_tree.item(selected[0])['values'][2]
|
|
292
|
+
self.show_details_window("Vulnerability Details", details)
|
|
293
|
+
|
|
294
|
+
def copy_vuln_details(self):
|
|
295
|
+
selected = self.results_tree.selection()
|
|
296
|
+
if selected:
|
|
297
|
+
details = self.results_tree.item(selected[0])['values'][2]
|
|
298
|
+
self.root.clipboard_clear()
|
|
299
|
+
self.root.clipboard_append(details)
|
|
300
|
+
messagebox.showinfo("Copied", "Vulnerability details copied to clipboard")
|
|
301
|
+
|
|
302
|
+
def export_vuln_to_report(self):
|
|
303
|
+
selected = self.results_tree.selection()
|
|
304
|
+
if selected:
|
|
305
|
+
item = self.results_tree.item(selected[0])
|
|
306
|
+
severity, vuln_type, details = item['values']
|
|
307
|
+
|
|
308
|
+
report = f"Vulnerability Report\n\n"
|
|
309
|
+
report += f"Severity: {severity}\n"
|
|
310
|
+
report += f"Type: {vuln_type}\n"
|
|
311
|
+
report += f"Details:\n{details}\n"
|
|
312
|
+
|
|
313
|
+
filename = filedialog.asksaveasfilename(
|
|
314
|
+
defaultextension=".txt",
|
|
315
|
+
filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")],
|
|
316
|
+
title="Save Report As"
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
if filename:
|
|
320
|
+
with open(filename, 'w') as f:
|
|
321
|
+
f.write(report)
|
|
322
|
+
messagebox.showinfo("Saved", f"Report saved to {filename}")
|
|
323
|
+
|
|
324
|
+
def show_details_window(self, title, content):
|
|
325
|
+
window = tk.Toplevel(self.root)
|
|
326
|
+
window.title(title)
|
|
327
|
+
window.geometry("600x400")
|
|
328
|
+
|
|
329
|
+
text = scrolledtext.ScrolledText(window, wrap=tk.WORD, font=('Consolas', 9))
|
|
330
|
+
text.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
|
331
|
+
text.insert(tk.END, content)
|
|
332
|
+
text.config(state=tk.DISABLED)
|
|
333
|
+
|
|
334
|
+
btn_frame = ttk.Frame(window)
|
|
335
|
+
btn_frame.pack(pady=5)
|
|
336
|
+
|
|
337
|
+
ttk.Button(btn_frame, text="Copy", command=lambda: self.copy_to_clipboard(content)).pack(side=tk.LEFT, padx=5)
|
|
338
|
+
ttk.Button(btn_frame, text="Close", command=window.destroy).pack(side=tk.LEFT, padx=5)
|
|
339
|
+
|
|
340
|
+
def copy_to_clipboard(self, text):
|
|
341
|
+
self.root.clipboard_clear()
|
|
342
|
+
self.root.clipboard_append(text)
|
|
343
|
+
messagebox.showinfo("Copied", "Content copied to clipboard")
|
|
344
|
+
|
|
345
|
+
def show_files_context_menu(self, event):
|
|
346
|
+
item = self.files_tree.identify_row(event.y)
|
|
347
|
+
if item:
|
|
348
|
+
self.files_tree.selection_set(item)
|
|
349
|
+
self.files_menu.post(event.x_root, event.y_root)
|
|
350
|
+
|
|
351
|
+
def open_selected_file(self):
|
|
352
|
+
selected = self.files_tree.selection()
|
|
353
|
+
if selected:
|
|
354
|
+
url = self.files_tree.item(selected[0])['values'][1]
|
|
355
|
+
webbrowser.open(url)
|
|
356
|
+
|
|
357
|
+
def download_selected_file(self):
|
|
358
|
+
selected = self.files_tree.selection()
|
|
359
|
+
if selected:
|
|
360
|
+
url = self.files_tree.item(selected[0])['values'][1]
|
|
361
|
+
try:
|
|
362
|
+
response = requests.get(url, stream=True, timeout=10)
|
|
363
|
+
if response.status_code == 200:
|
|
364
|
+
filename = filedialog.asksaveasfilename(
|
|
365
|
+
initialfile=os.path.basename(urlparse(url).path),
|
|
366
|
+
title="Save File As"
|
|
367
|
+
)
|
|
368
|
+
if filename:
|
|
369
|
+
with open(filename, 'wb') as f:
|
|
370
|
+
for chunk in response.iter_content(1024):
|
|
371
|
+
f.write(chunk)
|
|
372
|
+
messagebox.showinfo("Download Complete", f"File saved to {filename}")
|
|
373
|
+
except Exception as e:
|
|
374
|
+
messagebox.showerror("Download Error", f"Failed to download file: {str(e)}")
|
|
375
|
+
|
|
376
|
+
def copy_file_url(self):
|
|
377
|
+
selected = self.files_tree.selection()
|
|
378
|
+
if selected:
|
|
379
|
+
url = self.files_tree.item(selected[0])['values'][1]
|
|
380
|
+
self.root.clipboard_clear()
|
|
381
|
+
self.root.clipboard_append(url)
|
|
382
|
+
messagebox.showinfo("Copied", "URL copied to clipboard")
|
|
383
|
+
|
|
384
|
+
def open_wsl_linux(self):
|
|
385
|
+
"""Open WSL (Windows Subsystem for Linux) terminal"""
|
|
386
|
+
try:
|
|
387
|
+
subprocess.Popen("wsl", creationflags=subprocess.CREATE_NEW_CONSOLE)
|
|
388
|
+
messagebox.showinfo("WSL", "WSL terminal opened successfully!")
|
|
389
|
+
except Exception as e:
|
|
390
|
+
messagebox.showerror("Error", f"Failed to open WSL: {str(e)}")
|
|
391
|
+
|
|
392
|
+
def open_terminal(self):
|
|
393
|
+
"""Open system terminal"""
|
|
394
|
+
try:
|
|
395
|
+
if sys.platform == "win32":
|
|
396
|
+
subprocess.Popen("cmd", creationflags=subprocess.CREATE_NEW_CONSOLE)
|
|
397
|
+
elif sys.platform == "linux":
|
|
398
|
+
subprocess.Popen("x-terminal-emulator")
|
|
399
|
+
elif sys.platform == "darwin":
|
|
400
|
+
subprocess.Popen("open -a Terminal .")
|
|
401
|
+
messagebox.showinfo("Terminal", "Terminal opened successfully!")
|
|
402
|
+
except Exception as e:
|
|
403
|
+
messagebox.showerror("Error", f"Failed to open terminal: {str(e)}")
|
|
404
|
+
|
|
405
|
+
def save_report(self):
|
|
406
|
+
"""Save scan results to a file"""
|
|
407
|
+
try:
|
|
408
|
+
filename = filedialog.asksaveasfilename(
|
|
409
|
+
defaultextension=".txt",
|
|
410
|
+
filetypes=[("Text Files", "*.txt"), ("HTML Files", "*.html"), ("All Files", "*.*")],
|
|
411
|
+
title="Save Report As"
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
if filename:
|
|
415
|
+
if filename.endswith('.html'):
|
|
416
|
+
self.save_html_report(filename)
|
|
417
|
+
else:
|
|
418
|
+
self.save_text_report(filename)
|
|
419
|
+
|
|
420
|
+
messagebox.showinfo("Saved", f"Report saved to {filename}")
|
|
421
|
+
except Exception as e:
|
|
422
|
+
messagebox.showerror("Error", f"Failed to save report: {str(e)}")
|
|
423
|
+
|
|
424
|
+
def save_text_report(self, filename):
|
|
425
|
+
"""Save report as plain text"""
|
|
426
|
+
with open(filename, 'w') as f:
|
|
427
|
+
# Write header
|
|
428
|
+
f.write("="*80 + "\n")
|
|
429
|
+
f.write("Advanced Vulnerability Scanner Report\n")
|
|
430
|
+
f.write(f"Generated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
|
431
|
+
f.write(f"Target: {self.url_entry.get()}\n")
|
|
432
|
+
f.write("="*80 + "\n\n")
|
|
433
|
+
|
|
434
|
+
# Write vulnerabilities
|
|
435
|
+
f.write("Vulnerabilities:\n")
|
|
436
|
+
f.write("-"*80 + "\n")
|
|
437
|
+
for item in self.results_tree.get_children():
|
|
438
|
+
severity, vuln_type, details = self.results_tree.item(item)['values']
|
|
439
|
+
f.write(f"[{severity}] {vuln_type}:\n{details}\n\n")
|
|
440
|
+
|
|
441
|
+
# Write exposed files
|
|
442
|
+
f.write("\nExposed Files:\n")
|
|
443
|
+
f.write("-"*80 + "\n")
|
|
444
|
+
for item in self.files_tree.get_children():
|
|
445
|
+
file_type, url, status, size = self.files_tree.item(item)['values']
|
|
446
|
+
f.write(f"{file_type}: {url} ({status}, {size})\n")
|
|
447
|
+
|
|
448
|
+
# Write network info
|
|
449
|
+
if self.network_tree.get_children():
|
|
450
|
+
f.write("\nNetwork Information:\n")
|
|
451
|
+
f.write("-"*80 + "\n")
|
|
452
|
+
for item in self.network_tree.get_children():
|
|
453
|
+
service, port, state, version = self.network_tree.item(item)['values']
|
|
454
|
+
f.write(f"{service} on port {port} ({state}): {version}\n")
|
|
455
|
+
|
|
456
|
+
# Write scan details
|
|
457
|
+
f.write("\nScan Details:\n")
|
|
458
|
+
f.write("-"*80 + "\n")
|
|
459
|
+
f.write(self.details_text.get("1.0", tk.END))
|
|
460
|
+
|
|
461
|
+
def save_html_report(self, filename):
|
|
462
|
+
"""Save report as HTML"""
|
|
463
|
+
with open(filename, 'w') as f:
|
|
464
|
+
f.write("<!DOCTYPE html>\n<html>\n<head>\n")
|
|
465
|
+
f.write("<title>Vulnerability Scan Report</title>\n")
|
|
466
|
+
f.write("<style>\n")
|
|
467
|
+
f.write("body { font-family: Arial, sans-serif; line-height: 1.6; }\n")
|
|
468
|
+
f.write("h1, h2 { color: #333; }\n")
|
|
469
|
+
f.write(".critical { background-color: #ff9999; color: #800000; padding: 2px 5px; }\n")
|
|
470
|
+
f.write(".high { background-color: #ffcccc; padding: 2px 5px; }\n")
|
|
471
|
+
f.write(".medium { background-color: #fff3cd; padding: 2px 5px; }\n")
|
|
472
|
+
f.write(".low { background-color: #d4edda; padding: 2px 5px; }\n")
|
|
473
|
+
f.write(".info { background-color: #d1ecf1; padding: 2px 5px; }\n")
|
|
474
|
+
f.write("pre { background-color: #f5f5f5; padding: 10px; border-radius: 3px; }\n")
|
|
475
|
+
f.write("table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }\n")
|
|
476
|
+
f.write("th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }\n")
|
|
477
|
+
f.write("th { background-color: #f2f2f2; }\n")
|
|
478
|
+
f.write("</style>\n</head>\n<body>\n")
|
|
479
|
+
|
|
480
|
+
# Write header
|
|
481
|
+
f.write("<h1>Advanced Vulnerability Scanner Report</h1>\n")
|
|
482
|
+
f.write(f"<p><strong>Generated:</strong> {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>\n")
|
|
483
|
+
f.write(f"<p><strong>Target:</strong> {self.url_entry.get()}</p>\n")
|
|
484
|
+
f.write("<hr>\n")
|
|
485
|
+
|
|
486
|
+
# Write vulnerabilities
|
|
487
|
+
f.write("<h2>Vulnerabilities</h2>\n")
|
|
488
|
+
f.write("<table>\n")
|
|
489
|
+
f.write("<tr><th>Severity</th><th>Type</th><th>Details</th></tr>\n")
|
|
490
|
+
for item in self.results_tree.get_children():
|
|
491
|
+
severity, vuln_type, details = self.results_tree.item(item)['values']
|
|
492
|
+
f.write(f"<tr><td><span class='{severity.lower()}'>{severity}</span></td>")
|
|
493
|
+
f.write(f"<td>{vuln_type}</td><td><pre>{details}</pre></td></tr>\n")
|
|
494
|
+
f.write("</table>\n")
|
|
495
|
+
|
|
496
|
+
# Write exposed files
|
|
497
|
+
f.write("<h2>Exposed Files</h2>\n")
|
|
498
|
+
f.write("<table>\n")
|
|
499
|
+
f.write("<tr><th>Type</th><th>URL</th><th>Status</th><th>Size</th></tr>\n")
|
|
500
|
+
for item in self.files_tree.get_children():
|
|
501
|
+
file_type, url, status, size = self.files_tree.item(item)['values']
|
|
502
|
+
f.write(f"<tr><td>{file_type}</td><td><a href='{url}' target='_blank'>{url}</a></td>")
|
|
503
|
+
f.write(f"<td>{status}</td><td>{size}</td></tr>\n")
|
|
504
|
+
f.write("</table>\n")
|
|
505
|
+
|
|
506
|
+
# Write network info
|
|
507
|
+
if self.network_tree.get_children():
|
|
508
|
+
f.write("<h2>Network Information</h2>\n")
|
|
509
|
+
f.write("<table>\n")
|
|
510
|
+
f.write("<tr><th>Service</th><th>Port</th><th>State</th><th>Version</th></tr>\n")
|
|
511
|
+
for item in self.network_tree.get_children():
|
|
512
|
+
service, port, state, version = self.network_tree.item(item)['values']
|
|
513
|
+
f.write(f"<tr><td>{service}</td><td>{port}</td><td>{state}</td><td>{version}</td></tr>\n")
|
|
514
|
+
f.write("</table>\n")
|
|
515
|
+
|
|
516
|
+
# Write scan details
|
|
517
|
+
f.write("<h2>Scan Details</h2>\n")
|
|
518
|
+
f.write(f"<pre>{self.details_text.get('1.0', tk.END)}</pre>\n")
|
|
519
|
+
|
|
520
|
+
f.write("</body>\n</html>")
|
|
521
|
+
|
|
522
|
+
def load_config(self):
|
|
523
|
+
"""Load scan configuration from file"""
|
|
524
|
+
try:
|
|
525
|
+
filename = filedialog.askopenfilename(
|
|
526
|
+
filetypes=[("JSON Files", "*.json"), ("All Files", "*.*")],
|
|
527
|
+
title="Load Configuration"
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
if filename:
|
|
531
|
+
with open(filename, 'r') as f:
|
|
532
|
+
config = json.load(f)
|
|
533
|
+
|
|
534
|
+
# Update UI with loaded configuration
|
|
535
|
+
self.url_entry.delete(0, tk.END)
|
|
536
|
+
self.url_entry.insert(0, config.get('target_url', ''))
|
|
537
|
+
|
|
538
|
+
# Update scan options
|
|
539
|
+
self.sql_var.set(config.get('sql_injection', True))
|
|
540
|
+
self.xss_var.set(config.get('xss', True))
|
|
541
|
+
self.headers_var.set(config.get('headers', True))
|
|
542
|
+
self.ssl_var.set(config.get('ssl', True))
|
|
543
|
+
self.crawl_var.set(config.get('crawl', True))
|
|
544
|
+
self.csrf_var.set(config.get('csrf', True))
|
|
545
|
+
self.dir_trav_var.set(config.get('dir_traversal', True))
|
|
546
|
+
self.cmdi_var.set(config.get('command_injection', True))
|
|
547
|
+
self.file_exp_var.set(config.get('exposed_files', True))
|
|
548
|
+
self.ssrf_var.set(config.get('ssrf', False))
|
|
549
|
+
self.xxe_var.set(config.get('xxe', False))
|
|
550
|
+
self.idor_var.set(config.get('idor', False))
|
|
551
|
+
self.port_scan_var.set(config.get('port_scan', False))
|
|
552
|
+
self.os_detect_var.set(config.get('os_detection', False))
|
|
553
|
+
self.service_scan_var.set(config.get('service_scan', False))
|
|
554
|
+
|
|
555
|
+
# Update scan configuration
|
|
556
|
+
self.scan_config.update(config.get('scan_config', {}))
|
|
557
|
+
|
|
558
|
+
messagebox.showinfo("Loaded", "Configuration loaded successfully!")
|
|
559
|
+
except Exception as e:
|
|
560
|
+
messagebox.showerror("Error", f"Failed to load configuration: {str(e)}")
|
|
561
|
+
|
|
562
|
+
def save_config(self):
|
|
563
|
+
"""Save current configuration to file"""
|
|
564
|
+
try:
|
|
565
|
+
filename = filedialog.asksaveasfilename(
|
|
566
|
+
defaultextension=".json",
|
|
567
|
+
filetypes=[("JSON Files", "*.json"), ("All Files", "*.*")],
|
|
568
|
+
title="Save Configuration As"
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
if filename:
|
|
572
|
+
config = {
|
|
573
|
+
'target_url': self.url_entry.get(),
|
|
574
|
+
'scan_config': self.scan_config,
|
|
575
|
+
'sql_injection': self.sql_var.get(),
|
|
576
|
+
'xss': self.xss_var.get(),
|
|
577
|
+
'headers': self.headers_var.get(),
|
|
578
|
+
'ssl': self.ssl_var.get(),
|
|
579
|
+
'crawl': self.crawl_var.get(),
|
|
580
|
+
'csrf': self.csrf_var.get(),
|
|
581
|
+
'dir_traversal': self.dir_trav_var.get(),
|
|
582
|
+
'command_injection': self.cmdi_var.get(),
|
|
583
|
+
'exposed_files': self.file_exp_var.get(),
|
|
584
|
+
'ssrf': self.ssrf_var.get(),
|
|
585
|
+
'xxe': self.xxe_var.get(),
|
|
586
|
+
'idor': self.idor_var.get(),
|
|
587
|
+
'port_scan': self.port_scan_var.get(),
|
|
588
|
+
'os_detection': self.os_detect_var.get(),
|
|
589
|
+
'service_scan': self.service_scan_var.get()
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
with open(filename, 'w') as f:
|
|
593
|
+
json.dump(config, f, indent=4)
|
|
594
|
+
|
|
595
|
+
messagebox.showinfo("Saved", f"Configuration saved to {filename}")
|
|
596
|
+
except Exception as e:
|
|
597
|
+
messagebox.showerror("Error", f"Failed to save configuration: {str(e)}")
|
|
598
|
+
|
|
599
|
+
def open_port_scanner(self):
|
|
600
|
+
"""Open port scanner tool"""
|
|
601
|
+
port_window = tk.Toplevel(self.root)
|
|
602
|
+
port_window.title("Port Scanner")
|
|
603
|
+
port_window.geometry("500x400")
|
|
604
|
+
|
|
605
|
+
ttk.Label(port_window, text="Target Host:").pack(pady=(10, 0))
|
|
606
|
+
host_entry = ttk.Entry(port_window, width=30)
|
|
607
|
+
host_entry.pack()
|
|
608
|
+
host_entry.insert(0, self.url_entry.get())
|
|
609
|
+
|
|
610
|
+
ttk.Label(port_window, text="Port Range (e.g., 1-1024):").pack(pady=(10, 0))
|
|
611
|
+
port_entry = ttk.Entry(port_window, width=30)
|
|
612
|
+
port_entry.pack()
|
|
613
|
+
port_entry.insert(0, "1-1024")
|
|
614
|
+
|
|
615
|
+
result_text = scrolledtext.ScrolledText(port_window, wrap=tk.WORD, height=15)
|
|
616
|
+
result_text.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)
|
|
617
|
+
|
|
618
|
+
def scan_ports():
|
|
619
|
+
host = host_entry.get().strip()
|
|
620
|
+
port_range = port_entry.get().strip()
|
|
621
|
+
|
|
622
|
+
if not host:
|
|
623
|
+
messagebox.showerror("Error", "Please enter a target host")
|
|
624
|
+
return
|
|
625
|
+
|
|
626
|
+
try:
|
|
627
|
+
# Validate port range
|
|
628
|
+
if '-' in port_range:
|
|
629
|
+
start, end = map(int, port_range.split('-'))
|
|
630
|
+
else:
|
|
631
|
+
start = end = int(port_range)
|
|
632
|
+
|
|
633
|
+
result_text.delete(1.0, tk.END)
|
|
634
|
+
result_text.insert(tk.END, f"Scanning {host} ports {start}-{end}...\n\n")
|
|
635
|
+
port_window.update()
|
|
636
|
+
|
|
637
|
+
open_ports = []
|
|
638
|
+
|
|
639
|
+
for port in range(start, end + 1):
|
|
640
|
+
try:
|
|
641
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
642
|
+
sock.settimeout(1)
|
|
643
|
+
result = sock.connect_ex((host, port))
|
|
644
|
+
if result == 0:
|
|
645
|
+
service = socket.getservbyport(port, 'tcp') if port <= 65535 else 'unknown'
|
|
646
|
+
open_ports.append((port, service))
|
|
647
|
+
result_text.insert(tk.END, f"Port {port} ({service}) is open\n")
|
|
648
|
+
sock.close()
|
|
649
|
+
except:
|
|
650
|
+
result_text.insert(tk.END, f"Port {port}: Error scanning\n")
|
|
651
|
+
|
|
652
|
+
port_window.update()
|
|
653
|
+
|
|
654
|
+
result_text.insert(tk.END, f"\nScan complete. Found {len(open_ports)} open ports.\n")
|
|
655
|
+
|
|
656
|
+
if open_ports:
|
|
657
|
+
result_text.insert(tk.END, "\nOpen ports:\n")
|
|
658
|
+
for port, service in open_ports:
|
|
659
|
+
result_text.insert(tk.END, f"- {port}: {service}\n")
|
|
660
|
+
|
|
661
|
+
except ValueError:
|
|
662
|
+
messagebox.showerror("Error", "Invalid port range format")
|
|
663
|
+
except Exception as e:
|
|
664
|
+
messagebox.showerror("Error", f"Port scan failed: {str(e)}")
|
|
665
|
+
|
|
666
|
+
ttk.Button(port_window, text="Scan Ports", command=scan_ports).pack(pady=5)
|
|
667
|
+
|
|
668
|
+
def open_whois_tool(self):
|
|
669
|
+
"""Open WHOIS lookup tool"""
|
|
670
|
+
whois_window = tk.Toplevel(self.root)
|
|
671
|
+
whois_window.title("WHOIS Lookup")
|
|
672
|
+
whois_window.geometry("600x400")
|
|
673
|
+
|
|
674
|
+
ttk.Label(whois_window, text="Domain or IP:").pack(pady=(10, 0))
|
|
675
|
+
domain_entry = ttk.Entry(whois_window, width=30)
|
|
676
|
+
domain_entry.pack()
|
|
677
|
+
domain_entry.insert(0, self.url_entry.get())
|
|
678
|
+
|
|
679
|
+
result_text = scrolledtext.ScrolledText(whois_window, wrap=tk.WORD, height=20)
|
|
680
|
+
result_text.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)
|
|
681
|
+
|
|
682
|
+
def perform_whois():
|
|
683
|
+
domain = domain_entry.get().strip()
|
|
684
|
+
if not domain:
|
|
685
|
+
messagebox.showerror("Error", "Please enter a domain or IP")
|
|
686
|
+
return
|
|
687
|
+
|
|
688
|
+
try:
|
|
689
|
+
result_text.delete(1.0, tk.END)
|
|
690
|
+
result_text.insert(tk.END, f"Performing WHOIS lookup for {domain}...\n\n")
|
|
691
|
+
whois_window.update()
|
|
692
|
+
|
|
693
|
+
# Extract domain from URL if needed
|
|
694
|
+
if domain.startswith(('http://', 'https://')):
|
|
695
|
+
domain = urlparse(domain).netloc
|
|
696
|
+
|
|
697
|
+
# Perform WHOIS lookup
|
|
698
|
+
w = whois.whois(domain)
|
|
699
|
+
|
|
700
|
+
result_text.insert(tk.END, f"WHOIS Results for {domain}:\n\n")
|
|
701
|
+
|
|
702
|
+
# Display basic info
|
|
703
|
+
result_text.insert(tk.END, f"Domain Name: {w.domain_name}\n")
|
|
704
|
+
result_text.insert(tk.END, f"Registrar: {w.registrar}\n")
|
|
705
|
+
result_text.insert(tk.END, f"Creation Date: {w.creation_date}\n")
|
|
706
|
+
result_text.insert(tk.END, f"Expiration Date: {w.expiration_date}\n")
|
|
707
|
+
result_text.insert(tk.END, f"Name Servers: {', '.join(w.name_servers) if w.name_servers else 'N/A'}\n\n")
|
|
708
|
+
|
|
709
|
+
# Display raw WHOIS data
|
|
710
|
+
result_text.insert(tk.END, "Raw WHOIS Data:\n")
|
|
711
|
+
result_text.insert(tk.END, "-"*50 + "\n")
|
|
712
|
+
result_text.insert(tk.END, str(w.text) + "\n")
|
|
713
|
+
|
|
714
|
+
except Exception as e:
|
|
715
|
+
messagebox.showerror("Error", f"WHOIS lookup failed: {str(e)}")
|
|
716
|
+
|
|
717
|
+
ttk.Button(whois_window, text="Lookup", command=perform_whois).pack(pady=5)
|
|
718
|
+
|
|
719
|
+
def open_dns_tool(self):
|
|
720
|
+
"""Open DNS lookup tool"""
|
|
721
|
+
dns_window = tk.Toplevel(self.root)
|
|
722
|
+
dns_window.title("DNS Lookup")
|
|
723
|
+
dns_window.geometry("600x400")
|
|
724
|
+
|
|
725
|
+
ttk.Label(dns_window, text="Domain:").pack(pady=(10, 0))
|
|
726
|
+
domain_entry = ttk.Entry(dns_window, width=30)
|
|
727
|
+
domain_entry.pack()
|
|
728
|
+
domain_entry.insert(0, self.url_entry.get())
|
|
729
|
+
|
|
730
|
+
result_text = scrolledtext.ScrolledText(dns_window, wrap=tk.WORD, height=20)
|
|
731
|
+
result_text.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)
|
|
732
|
+
|
|
733
|
+
def perform_dns_lookup():
|
|
734
|
+
domain = domain_entry.get().strip()
|
|
735
|
+
if not domain:
|
|
736
|
+
messagebox.showerror("Error", "Please enter a domain")
|
|
737
|
+
return
|
|
738
|
+
|
|
739
|
+
try:
|
|
740
|
+
result_text.delete(1.0, tk.END)
|
|
741
|
+
result_text.insert(tk.END, f"Performing DNS lookup for {domain}...\n\n")
|
|
742
|
+
dns_window.update()
|
|
743
|
+
|
|
744
|
+
# Extract domain from URL if needed
|
|
745
|
+
if domain.startswith(('http://', 'https://')):
|
|
746
|
+
domain = urlparse(domain).netloc
|
|
747
|
+
|
|
748
|
+
# Perform DNS lookups
|
|
749
|
+
record_types = ['A', 'AAAA', 'MX', 'NS', 'TXT', 'CNAME', 'SOA']
|
|
750
|
+
|
|
751
|
+
for record_type in record_types:
|
|
752
|
+
try:
|
|
753
|
+
result_text.insert(tk.END, f"{record_type} Records:\n")
|
|
754
|
+
answers = dns.resolver.resolve(domain, record_type)
|
|
755
|
+
for rdata in answers:
|
|
756
|
+
result_text.insert(tk.END, f"- {rdata}\n")
|
|
757
|
+
result_text.insert(tk.END, "\n")
|
|
758
|
+
except dns.resolver.NoAnswer:
|
|
759
|
+
result_text.insert(tk.END, f"No {record_type} records found\n\n")
|
|
760
|
+
except dns.resolver.NXDOMAIN:
|
|
761
|
+
result_text.insert(tk.END, f"Domain {domain} does not exist\n\n")
|
|
762
|
+
break
|
|
763
|
+
except Exception as e:
|
|
764
|
+
result_text.insert(tk.END, f"Error querying {record_type} records: {str(e)}\n\n")
|
|
765
|
+
|
|
766
|
+
dns_window.update()
|
|
767
|
+
|
|
768
|
+
result_text.insert(tk.END, "DNS lookup complete.\n")
|
|
769
|
+
|
|
770
|
+
except Exception as e:
|
|
771
|
+
messagebox.showerror("Error", f"DNS lookup failed: {str(e)}")
|
|
772
|
+
|
|
773
|
+
ttk.Button(dns_window, text="Lookup", command=perform_dns_lookup).pack(pady=5)
|
|
774
|
+
|
|
775
|
+
def open_ping_tool(self):
|
|
776
|
+
"""Open ping tool"""
|
|
777
|
+
ping_window = tk.Toplevel(self.root)
|
|
778
|
+
ping_window.title("Ping Tool")
|
|
779
|
+
ping_window.geometry("500x300")
|
|
780
|
+
|
|
781
|
+
ttk.Label(ping_window, text="Host:").pack(pady=(10, 0))
|
|
782
|
+
host_entry = ttk.Entry(ping_window, width=30)
|
|
783
|
+
host_entry.pack()
|
|
784
|
+
host_entry.insert(0, self.url_entry.get())
|
|
785
|
+
|
|
786
|
+
ttk.Label(ping_window, text="Count (1-10):").pack(pady=(10, 0))
|
|
787
|
+
count_entry = ttk.Entry(ping_window, width=10)
|
|
788
|
+
count_entry.pack()
|
|
789
|
+
count_entry.insert(0, "4")
|
|
790
|
+
|
|
791
|
+
result_text = scrolledtext.ScrolledText(ping_window, wrap=tk.WORD, height=10)
|
|
792
|
+
result_text.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)
|
|
793
|
+
|
|
794
|
+
def perform_ping():
|
|
795
|
+
host = host_entry.get().strip()
|
|
796
|
+
if not host:
|
|
797
|
+
messagebox.showerror("Error", "Please enter a host")
|
|
798
|
+
return
|
|
799
|
+
|
|
800
|
+
try:
|
|
801
|
+
count = int(count_entry.get())
|
|
802
|
+
if count < 1 or count > 10:
|
|
803
|
+
raise ValueError
|
|
804
|
+
except ValueError:
|
|
805
|
+
messagebox.showerror("Error", "Please enter a count between 1 and 10")
|
|
806
|
+
return
|
|
807
|
+
|
|
808
|
+
try:
|
|
809
|
+
result_text.delete(1.0, tk.END)
|
|
810
|
+
result_text.insert(tk.END, f"Pinging {host} {count} times...\n\n")
|
|
811
|
+
ping_window.update()
|
|
812
|
+
|
|
813
|
+
# Extract host from URL if needed
|
|
814
|
+
if host.startswith(('http://', 'https://')):
|
|
815
|
+
host = urlparse(host).netloc
|
|
816
|
+
|
|
817
|
+
# Platform-specific ping command
|
|
818
|
+
if sys.platform == "win32":
|
|
819
|
+
cmd = ["ping", "-n", str(count), host]
|
|
820
|
+
else:
|
|
821
|
+
cmd = ["ping", "-c", str(count), host]
|
|
822
|
+
|
|
823
|
+
process = subprocess.Popen(
|
|
824
|
+
cmd,
|
|
825
|
+
stdout=subprocess.PIPE,
|
|
826
|
+
stderr=subprocess.PIPE,
|
|
827
|
+
universal_newlines=True
|
|
828
|
+
)
|
|
829
|
+
|
|
830
|
+
for line in process.stdout:
|
|
831
|
+
result_text.insert(tk.END, line)
|
|
832
|
+
ping_window.update()
|
|
833
|
+
|
|
834
|
+
result_text.insert(tk.END, "\nPing complete.\n")
|
|
835
|
+
|
|
836
|
+
except Exception as e:
|
|
837
|
+
messagebox.showerror("Error", f"Ping failed: {str(e)}")
|
|
838
|
+
|
|
839
|
+
ttk.Button(ping_window, text="Ping", command=perform_ping).pack(pady=5)
|
|
840
|
+
|
|
841
|
+
def open_documentation(self):
|
|
842
|
+
"""Open documentation in browser"""
|
|
843
|
+
try:
|
|
844
|
+
webbrowser.open("https://github.com/yourusername/vulnerability-scanner/docs")
|
|
845
|
+
except:
|
|
846
|
+
messagebox.showerror("Error", "Could not open documentation")
|
|
847
|
+
|
|
848
|
+
def show_about(self):
|
|
849
|
+
"""Show about dialog"""
|
|
850
|
+
about_window = tk.Toplevel(self.root)
|
|
851
|
+
about_window.title("About")
|
|
852
|
+
about_window.geometry("400x300")
|
|
853
|
+
|
|
854
|
+
try:
|
|
855
|
+
icon_path = os.path.join(os.path.dirname(__file__), "icon.ico")
|
|
856
|
+
if os.path.exists(icon_path):
|
|
857
|
+
img = Image.open(icon_path)
|
|
858
|
+
img = img.resize((64, 64), Image.LANCZOS)
|
|
859
|
+
tk_icon = ImageTk.PhotoImage(img)
|
|
860
|
+
icon_label = ttk.Label(about_window, image=tk_icon)
|
|
861
|
+
icon_label.image = tk_icon # Keep a reference
|
|
862
|
+
icon_label.pack(pady=10)
|
|
863
|
+
except Exception:
|
|
864
|
+
pass
|
|
865
|
+
|
|
866
|
+
ttk.Label(about_window, text="Advanced Vulnerability Scanner Pro",
|
|
867
|
+
font=('Arial', 12, 'bold')).pack()
|
|
868
|
+
|
|
869
|
+
ttk.Label(about_window, text="Version 2.0").pack()
|
|
870
|
+
ttk.Label(about_window, text="\nA comprehensive security scanning tool").pack()
|
|
871
|
+
ttk.Label(about_window, text="for identifying vulnerabilities in web applications.").pack()
|
|
872
|
+
|
|
873
|
+
ttk.Label(about_window, text="\n© 2023 Security Tools Inc.").pack()
|
|
874
|
+
|
|
875
|
+
btn_frame = ttk.Frame(about_window)
|
|
876
|
+
btn_frame.pack(pady=10)
|
|
877
|
+
|
|
878
|
+
ttk.Button(btn_frame, text="Close", command=about_window.destroy).pack()
|
|
879
|
+
|
|
880
|
+
def start_scan(self):
|
|
881
|
+
target = self.url_entry.get().strip()
|
|
882
|
+
if not target:
|
|
883
|
+
messagebox.showerror("Error", "Please enter a target URL or IP")
|
|
884
|
+
return
|
|
885
|
+
|
|
886
|
+
# Clear previous results
|
|
887
|
+
for item in self.results_tree.get_children():
|
|
888
|
+
self.results_tree.delete(item)
|
|
889
|
+
for item in self.files_tree.get_children():
|
|
890
|
+
self.files_tree.delete(item)
|
|
891
|
+
for item in self.network_tree.get_children():
|
|
892
|
+
self.network_tree.delete(item)
|
|
893
|
+
self.details_text.delete(1.0, tk.END)
|
|
894
|
+
|
|
895
|
+
self.scan_btn.config(state=tk.DISABLED)
|
|
896
|
+
|
|
897
|
+
scan_thread = threading.Thread(target=self.run_scan, args=(target,), daemon=True)
|
|
898
|
+
scan_thread.start()
|
|
899
|
+
|
|
900
|
+
def run_scan(self, target):
|
|
901
|
+
try:
|
|
902
|
+
# Check if target is an IP address
|
|
903
|
+
try:
|
|
904
|
+
ipaddress.ip_address(target)
|
|
905
|
+
is_ip = True
|
|
906
|
+
except ValueError:
|
|
907
|
+
is_ip = False
|
|
908
|
+
|
|
909
|
+
if not is_ip and not target.startswith(('http://', 'https://')):
|
|
910
|
+
target = f"http://{target}"
|
|
911
|
+
|
|
912
|
+
parsed_url = urlparse(target) if not is_ip else None
|
|
913
|
+
domain = parsed_url.netloc if not is_ip else target
|
|
914
|
+
base_url = f"{parsed_url.scheme}://{domain}" if not is_ip else None
|
|
915
|
+
|
|
916
|
+
# Calculate total steps for progress
|
|
917
|
+
total_steps = sum([
|
|
918
|
+
self.sql_var.get(),
|
|
919
|
+
self.xss_var.get(),
|
|
920
|
+
self.headers_var.get(),
|
|
921
|
+
self.ssl_var.get(),
|
|
922
|
+
self.csrf_var.get(),
|
|
923
|
+
self.dir_trav_var.get(),
|
|
924
|
+
self.cmdi_var.get(),
|
|
925
|
+
self.crawl_var.get(),
|
|
926
|
+
self.file_exp_var.get(),
|
|
927
|
+
self.ssrf_var.get(),
|
|
928
|
+
self.xxe_var.get(),
|
|
929
|
+
self.idor_var.get(),
|
|
930
|
+
self.port_scan_var.get(),
|
|
931
|
+
self.os_detect_var.get(),
|
|
932
|
+
self.service_scan_var.get()
|
|
933
|
+
])
|
|
934
|
+
current_step = 0
|
|
935
|
+
|
|
936
|
+
# Network scans (work for both IP and URL)
|
|
937
|
+
if self.port_scan_var.get() and self.nm:
|
|
938
|
+
self.update_status("Performing port scan...")
|
|
939
|
+
self.port_scan(domain)
|
|
940
|
+
current_step += 1
|
|
941
|
+
self.update_progress(current_step, total_steps)
|
|
942
|
+
|
|
943
|
+
if self.os_detect_var.get() and self.nm:
|
|
944
|
+
self.update_status("Detecting OS...")
|
|
945
|
+
self.os_detection(domain)
|
|
946
|
+
current_step += 1
|
|
947
|
+
self.update_progress(current_step, total_steps)
|
|
948
|
+
|
|
949
|
+
if self.service_scan_var.get() and self.nm:
|
|
950
|
+
self.update_status("Scanning services...")
|
|
951
|
+
self.service_scan(domain)
|
|
952
|
+
current_step += 1
|
|
953
|
+
self.update_progress(current_step, total_steps)
|
|
954
|
+
|
|
955
|
+
# Web application scans (only for URLs)
|
|
956
|
+
if not is_ip:
|
|
957
|
+
# SSL/TLS Check
|
|
958
|
+
if self.ssl_var.get():
|
|
959
|
+
self.update_status("Checking SSL/TLS configuration...")
|
|
960
|
+
self.check_ssl(domain)
|
|
961
|
+
current_step += 1
|
|
962
|
+
self.update_progress(current_step, total_steps)
|
|
963
|
+
|
|
964
|
+
# Headers Analysis
|
|
965
|
+
if self.headers_var.get():
|
|
966
|
+
self.update_status("Analyzing HTTP headers...")
|
|
967
|
+
self.analyze_headers(target)
|
|
968
|
+
current_step += 1
|
|
969
|
+
self.update_progress(current_step, total_steps)
|
|
970
|
+
|
|
971
|
+
# SQL Injection Test
|
|
972
|
+
if self.sql_var.get():
|
|
973
|
+
self.update_status("Testing for SQL Injection...")
|
|
974
|
+
self.test_sql_injection(target)
|
|
975
|
+
current_step += 1
|
|
976
|
+
self.update_progress(current_step, total_steps)
|
|
977
|
+
|
|
978
|
+
# XSS Test
|
|
979
|
+
if self.xss_var.get():
|
|
980
|
+
self.update_status("Testing for XSS vulnerabilities...")
|
|
981
|
+
self.test_xss(target)
|
|
982
|
+
current_step += 1
|
|
983
|
+
self.update_progress(current_step, total_steps)
|
|
984
|
+
|
|
985
|
+
# CSRF Test
|
|
986
|
+
if self.csrf_var.get():
|
|
987
|
+
self.update_status("Checking for CSRF vulnerabilities...")
|
|
988
|
+
self.test_csrf(target)
|
|
989
|
+
current_step += 1
|
|
990
|
+
self.update_progress(current_step, total_steps)
|
|
991
|
+
|
|
992
|
+
# Directory Traversal Test
|
|
993
|
+
if self.dir_trav_var.get():
|
|
994
|
+
self.update_status("Testing for Directory Traversal...")
|
|
995
|
+
self.test_directory_traversal(target)
|
|
996
|
+
current_step += 1
|
|
997
|
+
self.update_progress(current_step, total_steps)
|
|
998
|
+
|
|
999
|
+
# Command Injection Test
|
|
1000
|
+
if self.cmdi_var.get():
|
|
1001
|
+
self.update_status("Testing for Command Injection...")
|
|
1002
|
+
self.test_command_injection(target)
|
|
1003
|
+
current_step += 1
|
|
1004
|
+
self.update_progress(current_step, total_steps)
|
|
1005
|
+
|
|
1006
|
+
# SSRF Test
|
|
1007
|
+
if self.ssrf_var.get():
|
|
1008
|
+
self.update_status("Testing for SSRF vulnerabilities...")
|
|
1009
|
+
self.test_ssrf(target)
|
|
1010
|
+
current_step += 1
|
|
1011
|
+
self.update_progress(current_step, total_steps)
|
|
1012
|
+
|
|
1013
|
+
# XXE Test
|
|
1014
|
+
if self.xxe_var.get():
|
|
1015
|
+
self.update_status("Testing for XXE vulnerabilities...")
|
|
1016
|
+
self.test_xxe(target)
|
|
1017
|
+
current_step += 1
|
|
1018
|
+
self.update_progress(current_step, total_steps)
|
|
1019
|
+
|
|
1020
|
+
# IDOR Test
|
|
1021
|
+
if self.idor_var.get():
|
|
1022
|
+
self.update_status("Testing for IDOR vulnerabilities...")
|
|
1023
|
+
self.test_idor(target)
|
|
1024
|
+
current_step += 1
|
|
1025
|
+
self.update_progress(current_step, total_steps)
|
|
1026
|
+
|
|
1027
|
+
# Crawl Pages
|
|
1028
|
+
if self.crawl_var.get():
|
|
1029
|
+
self.update_status("Crawling website for links...")
|
|
1030
|
+
self.crawl_pages(base_url)
|
|
1031
|
+
current_step += 1
|
|
1032
|
+
self.update_progress(current_step, total_steps)
|
|
1033
|
+
|
|
1034
|
+
# Check for exposed files
|
|
1035
|
+
if self.file_exp_var.get():
|
|
1036
|
+
self.update_status("Checking for exposed files...")
|
|
1037
|
+
self.check_exposed_files(base_url)
|
|
1038
|
+
current_step += 1
|
|
1039
|
+
self.update_progress(current_step, total_steps)
|
|
1040
|
+
|
|
1041
|
+
self.update_status("Scan completed successfully!")
|
|
1042
|
+
messagebox.showinfo("Scan Complete", "Vulnerability scan completed!")
|
|
1043
|
+
|
|
1044
|
+
except Exception as e:
|
|
1045
|
+
self.add_result("High", "Scan Error", f"Error during scan: {str(e)}")
|
|
1046
|
+
self.update_status(f"Error: {str(e)}")
|
|
1047
|
+
|
|
1048
|
+
finally:
|
|
1049
|
+
self.scan_btn.config(state=tk.NORMAL)
|
|
1050
|
+
|
|
1051
|
+
def port_scan(self, host):
|
|
1052
|
+
"""Perform port scan using nmap"""
|
|
1053
|
+
try:
|
|
1054
|
+
if not self.nm:
|
|
1055
|
+
self.add_result("Medium", "Port Scan", "Nmap is not installed. Port scanning disabled.")
|
|
1056
|
+
return
|
|
1057
|
+
|
|
1058
|
+
self.nm.scan(hosts=host, arguments='-F') # Fast scan (top 100 ports)
|
|
1059
|
+
|
|
1060
|
+
if host not in self.nm.all_hosts():
|
|
1061
|
+
self.add_result("Medium", "Port Scan", "Host not reachable for port scanning")
|
|
1062
|
+
return
|
|
1063
|
+
|
|
1064
|
+
open_ports = []
|
|
1065
|
+
|
|
1066
|
+
for proto in self.nm[host].all_protocols():
|
|
1067
|
+
ports = self.nm[host][proto].keys()
|
|
1068
|
+
for port in ports:
|
|
1069
|
+
port_info = self.nm[host][proto][port]
|
|
1070
|
+
state = port_info['state']
|
|
1071
|
+
service = port_info['name']
|
|
1072
|
+
version = port_info.get('version', 'unknown')
|
|
1073
|
+
|
|
1074
|
+
if state == 'open':
|
|
1075
|
+
open_ports.append((service, port, state, version))
|
|
1076
|
+
self.network_tree.insert('', tk.END,
|
|
1077
|
+
values=(service, port, state, version),
|
|
1078
|
+
tags=(state,))
|
|
1079
|
+
|
|
1080
|
+
if open_ports:
|
|
1081
|
+
self.add_result("Medium", "Open Ports",
|
|
1082
|
+
f"Found {len(open_ports)} open ports on {host}")
|
|
1083
|
+
else:
|
|
1084
|
+
self.add_result("Low", "Port Scan", "No open ports found")
|
|
1085
|
+
|
|
1086
|
+
self.details_text.insert(tk.END, f"\nPort Scan Results for {host}:\n")
|
|
1087
|
+
self.details_text.insert(tk.END, f"Scanned {len(self.nm[host].all_ports())} ports\n")
|
|
1088
|
+
self.details_text.insert(tk.END, f"Found {len(open_ports)} open ports\n\n")
|
|
1089
|
+
|
|
1090
|
+
except Exception as e:
|
|
1091
|
+
self.add_result("Medium", "Port Scan Error", f"Port scan failed: {str(e)}")
|
|
1092
|
+
|
|
1093
|
+
def os_detection(self, host):
|
|
1094
|
+
"""Perform OS detection using nmap"""
|
|
1095
|
+
try:
|
|
1096
|
+
if not self.nm:
|
|
1097
|
+
self.add_result("Medium", "OS Detection", "Nmap is not installed. OS detection disabled.")
|
|
1098
|
+
return
|
|
1099
|
+
|
|
1100
|
+
self.nm.scan(hosts=host, arguments='-O') # OS detection scan
|
|
1101
|
+
|
|
1102
|
+
if host not in self.nm.all_hosts():
|
|
1103
|
+
self.add_result("Medium", "OS Detection", "Host not reachable for OS detection")
|
|
1104
|
+
return
|
|
1105
|
+
|
|
1106
|
+
os_info = self.nm[host].get('osmatch', [])
|
|
1107
|
+
|
|
1108
|
+
if os_info:
|
|
1109
|
+
best_guess = os_info[0]
|
|
1110
|
+
os_name = best_guess['name']
|
|
1111
|
+
accuracy = best_guess['accuracy']
|
|
1112
|
+
|
|
1113
|
+
self.add_result("Low", "OS Detection",
|
|
1114
|
+
f"Likely OS: {os_name} (Accuracy: {accuracy}%)")
|
|
1115
|
+
|
|
1116
|
+
self.details_text.insert(tk.END, f"\nOS Detection Results for {host}:\n")
|
|
1117
|
+
self.details_text.insert(tk.END, f"Best guess: {os_name} ({accuracy}% accuracy)\n")
|
|
1118
|
+
|
|
1119
|
+
if len(os_info) > 1:
|
|
1120
|
+
self.details_text.insert(tk.END, "\nOther possible matches:\n")
|
|
1121
|
+
for match in os_info[1:]:
|
|
1122
|
+
self.details_text.insert(tk.END, f"- {match['name']} ({match['accuracy']}%)\n")
|
|
1123
|
+
else:
|
|
1124
|
+
self.add_result("Low", "OS Detection", "Could not determine operating system")
|
|
1125
|
+
self.details_text.insert(tk.END, f"\nOS Detection Results for {host}:\n")
|
|
1126
|
+
self.details_text.insert(tk.END, "Could not determine operating system\n")
|
|
1127
|
+
|
|
1128
|
+
except Exception as e:
|
|
1129
|
+
self.add_result("Medium", "OS Detection Error", f"OS detection failed: {str(e)}")
|
|
1130
|
+
|
|
1131
|
+
def service_scan(self, host):
|
|
1132
|
+
"""Perform service version detection using nmap"""
|
|
1133
|
+
try:
|
|
1134
|
+
if not self.nm:
|
|
1135
|
+
self.add_result("Medium", "Service Scan", "Nmap is not installed. Service scan disabled.")
|
|
1136
|
+
return
|
|
1137
|
+
|
|
1138
|
+
self.nm.scan(hosts=host, arguments='-sV') # Service version scan
|
|
1139
|
+
|
|
1140
|
+
if host not in self.nm.all_hosts():
|
|
1141
|
+
self.add_result("Medium", "Service Scan", "Host not reachable for service scan")
|
|
1142
|
+
return
|
|
1143
|
+
|
|
1144
|
+
service_info = []
|
|
1145
|
+
|
|
1146
|
+
for proto in self.nm[host].all_protocols():
|
|
1147
|
+
ports = self.nm[host][proto].keys()
|
|
1148
|
+
for port in ports:
|
|
1149
|
+
port_data = self.nm[host][proto][port]
|
|
1150
|
+
if port_data['state'] == 'open':
|
|
1151
|
+
service_info.append((
|
|
1152
|
+
port_data['name'],
|
|
1153
|
+
port,
|
|
1154
|
+
port_data['state'],
|
|
1155
|
+
port_data.get('product', '') + ' ' + port_data.get('version', '')
|
|
1156
|
+
))
|
|
1157
|
+
|
|
1158
|
+
if service_info:
|
|
1159
|
+
self.add_result("Low", "Service Scan",
|
|
1160
|
+
f"Found {len(service_info)} services on {host}")
|
|
1161
|
+
|
|
1162
|
+
self.details_text.insert(tk.END, f"\nService Scan Results for {host}:\n")
|
|
1163
|
+
for service in service_info:
|
|
1164
|
+
self.details_text.insert(tk.END,
|
|
1165
|
+
f"Port {service[1]}: {service[0]} - {service[3]}\n")
|
|
1166
|
+
else:
|
|
1167
|
+
self.add_result("Low", "Service Scan", "No services detected")
|
|
1168
|
+
self.details_text.insert(tk.END, f"\nService Scan Results for {host}:\n")
|
|
1169
|
+
self.details_text.insert(tk.END, "No services detected\n")
|
|
1170
|
+
|
|
1171
|
+
except Exception as e:
|
|
1172
|
+
self.add_result("Medium", "Service Scan Error", f"Service scan failed: {str(e)}")
|
|
1173
|
+
|
|
1174
|
+
def check_exposed_files(self, base_url):
|
|
1175
|
+
"""Check for exposed files on hosting platforms like Vercel, Netlify, etc."""
|
|
1176
|
+
try:
|
|
1177
|
+
parsed = urlparse(base_url)
|
|
1178
|
+
domain = parsed.netloc
|
|
1179
|
+
scheme = parsed.scheme
|
|
1180
|
+
|
|
1181
|
+
# Common exposed files and directories
|
|
1182
|
+
common_files = [
|
|
1183
|
+
# Configuration files
|
|
1184
|
+
"_config.yml", "config.yml", "config.json", "package.json",
|
|
1185
|
+
"composer.json", "package-lock.json", "yarn.lock",
|
|
1186
|
+
"dockerfile", "docker-compose.yml", ".env", ".env.example",
|
|
1187
|
+
".gitignore", ".htaccess", "robots.txt", "sitemap.xml",
|
|
1188
|
+
|
|
1189
|
+
# Platform specific files
|
|
1190
|
+
"vercel.json", "netlify.toml", "now.json", "firebase.json",
|
|
1191
|
+
"_redirects", "_headers",
|
|
1192
|
+
|
|
1193
|
+
# Source code files
|
|
1194
|
+
"index.php", "index.html", "main.js", "app.js", "server.js",
|
|
1195
|
+
"style.css", "main.css", "app.css", "README.md", "LICENSE",
|
|
1196
|
+
|
|
1197
|
+
# Backup files
|
|
1198
|
+
"backup.zip", "backup.tar.gz", "backup.sql", "dump.sql",
|
|
1199
|
+
"database.sql", "backup.rar", "backup.db",
|
|
1200
|
+
|
|
1201
|
+
# Admin interfaces
|
|
1202
|
+
"admin.php", "admin.html", "wp-admin", "administrator",
|
|
1203
|
+
"login.php", "login.html", "wp-login.php",
|
|
1204
|
+
|
|
1205
|
+
# API endpoints
|
|
1206
|
+
"api/v1", "graphql", "graphiql", "api.json", "swagger.json",
|
|
1207
|
+
"openapi.json", "api.php", "api.js",
|
|
1208
|
+
|
|
1209
|
+
# Log files
|
|
1210
|
+
"logs", "error.log", "access.log", "debug.log"
|
|
1211
|
+
]
|
|
1212
|
+
|
|
1213
|
+
# Platform-specific file patterns
|
|
1214
|
+
platform_patterns = {
|
|
1215
|
+
"vercel": [
|
|
1216
|
+
"/_next/static/chunks/pages/",
|
|
1217
|
+
"/_next/static/development/",
|
|
1218
|
+
"/_next/static/css/",
|
|
1219
|
+
"/api/",
|
|
1220
|
+
"/public/"
|
|
1221
|
+
],
|
|
1222
|
+
"netlify": [
|
|
1223
|
+
"/.netlify/functions/",
|
|
1224
|
+
"/public/",
|
|
1225
|
+
"/static/",
|
|
1226
|
+
"/dist/"
|
|
1227
|
+
],
|
|
1228
|
+
"github": [
|
|
1229
|
+
"/.github/workflows/",
|
|
1230
|
+
"/.github/",
|
|
1231
|
+
"/actions/"
|
|
1232
|
+
],
|
|
1233
|
+
"firebase": [
|
|
1234
|
+
"/__/firebase/",
|
|
1235
|
+
"/__/auth/",
|
|
1236
|
+
"/__/database/"
|
|
1237
|
+
]
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
# Check if the domain matches known hosting platforms
|
|
1241
|
+
platform = None
|
|
1242
|
+
if "vercel.app" in domain:
|
|
1243
|
+
platform = "vercel"
|
|
1244
|
+
elif "netlify.app" in domain:
|
|
1245
|
+
platform = "netlify"
|
|
1246
|
+
elif "github.io" in domain:
|
|
1247
|
+
platform = "github"
|
|
1248
|
+
elif "firebaseapp.com" in domain or "web.app" in domain:
|
|
1249
|
+
platform = "firebase"
|
|
1250
|
+
|
|
1251
|
+
found_files = []
|
|
1252
|
+
|
|
1253
|
+
# Check common files
|
|
1254
|
+
for file in common_files:
|
|
1255
|
+
test_url = f"{scheme}://{domain}/{file}"
|
|
1256
|
+
try:
|
|
1257
|
+
response = requests.head(test_url, timeout=5, allow_redirects=False)
|
|
1258
|
+
if response.status_code == 200:
|
|
1259
|
+
size = response.headers.get('content-length', 'unknown')
|
|
1260
|
+
found_files.append(("file", test_url, "200 OK", size))
|
|
1261
|
+
self.files_tree.insert('', tk.END,
|
|
1262
|
+
values=("File", test_url, "200 OK", size))
|
|
1263
|
+
except:
|
|
1264
|
+
continue
|
|
1265
|
+
|
|
1266
|
+
# Check platform-specific patterns if platform is detected
|
|
1267
|
+
if platform:
|
|
1268
|
+
for pattern in platform_patterns.get(platform, []):
|
|
1269
|
+
test_url = f"{scheme}://{domain}{pattern}"
|
|
1270
|
+
try:
|
|
1271
|
+
response = requests.head(test_url, timeout=5, allow_redirects=False)
|
|
1272
|
+
if response.status_code == 200:
|
|
1273
|
+
size = response.headers.get('content-length', 'unknown')
|
|
1274
|
+
found_files.append(("directory", test_url, "200 OK", size))
|
|
1275
|
+
self.files_tree.insert('', tk.END,
|
|
1276
|
+
values=("Directory", test_url, "200 OK", size))
|
|
1277
|
+
except:
|
|
1278
|
+
continue
|
|
1279
|
+
|
|
1280
|
+
# Special check for .git directory
|
|
1281
|
+
test_url = f"{scheme}://{domain}/.git/"
|
|
1282
|
+
try:
|
|
1283
|
+
response = requests.head(test_url, timeout=5, allow_redirects=False)
|
|
1284
|
+
if response.status_code in (200, 403):
|
|
1285
|
+
size = response.headers.get('content-length', 'unknown')
|
|
1286
|
+
found_files.append(("directory", test_url, f"{response.status_code}", size))
|
|
1287
|
+
self.files_tree.insert('', tk.END,
|
|
1288
|
+
values=("Git", test_url, f"{response.status_code}", size))
|
|
1289
|
+
except:
|
|
1290
|
+
pass
|
|
1291
|
+
|
|
1292
|
+
# Check for exposed source code
|
|
1293
|
+
self.check_exposed_source_code(base_url)
|
|
1294
|
+
|
|
1295
|
+
if found_files:
|
|
1296
|
+
self.add_result("Medium", "Exposed Files", f"Found {len(found_files)} exposed files/directories")
|
|
1297
|
+
else:
|
|
1298
|
+
self.add_result("Low", "Exposed Files", "No obvious exposed files found")
|
|
1299
|
+
|
|
1300
|
+
self.details_text.insert(tk.END, f"\nExposed Files Check:\nFound {len(found_files)} files/directories\n")
|
|
1301
|
+
|
|
1302
|
+
except Exception as e:
|
|
1303
|
+
self.add_result("Medium", "Exposed Files Error", f"Exposed files check failed: {str(e)}")
|
|
1304
|
+
|
|
1305
|
+
def check_exposed_source_code(self, base_url):
|
|
1306
|
+
"""Check for exposed source code files on platforms like Vercel, Netlify"""
|
|
1307
|
+
try:
|
|
1308
|
+
parsed = urlparse(base_url)
|
|
1309
|
+
domain = parsed.netloc
|
|
1310
|
+
scheme = parsed.scheme
|
|
1311
|
+
|
|
1312
|
+
# Common source code patterns for Vercel/Next.js
|
|
1313
|
+
vercel_patterns = [
|
|
1314
|
+
"/_next/static/chunks/pages/_app.js",
|
|
1315
|
+
"/_next/static/chunks/main.js",
|
|
1316
|
+
"/_next/static/chunks/webpack.js",
|
|
1317
|
+
"/_next/static/css/styles.chunk.css"
|
|
1318
|
+
]
|
|
1319
|
+
|
|
1320
|
+
# Common source code patterns for Netlify
|
|
1321
|
+
netlify_patterns = [
|
|
1322
|
+
"/static/js/main.chunk.js",
|
|
1323
|
+
"/static/js/runtime-main.js",
|
|
1324
|
+
"/static/css/main.chunk.css"
|
|
1325
|
+
]
|
|
1326
|
+
|
|
1327
|
+
# Check if the domain matches known hosting platforms
|
|
1328
|
+
if "vercel.app" in domain:
|
|
1329
|
+
for pattern in vercel_patterns:
|
|
1330
|
+
test_url = f"{scheme}://{domain}{pattern}"
|
|
1331
|
+
try:
|
|
1332
|
+
response = requests.head(test_url, timeout=5, allow_redirects=False)
|
|
1333
|
+
if response.status_code == 200:
|
|
1334
|
+
size = response.headers.get('content-length', 'unknown')
|
|
1335
|
+
self.files_tree.insert('', tk.END,
|
|
1336
|
+
values=("Source", test_url, "200 OK", size))
|
|
1337
|
+
except:
|
|
1338
|
+
continue
|
|
1339
|
+
|
|
1340
|
+
elif "netlify.app" in domain:
|
|
1341
|
+
for pattern in netlify_patterns:
|
|
1342
|
+
test_url = f"{scheme}://{domain}{pattern}"
|
|
1343
|
+
try:
|
|
1344
|
+
response = requests.head(test_url, timeout=5, allow_redirects=False)
|
|
1345
|
+
if response.status_code == 200:
|
|
1346
|
+
size = response.headers.get('content-length', 'unknown')
|
|
1347
|
+
self.files_tree.insert('', tk.END,
|
|
1348
|
+
values=("Source", test_url, "200 OK", size))
|
|
1349
|
+
except:
|
|
1350
|
+
continue
|
|
1351
|
+
|
|
1352
|
+
# Special check for source map files
|
|
1353
|
+
source_map_patterns = [
|
|
1354
|
+
"/static/js/main.js.map",
|
|
1355
|
+
"/static/js/bundle.js.map",
|
|
1356
|
+
"/static/js/vendor.js.map",
|
|
1357
|
+
"/app.js.map",
|
|
1358
|
+
"/main.js.map"
|
|
1359
|
+
]
|
|
1360
|
+
|
|
1361
|
+
for pattern in source_map_patterns:
|
|
1362
|
+
test_url = f"{scheme}://{domain}{pattern}"
|
|
1363
|
+
try:
|
|
1364
|
+
response = requests.head(test_url, timeout=5, allow_redirects=False)
|
|
1365
|
+
if response.status_code == 200:
|
|
1366
|
+
size = response.headers.get('content-length', 'unknown')
|
|
1367
|
+
self.files_tree.insert('', tk.END,
|
|
1368
|
+
values=("Source Map", test_url, "200 OK", size))
|
|
1369
|
+
except:
|
|
1370
|
+
continue
|
|
1371
|
+
|
|
1372
|
+
except Exception as e:
|
|
1373
|
+
self.add_result("Medium", "Source Code Check Error", f"Source code check failed: {str(e)}")
|
|
1374
|
+
|
|
1375
|
+
def update_status(self, message):
|
|
1376
|
+
self.status.config(text=message)
|
|
1377
|
+
self.root.update()
|
|
1378
|
+
|
|
1379
|
+
def update_progress(self, current, total):
|
|
1380
|
+
progress = (current / total) * 100
|
|
1381
|
+
self.progress['value'] = progress
|
|
1382
|
+
self.root.update()
|
|
1383
|
+
|
|
1384
|
+
def add_result(self, severity, vuln_type, details):
|
|
1385
|
+
tag = severity.lower()
|
|
1386
|
+
self.results_tree.insert('', tk.END, values=(severity, vuln_type, details), tags=(tag,))
|
|
1387
|
+
self.details_text.insert(tk.END, f"[{severity}] {vuln_type}: {details}\n\n")
|
|
1388
|
+
self.details_text.see(tk.END)
|
|
1389
|
+
self.root.update()
|
|
1390
|
+
|
|
1391
|
+
def check_ssl(self, domain):
|
|
1392
|
+
try:
|
|
1393
|
+
context = ssl.create_default_context()
|
|
1394
|
+
with socket.create_connection((domain, 443)) as sock:
|
|
1395
|
+
with context.wrap_socket(sock, server_hostname=domain) as ssock:
|
|
1396
|
+
cert = ssock.getpeercert()
|
|
1397
|
+
|
|
1398
|
+
# Check certificate expiration
|
|
1399
|
+
expiry_date = datetime.datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z')
|
|
1400
|
+
days_remaining = (expiry_date - datetime.datetime.now()).days
|
|
1401
|
+
|
|
1402
|
+
details = "SSL Certificate:\nIssuer: {}\n".format(cert['issuer'][1][0][1])
|
|
1403
|
+
details += "Subject: {}\n".format(cert['subject'][1][0][1])
|
|
1404
|
+
details += "Serial Number: {}\n".format(cert['serialNumber'])
|
|
1405
|
+
details += "Valid From: {}\n".format(cert['notBefore'])
|
|
1406
|
+
details += "Valid Until: {} ({} days remaining)\n".format(cert['notAfter'], days_remaining)
|
|
1407
|
+
|
|
1408
|
+
# Check certificate algorithms
|
|
1409
|
+
details += "\nCertificate Algorithms:\n"
|
|
1410
|
+
details += "Signature Algorithm: {}\n".format(cert['signatureAlgorithm'])
|
|
1411
|
+
|
|
1412
|
+
# Check for weak protocols
|
|
1413
|
+
weak_protocols = self.detect_weak_ssl_protocols(domain)
|
|
1414
|
+
if weak_protocols:
|
|
1415
|
+
details += "\nWeak Protocols: {}\n".format(', '.join(weak_protocols))
|
|
1416
|
+
self.add_result("High", "Weak SSL Protocols", f"Server supports: {', '.join(weak_protocols)}")
|
|
1417
|
+
|
|
1418
|
+
# Check for weak ciphers
|
|
1419
|
+
weak_ciphers = self.detect_weak_ciphers(domain)
|
|
1420
|
+
if weak_ciphers:
|
|
1421
|
+
details += "\nWeak Ciphers:\n"
|
|
1422
|
+
for cipher in weak_ciphers:
|
|
1423
|
+
details += f"- {cipher}\n"
|
|
1424
|
+
self.add_result("High", "Weak SSL Ciphers", f"Server supports weak ciphers: {', '.join(weak_ciphers[:3])}...")
|
|
1425
|
+
|
|
1426
|
+
if days_remaining < 30:
|
|
1427
|
+
self.add_result("High", "SSL Expiry", f"Certificate expires in {days_remaining} days")
|
|
1428
|
+
elif days_remaining < 90:
|
|
1429
|
+
self.add_result("Medium", "SSL Expiry", f"Certificate expires in {days_remaining} days")
|
|
1430
|
+
|
|
1431
|
+
self.details_text.insert(tk.END, details + "\n")
|
|
1432
|
+
|
|
1433
|
+
except Exception as e:
|
|
1434
|
+
self.add_result("High", "SSL Error", f"SSL verification failed: {str(e)}")
|
|
1435
|
+
|
|
1436
|
+
def detect_weak_ssl_protocols(self, domain):
|
|
1437
|
+
weak_protocols = []
|
|
1438
|
+
protocols = {
|
|
1439
|
+
'SSLv2': ssl.PROTOCOL_SSLv2,
|
|
1440
|
+
'SSLv3': ssl.PROTOCOL_SSLv3,
|
|
1441
|
+
'TLSv1': ssl.PROTOCOL_TLSv1,
|
|
1442
|
+
'TLSv1.1': ssl.PROTOCOL_TLSv1_1
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
for name, proto in protocols.items():
|
|
1446
|
+
try:
|
|
1447
|
+
context = ssl.SSLContext(proto)
|
|
1448
|
+
with socket.create_connection((domain, 443)) as sock:
|
|
1449
|
+
with context.wrap_socket(sock, server_hostname=domain):
|
|
1450
|
+
weak_protocols.append(name)
|
|
1451
|
+
except:
|
|
1452
|
+
continue
|
|
1453
|
+
|
|
1454
|
+
return weak_protocols
|
|
1455
|
+
|
|
1456
|
+
def detect_weak_ciphers(self, domain):
|
|
1457
|
+
weak_ciphers = [
|
|
1458
|
+
'DES', '3DES', 'RC4', 'RC2', 'IDEA', 'SEED',
|
|
1459
|
+
'MD5', 'SHA1', 'NULL', 'ANON', 'ADH', 'EXP',
|
|
1460
|
+
'CBC', 'CAMELLIA', 'PSK', 'SRP'
|
|
1461
|
+
]
|
|
1462
|
+
|
|
1463
|
+
detected = []
|
|
1464
|
+
|
|
1465
|
+
try:
|
|
1466
|
+
context = ssl.create_default_context()
|
|
1467
|
+
with socket.create_connection((domain, 443)) as sock:
|
|
1468
|
+
with context.wrap_socket(sock, server_hostname=domain) as ssock:
|
|
1469
|
+
cipher = ssock.cipher()
|
|
1470
|
+
if cipher:
|
|
1471
|
+
for weak in weak_ciphers:
|
|
1472
|
+
if weak in cipher[0]:
|
|
1473
|
+
detected.append(cipher[0])
|
|
1474
|
+
break
|
|
1475
|
+
except:
|
|
1476
|
+
pass
|
|
1477
|
+
|
|
1478
|
+
return detected
|
|
1479
|
+
|
|
1480
|
+
def analyze_headers(self, url):
|
|
1481
|
+
try:
|
|
1482
|
+
response = requests.get(url, timeout=10, allow_redirects=True)
|
|
1483
|
+
headers = response.headers
|
|
1484
|
+
|
|
1485
|
+
details = "Security Headers Analysis:\n"
|
|
1486
|
+
missing_headers = []
|
|
1487
|
+
security_headers = {
|
|
1488
|
+
'X-XSS-Protection': '1; mode=block',
|
|
1489
|
+
'X-Content-Type-Options': 'nosniff',
|
|
1490
|
+
'X-Frame-Options': ['DENY', 'SAMEORIGIN'],
|
|
1491
|
+
'Content-Security-Policy': '',
|
|
1492
|
+
'Strict-Transport-Security': '',
|
|
1493
|
+
'Referrer-Policy': 'no-referrer',
|
|
1494
|
+
'Feature-Policy': '',
|
|
1495
|
+
'Permissions-Policy': ''
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
for header, expected in security_headers.items():
|
|
1499
|
+
if header not in headers:
|
|
1500
|
+
missing_headers.append(header)
|
|
1501
|
+
elif expected and isinstance(expected, list) and headers[header] not in expected:
|
|
1502
|
+
self.add_result("Medium", f"Misconfigured {header}",
|
|
1503
|
+
f"Expected one of {expected}, got {headers[header]}")
|
|
1504
|
+
elif expected and isinstance(expected, str) and headers[header] != expected:
|
|
1505
|
+
self.add_result("Medium", f"Misconfigured {header}",
|
|
1506
|
+
f"Expected {expected}, got {headers[header]}")
|
|
1507
|
+
|
|
1508
|
+
if missing_headers:
|
|
1509
|
+
self.add_result("Medium", "Missing Security Headers",
|
|
1510
|
+
f"Missing: {', '.join(missing_headers)}")
|
|
1511
|
+
|
|
1512
|
+
# Check for server information disclosure
|
|
1513
|
+
if 'server' in headers:
|
|
1514
|
+
self.add_result("Low", "Server Disclosure", f"Server header: {headers['server']}")
|
|
1515
|
+
|
|
1516
|
+
# Check for CORS misconfiguration
|
|
1517
|
+
if 'access-control-allow-origin' in headers and headers['access-control-allow-origin'] == '*':
|
|
1518
|
+
self.add_result("Medium", "Permissive CORS", "Access-Control-Allow-Origin is set to '*'")
|
|
1519
|
+
|
|
1520
|
+
# Check for clickjacking protection
|
|
1521
|
+
if 'x-frame-options' not in headers:
|
|
1522
|
+
self.add_result("Medium", "Clickjacking", "Missing X-Frame-Options header")
|
|
1523
|
+
|
|
1524
|
+
# Check for content type sniffing protection
|
|
1525
|
+
if 'x-content-type-options' not in headers:
|
|
1526
|
+
self.add_result("Low", "Content Type Sniffing", "Missing X-Content-Type-Options header")
|
|
1527
|
+
|
|
1528
|
+
self.details_text.insert(tk.END, details + "\n")
|
|
1529
|
+
|
|
1530
|
+
except Exception as e:
|
|
1531
|
+
self.add_result("Medium", "Headers Error", f"Header analysis failed: {str(e)}")
|
|
1532
|
+
|
|
1533
|
+
def test_sql_injection(self, url):
|
|
1534
|
+
payloads = [
|
|
1535
|
+
"'", "\"", "' OR '1'='1", "' OR 1=1--",
|
|
1536
|
+
"' OR 1=1#", "' OR 1=1/*", "' UNION SELECT null,version()--",
|
|
1537
|
+
"' UNION SELECT username,password FROM users--",
|
|
1538
|
+
"1 AND 1=1", "1 AND 1=2", "1' AND SLEEP(5)--",
|
|
1539
|
+
"1' OR IF(1=1,SLEEP(5),0)--"
|
|
1540
|
+
]
|
|
1541
|
+
|
|
1542
|
+
try:
|
|
1543
|
+
parsed = urlparse(url)
|
|
1544
|
+
params = parse_qs(parsed.query)
|
|
1545
|
+
|
|
1546
|
+
if not params:
|
|
1547
|
+
self.add_result("Info", "SQLi Test", "No parameters to test")
|
|
1548
|
+
return
|
|
1549
|
+
|
|
1550
|
+
vulnerable = False
|
|
1551
|
+
details = "SQL Injection Tests:\n"
|
|
1552
|
+
|
|
1553
|
+
for param in params:
|
|
1554
|
+
for payload in payloads:
|
|
1555
|
+
test_url = url.replace(f"{param}={params[param][0]}", f"{param}={payload}")
|
|
1556
|
+
try:
|
|
1557
|
+
start_time = time.time()
|
|
1558
|
+
response = requests.get(test_url, timeout=5)
|
|
1559
|
+
elapsed = time.time() - start_time
|
|
1560
|
+
|
|
1561
|
+
if self.detect_sql_errors(response.text):
|
|
1562
|
+
vulnerable = True
|
|
1563
|
+
details += f"Potential SQLi in {param} with payload: {payload} (Error-based)\n"
|
|
1564
|
+
break
|
|
1565
|
+
elif elapsed > 4: # Time-based detection
|
|
1566
|
+
vulnerable = True
|
|
1567
|
+
details += f"Potential SQLi in {param} with payload: {payload} (Time-based, {elapsed:.2f}s)\n"
|
|
1568
|
+
break
|
|
1569
|
+
elif payload in response.text: # Boolean-based detection
|
|
1570
|
+
vulnerable = True
|
|
1571
|
+
details += f"Potential SQLi in {param} with payload: {payload} (Boolean-based)\n"
|
|
1572
|
+
break
|
|
1573
|
+
except:
|
|
1574
|
+
continue
|
|
1575
|
+
|
|
1576
|
+
if vulnerable:
|
|
1577
|
+
self.add_result("High", "SQL Injection", "Potential SQLi vulnerabilities detected")
|
|
1578
|
+
else:
|
|
1579
|
+
self.add_result("Low", "SQL Injection", "No obvious SQLi vulnerabilities found")
|
|
1580
|
+
|
|
1581
|
+
self.details_text.insert(tk.END, details + "\n")
|
|
1582
|
+
|
|
1583
|
+
except Exception as e:
|
|
1584
|
+
self.add_result("Medium", "SQLi Error", f"SQLi test failed: {str(e)}")
|
|
1585
|
+
|
|
1586
|
+
def detect_sql_errors(self, content):
|
|
1587
|
+
errors = [
|
|
1588
|
+
"SQL syntax", "MySQL server", "ORA-", "syntax error",
|
|
1589
|
+
"unclosed quotation mark", "Microsoft OLE DB Provider",
|
|
1590
|
+
"ODBC Driver", "PostgreSQL", "SQLite", "MariaDB",
|
|
1591
|
+
"SQL error", "database error", "query failed",
|
|
1592
|
+
"syntax error", "unknown column", "table not found"
|
|
1593
|
+
]
|
|
1594
|
+
return any(error.lower() in content.lower() for error in errors)
|
|
1595
|
+
|
|
1596
|
+
def test_xss(self, url):
|
|
1597
|
+
payloads = [
|
|
1598
|
+
"<script>alert(1)</script>",
|
|
1599
|
+
"<img src=x onerror=alert(1)>",
|
|
1600
|
+
"\"><script>alert(1)</script>",
|
|
1601
|
+
"javascript:alert(1)",
|
|
1602
|
+
"onmouseover=alert(1)",
|
|
1603
|
+
"onload=alert(1)",
|
|
1604
|
+
"onfocus=alert(1)",
|
|
1605
|
+
"svg/onload=alert(1)",
|
|
1606
|
+
"alert`1`",
|
|
1607
|
+
"eval('alert(1)')"
|
|
1608
|
+
]
|
|
1609
|
+
|
|
1610
|
+
try:
|
|
1611
|
+
parsed = urlparse(url)
|
|
1612
|
+
params = parse_qs(parsed.query)
|
|
1613
|
+
|
|
1614
|
+
if not params:
|
|
1615
|
+
self.add_result("Info", "XSS Test", "No parameters to test")
|
|
1616
|
+
return
|
|
1617
|
+
|
|
1618
|
+
vulnerable = False
|
|
1619
|
+
details = "XSS Tests:\n"
|
|
1620
|
+
|
|
1621
|
+
for param in params:
|
|
1622
|
+
for payload in payloads:
|
|
1623
|
+
test_url = url.replace(f"{param}={params[param][0]}", f"{param}={payload}")
|
|
1624
|
+
try:
|
|
1625
|
+
response = requests.get(test_url, timeout=5)
|
|
1626
|
+
if payload in response.text:
|
|
1627
|
+
vulnerable = True
|
|
1628
|
+
details += f"Potential XSS in {param} with payload: {payload}\n"
|
|
1629
|
+
break
|
|
1630
|
+
except:
|
|
1631
|
+
continue
|
|
1632
|
+
|
|
1633
|
+
if vulnerable:
|
|
1634
|
+
self.add_result("High", "XSS", "Potential XSS vulnerabilities detected")
|
|
1635
|
+
else:
|
|
1636
|
+
self.add_result("Low", "XSS", "No obvious XSS vulnerabilities found")
|
|
1637
|
+
|
|
1638
|
+
self.details_text.insert(tk.END, details + "\n")
|
|
1639
|
+
|
|
1640
|
+
except Exception as e:
|
|
1641
|
+
self.add_result("Medium", "XSS Error", f"XSS test failed: {str(e)}")
|
|
1642
|
+
|
|
1643
|
+
def test_csrf(self, url):
|
|
1644
|
+
try:
|
|
1645
|
+
response = requests.get(url, timeout=10)
|
|
1646
|
+
soup = BeautifulSoup(response.text, 'html.parser')
|
|
1647
|
+
forms = soup.find_all('form')
|
|
1648
|
+
|
|
1649
|
+
vulnerable = False
|
|
1650
|
+
details = "CSRF Tests:\n"
|
|
1651
|
+
|
|
1652
|
+
for form in forms:
|
|
1653
|
+
if not form.find('input', {'name': 'csrf_token'}) and \
|
|
1654
|
+
not form.find('input', {'name': 'csrfmiddlewaretoken'}) and \
|
|
1655
|
+
not form.find('input', {'name': '_token'}):
|
|
1656
|
+
vulnerable = True
|
|
1657
|
+
action = form.get('action', 'current URL')
|
|
1658
|
+
method = form.get('method', 'GET').upper()
|
|
1659
|
+
details += f"Form without CSRF protection found (Action: {action}, Method: {method})\n"
|
|
1660
|
+
break
|
|
1661
|
+
|
|
1662
|
+
if vulnerable:
|
|
1663
|
+
self.add_result("Medium", "CSRF", "Forms without CSRF protection detected")
|
|
1664
|
+
else:
|
|
1665
|
+
self.add_result("Low", "CSRF", "No obvious CSRF vulnerabilities found")
|
|
1666
|
+
|
|
1667
|
+
self.details_text.insert(tk.END, details + "\n")
|
|
1668
|
+
|
|
1669
|
+
except Exception as e:
|
|
1670
|
+
self.add_result("Medium", "CSRF Error", f"CSRF test failed: {str(e)}")
|
|
1671
|
+
|
|
1672
|
+
def test_directory_traversal(self, url):
|
|
1673
|
+
payloads = [
|
|
1674
|
+
"../../../../etc/passwd",
|
|
1675
|
+
"..%2F..%2F..%2Fetc%2Fpasswd",
|
|
1676
|
+
"....//....//etc/passwd",
|
|
1677
|
+
"%2e%2e%2fetc%2fpasswd",
|
|
1678
|
+
"..\\..\\..\\windows\\win.ini",
|
|
1679
|
+
"%2e%2e%5cwindows%5cwin.ini"
|
|
1680
|
+
]
|
|
1681
|
+
|
|
1682
|
+
try:
|
|
1683
|
+
parsed = urlparse(url)
|
|
1684
|
+
base_url = f"{parsed.scheme}://{parsed.netloc}"
|
|
1685
|
+
paths = [p for p in parsed.path.split('/') if p]
|
|
1686
|
+
|
|
1687
|
+
vulnerable = False
|
|
1688
|
+
details = "Directory Traversal Tests:\n"
|
|
1689
|
+
|
|
1690
|
+
for payload in payloads:
|
|
1691
|
+
test_url = f"{base_url}/{payload}"
|
|
1692
|
+
try:
|
|
1693
|
+
response = requests.get(test_url, timeout=5)
|
|
1694
|
+
if "root:" in response.text or "bin:" in response.text or "[fonts]" in response.text:
|
|
1695
|
+
vulnerable = True
|
|
1696
|
+
details += f"Potential directory traversal with payload: {payload}\n"
|
|
1697
|
+
break
|
|
1698
|
+
except:
|
|
1699
|
+
continue
|
|
1700
|
+
|
|
1701
|
+
if vulnerable:
|
|
1702
|
+
self.add_result("High", "Directory Traversal", "Potential directory traversal vulnerabilities detected")
|
|
1703
|
+
else:
|
|
1704
|
+
self.add_result("Low", "Directory Traversal", "No obvious directory traversal vulnerabilities found")
|
|
1705
|
+
|
|
1706
|
+
self.details_text.insert(tk.END, details + "\n")
|
|
1707
|
+
|
|
1708
|
+
except Exception as e:
|
|
1709
|
+
self.add_result("Medium", "Dir Traversal Error", f"Directory traversal test failed: {str(e)}")
|
|
1710
|
+
|
|
1711
|
+
def test_command_injection(self, url):
|
|
1712
|
+
payloads = [
|
|
1713
|
+
";id", "|id", "`id`", "$(id)",
|
|
1714
|
+
"|| ping -c 1 localhost", "&& ping -c 1 localhost",
|
|
1715
|
+
"| dir", "&& dir", "; dir",
|
|
1716
|
+
"| ls", "&& ls", "; ls"
|
|
1717
|
+
]
|
|
1718
|
+
|
|
1719
|
+
try:
|
|
1720
|
+
parsed = urlparse(url)
|
|
1721
|
+
params = parse_qs(parsed.query)
|
|
1722
|
+
|
|
1723
|
+
if not params:
|
|
1724
|
+
self.add_result("Info", "Command Injection", "No parameters to test")
|
|
1725
|
+
return
|
|
1726
|
+
|
|
1727
|
+
vulnerable = False
|
|
1728
|
+
details = "Command Injection Tests:\n"
|
|
1729
|
+
|
|
1730
|
+
for param in params:
|
|
1731
|
+
for payload in payloads:
|
|
1732
|
+
test_url = url.replace(f"{param}={params[param][0]}", f"{param}={params[param][0]}{payload}")
|
|
1733
|
+
try:
|
|
1734
|
+
response = requests.get(test_url, timeout=5)
|
|
1735
|
+
if "uid=" in response.text or "bytes from" in response.text or "Volume Serial" in response.text:
|
|
1736
|
+
vulnerable = True
|
|
1737
|
+
details += f"Potential command injection in {param} with payload: {payload}\n"
|
|
1738
|
+
break
|
|
1739
|
+
except:
|
|
1740
|
+
continue
|
|
1741
|
+
|
|
1742
|
+
if vulnerable:
|
|
1743
|
+
self.add_result("High", "Command Injection", "Potential command injection vulnerabilities detected")
|
|
1744
|
+
else:
|
|
1745
|
+
self.add_result("Low", "Command Injection", "No obvious command injection vulnerabilities found")
|
|
1746
|
+
|
|
1747
|
+
self.details_text.insert(tk.END, details + "\n")
|
|
1748
|
+
|
|
1749
|
+
except Exception as e:
|
|
1750
|
+
self.add_result("Medium", "CMD Injection Error", f"Command injection test failed: {str(e)}")
|
|
1751
|
+
|
|
1752
|
+
def test_ssrf(self, url):
|
|
1753
|
+
"""Test for Server-Side Request Forgery vulnerabilities"""
|
|
1754
|
+
test_servers = [
|
|
1755
|
+
"http://169.254.169.254/latest/meta-data/", # AWS metadata
|
|
1756
|
+
"http://metadata.google.internal/computeMetadata/v1/", # GCP metadata
|
|
1757
|
+
"http://169.254.169.253/latest/meta-data/", # Azure metadata
|
|
1758
|
+
"http://localhost:80",
|
|
1759
|
+
"http://127.0.0.1:80"
|
|
1760
|
+
]
|
|
1761
|
+
|
|
1762
|
+
try:
|
|
1763
|
+
parsed = urlparse(url)
|
|
1764
|
+
params = parse_qs(parsed.query)
|
|
1765
|
+
|
|
1766
|
+
if not params:
|
|
1767
|
+
self.add_result("Info", "SSRF Test", "No parameters to test")
|
|
1768
|
+
return
|
|
1769
|
+
|
|
1770
|
+
vulnerable = False
|
|
1771
|
+
details = "SSRF Tests:\n"
|
|
1772
|
+
|
|
1773
|
+
for param in params:
|
|
1774
|
+
for server in test_servers:
|
|
1775
|
+
test_url = url.replace(f"{param}={params[param][0]}", f"{param}={server}")
|
|
1776
|
+
try:
|
|
1777
|
+
response = requests.get(test_url, timeout=5)
|
|
1778
|
+
if response.status_code == 200 and any(
|
|
1779
|
+
keyword in response.text.lower()
|
|
1780
|
+
for keyword in ["instance-id", "ami-id", "compute", "metadata"]
|
|
1781
|
+
):
|
|
1782
|
+
vulnerable = True
|
|
1783
|
+
details += f"Potential SSRF in {param} with payload: {server}\n"
|
|
1784
|
+
break
|
|
1785
|
+
except:
|
|
1786
|
+
continue
|
|
1787
|
+
|
|
1788
|
+
if vulnerable:
|
|
1789
|
+
self.add_result("High", "SSRF", "Potential SSRF vulnerabilities detected")
|
|
1790
|
+
else:
|
|
1791
|
+
self.add_result("Low", "SSRF", "No obvious SSRF vulnerabilities found")
|
|
1792
|
+
|
|
1793
|
+
self.details_text.insert(tk.END, details + "\n")
|
|
1794
|
+
|
|
1795
|
+
except Exception as e:
|
|
1796
|
+
self.add_result("Medium", "SSRF Error", f"SSRF test failed: {str(e)}")
|
|
1797
|
+
|
|
1798
|
+
def test_xxe(self, url):
|
|
1799
|
+
"""Test for XML External Entity vulnerabilities"""
|
|
1800
|
+
xxe_payload = """<?xml version="1.0" encoding="ISO-8859-1"?>
|
|
1801
|
+
<!DOCTYPE foo [ <!ELEMENT foo ANY >
|
|
1802
|
+
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
|
|
1803
|
+
<foo>&xxe;</foo>"""
|
|
1804
|
+
|
|
1805
|
+
try:
|
|
1806
|
+
# First check if the endpoint accepts XML
|
|
1807
|
+
response = requests.get(url, timeout=5)
|
|
1808
|
+
if "xml" not in response.headers.get('content-type', '').lower():
|
|
1809
|
+
self.add_result("Info", "XXE Test", "Endpoint doesn't appear to accept XML")
|
|
1810
|
+
return
|
|
1811
|
+
|
|
1812
|
+
# Try XXE injection
|
|
1813
|
+
headers = {'Content-Type': 'application/xml'}
|
|
1814
|
+
response = requests.post(url, data=xxe_payload, headers=headers, timeout=5)
|
|
1815
|
+
|
|
1816
|
+
if "root:" in response.text:
|
|
1817
|
+
self.add_result("Critical", "XXE", "XML External Entity injection vulnerability detected")
|
|
1818
|
+
self.details_text.insert(tk.END, "XXE Test:\nVulnerable to XXE injection\n")
|
|
1819
|
+
else:
|
|
1820
|
+
self.add_result("Low", "XXE", "No obvious XXE vulnerabilities found")
|
|
1821
|
+
self.details_text.insert(tk.END, "XXE Test:\nNo XXE vulnerability detected\n")
|
|
1822
|
+
|
|
1823
|
+
except Exception as e:
|
|
1824
|
+
self.add_result("Medium", "XXE Error", f"XXE test failed: {str(e)}")
|
|
1825
|
+
|
|
1826
|
+
def test_idor(self, url):
|
|
1827
|
+
"""Test for Insecure Direct Object References"""
|
|
1828
|
+
try:
|
|
1829
|
+
# This is a simple test that checks for sequential IDs
|
|
1830
|
+
# In a real scanner, you'd need more sophisticated tests
|
|
1831
|
+
parsed = urlparse(url)
|
|
1832
|
+
params = parse_qs(parsed.query)
|
|
1833
|
+
|
|
1834
|
+
id_params = [p for p in params if 'id' in p.lower()]
|
|
1835
|
+
|
|
1836
|
+
if not id_params:
|
|
1837
|
+
self.add_result("Info", "IDOR Test", "No ID parameters to test")
|
|
1838
|
+
return
|
|
1839
|
+
|
|
1840
|
+
vulnerable = False
|
|
1841
|
+
details = "IDOR Tests:\n"
|
|
1842
|
+
|
|
1843
|
+
for param in id_params:
|
|
1844
|
+
original_id = params[param][0]
|
|
1845
|
+
if original_id.isdigit():
|
|
1846
|
+
test_id = str(int(original_id) + 1)
|
|
1847
|
+
test_url = url.replace(f"{param}={original_id}", f"{param}={test_id}")
|
|
1848
|
+
|
|
1849
|
+
try:
|
|
1850
|
+
original_response = requests.get(url, timeout=5)
|
|
1851
|
+
test_response = requests.get(test_url, timeout=5)
|
|
1852
|
+
|
|
1853
|
+
if test_response.status_code == 200 and \
|
|
1854
|
+
test_response.text != original_response.text and \
|
|
1855
|
+
len(test_response.text) > 100: # Basic check to avoid 404 pages
|
|
1856
|
+
vulnerable = True
|
|
1857
|
+
details += f"Potential IDOR in {param} by changing {original_id} to {test_id}\n"
|
|
1858
|
+
except:
|
|
1859
|
+
continue
|
|
1860
|
+
|
|
1861
|
+
if vulnerable:
|
|
1862
|
+
self.add_result("Medium", "IDOR", "Potential IDOR vulnerabilities detected")
|
|
1863
|
+
else:
|
|
1864
|
+
self.add_result("Low", "IDOR", "No obvious IDOR vulnerabilities found")
|
|
1865
|
+
|
|
1866
|
+
self.details_text.insert(tk.END, details + "\n")
|
|
1867
|
+
|
|
1868
|
+
except Exception as e:
|
|
1869
|
+
self.add_result("Medium", "IDOR Error", f"IDOR test failed: {str(e)}")
|
|
1870
|
+
|
|
1871
|
+
def crawl_pages(self, base_url):
|
|
1872
|
+
try:
|
|
1873
|
+
visited = set()
|
|
1874
|
+
to_visit = {base_url}
|
|
1875
|
+
max_pages = self.scan_config['max_pages']
|
|
1876
|
+
|
|
1877
|
+
details = "Crawling Results:\n"
|
|
1878
|
+
|
|
1879
|
+
while to_visit and len(visited) < max_pages:
|
|
1880
|
+
url = to_visit.pop()
|
|
1881
|
+
if url in visited:
|
|
1882
|
+
continue
|
|
1883
|
+
|
|
1884
|
+
try:
|
|
1885
|
+
response = requests.get(url, timeout=5)
|
|
1886
|
+
visited.add(url)
|
|
1887
|
+
details += f"Found: {url} ({response.status_code})\n"
|
|
1888
|
+
|
|
1889
|
+
soup = BeautifulSoup(response.text, 'html.parser')
|
|
1890
|
+
|
|
1891
|
+
# Find links
|
|
1892
|
+
for link in soup.find_all('a', href=True):
|
|
1893
|
+
href = link['href']
|
|
1894
|
+
if href.startswith('http') and base_url in href and href not in visited:
|
|
1895
|
+
to_visit.add(href)
|
|
1896
|
+
elif href.startswith('/'):
|
|
1897
|
+
absolute = urljoin(base_url, href)
|
|
1898
|
+
if absolute not in visited:
|
|
1899
|
+
to_visit.add(absolute)
|
|
1900
|
+
|
|
1901
|
+
# Check for forms
|
|
1902
|
+
forms = soup.find_all('form')
|
|
1903
|
+
if forms:
|
|
1904
|
+
details += f"Found {len(forms)} forms on {url}\n"
|
|
1905
|
+
|
|
1906
|
+
# Check for sensitive content
|
|
1907
|
+
sensitive_keywords = [
|
|
1908
|
+
'password', 'secret', 'key', 'token',
|
|
1909
|
+
'admin', 'login', 'credentials', 'backup'
|
|
1910
|
+
]
|
|
1911
|
+
page_text = soup.get_text().lower()
|
|
1912
|
+
found_keywords = [kw for kw in sensitive_keywords if kw in page_text]
|
|
1913
|
+
if found_keywords:
|
|
1914
|
+
details += f"Found sensitive keywords: {', '.join(found_keywords)}\n"
|
|
1915
|
+
|
|
1916
|
+
self.root.update()
|
|
1917
|
+
except:
|
|
1918
|
+
continue
|
|
1919
|
+
|
|
1920
|
+
details += f"\nCrawled {len(visited)} pages\n"
|
|
1921
|
+
self.details_text.insert(tk.END, details + "\n")
|
|
1922
|
+
|
|
1923
|
+
if len(visited) >= max_pages:
|
|
1924
|
+
self.add_result("Info", "Crawl Limit", f"Limited to {max_pages} pages for demo")
|
|
1925
|
+
|
|
1926
|
+
except Exception as e:
|
|
1927
|
+
self.add_result("Medium", "Crawl Error", f"Crawling failed: {str(e)}")
|
|
1928
|
+
|
|
1929
|
+
if __name__ == "__main__":
|
|
1930
|
+
root = tk.Tk()
|
|
1931
|
+
|
|
1932
|
+
# Set Windows style if available
|
|
1933
|
+
if sys.platform == "win32":
|
|
1934
|
+
from ctypes import windll
|
|
1935
|
+
windll.shcore.SetProcessDpiAwareness(1)
|
|
1936
|
+
|
|
1937
|
+
app = AdvancedVulnerabilityScanner(root)
|
|
1938
|
+
root.mainloop()
|