astrotools-cli 1.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.
- astrotools/__init__.py +3 -0
- astrotools/__main__.py +4 -0
- astrotools/cli.py +1345 -0
- astrotools_cli-1.0.0.dist-info/METADATA +114 -0
- astrotools_cli-1.0.0.dist-info/RECORD +9 -0
- astrotools_cli-1.0.0.dist-info/WHEEL +5 -0
- astrotools_cli-1.0.0.dist-info/entry_points.txt +2 -0
- astrotools_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
- astrotools_cli-1.0.0.dist-info/top_level.txt +1 -0
astrotools/cli.py
ADDED
|
@@ -0,0 +1,1345 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
===============================================================================
|
|
4
|
+
SUPER TOOLKIT - ULTIMATE EDITION
|
|
5
|
+
Comprehensive Network Reconnaissance, Security & Cryptography
|
|
6
|
+
===============================================================================
|
|
7
|
+
"""
|
|
8
|
+
import hashlib
|
|
9
|
+
import ipaddress
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import random
|
|
13
|
+
import re
|
|
14
|
+
import socket
|
|
15
|
+
import string
|
|
16
|
+
import struct
|
|
17
|
+
import sys
|
|
18
|
+
import time
|
|
19
|
+
import ssl
|
|
20
|
+
import subprocess
|
|
21
|
+
from urllib.parse import urlparse
|
|
22
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
23
|
+
import requests
|
|
24
|
+
import questionary
|
|
25
|
+
from rich.console import Console
|
|
26
|
+
from rich.panel import Panel
|
|
27
|
+
from rich.table import Table
|
|
28
|
+
from rich.progress import track, Progress, SpinnerColumn, TextColumn
|
|
29
|
+
|
|
30
|
+
# Initialize Rich Console
|
|
31
|
+
console = Console()
|
|
32
|
+
|
|
33
|
+
# Enable ANSI colors on Windows legacy terminals if needed
|
|
34
|
+
if os.name == 'nt':
|
|
35
|
+
os.system('')
|
|
36
|
+
|
|
37
|
+
# Custom Questionary Style
|
|
38
|
+
MENU_STYLE = questionary.Style([
|
|
39
|
+
('qmark', 'fg:cyan bold'),
|
|
40
|
+
('question', 'fg:cyan bold'),
|
|
41
|
+
('answer', 'fg:green bold'),
|
|
42
|
+
('pointer', 'fg:cyan bold'),
|
|
43
|
+
('highlighted', 'fg:black bg:cyan bold'),
|
|
44
|
+
('selected', 'fg:green bold'),
|
|
45
|
+
('separator', 'fg:gray'),
|
|
46
|
+
('instruction', 'fg:gray italic'),
|
|
47
|
+
])
|
|
48
|
+
|
|
49
|
+
def clear_screen():
|
|
50
|
+
"""Clears terminal screen cross-platform."""
|
|
51
|
+
os.system('cls' if os.name == 'nt' else 'clear')
|
|
52
|
+
|
|
53
|
+
def show_banner():
|
|
54
|
+
"""Displays the stylized AstroTools banner."""
|
|
55
|
+
|
|
56
|
+
clear_screen()
|
|
57
|
+
|
|
58
|
+
banner_art = r"""
|
|
59
|
+
█████╗ ███████╗████████╗██████╗ ██████╗ ████████╗ ██████╗ ██████╗ ██╗ ███████╗
|
|
60
|
+
██╔══██╗██╔════╝╚══██╔══╝██╔══██╗██╔═══██╗ ╚══██╔══╝██╔═══██╗██╔═══██╗██║ ██╔════╝
|
|
61
|
+
███████║███████╗ ██║ ██████╔╝██║ ██║ ██║ ██║ ██║██║ ██║██║ ███████╗
|
|
62
|
+
██╔══██║╚════██║ ██║ ██╔══██╗██║ ██║ ██║ ██║ ██║██║ ██║██║ ╚════██║
|
|
63
|
+
██║ ██║███████║ ██║ ██║ ██║╚██████╔╝ ██║ ╚██████╔╝╚██████╔╝███████╗ ███████║
|
|
64
|
+
╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚══════╝
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
console.print(banner_art, style="bold cyan")
|
|
70
|
+
console.print(Panel(
|
|
71
|
+
"[bold green]⚡ AstroTools Beta (Proffessional Networking tools)[/bold green]\n"
|
|
72
|
+
"[italic gray]By CINEFy. Future updates comming soon...[/italic gray]",
|
|
73
|
+
border_style="cyan"
|
|
74
|
+
))
|
|
75
|
+
print()
|
|
76
|
+
|
|
77
|
+
def pause():
|
|
78
|
+
"""Prompts user to press Enter before returning to menu."""
|
|
79
|
+
console.print("\n[dim]Press [bold]Enter[/bold] to return to main menu...[/dim]")
|
|
80
|
+
try:
|
|
81
|
+
input()
|
|
82
|
+
except (KeyboardInterrupt, EOFError):
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
def clean_host(target):
|
|
86
|
+
"""Normalizes a URL, hostname, or host:port string to just the hostname."""
|
|
87
|
+
target = target.strip()
|
|
88
|
+
if not target:
|
|
89
|
+
return ""
|
|
90
|
+
if "://" in target:
|
|
91
|
+
parsed = urlparse(target)
|
|
92
|
+
return parsed.hostname or ""
|
|
93
|
+
return target.split("/")[0].split(":")[0].strip()
|
|
94
|
+
|
|
95
|
+
def ensure_url(target, default_scheme="https"):
|
|
96
|
+
"""Returns a URL with a scheme for HTTP tools."""
|
|
97
|
+
target = target.strip()
|
|
98
|
+
if not target:
|
|
99
|
+
return ""
|
|
100
|
+
if target.startswith(("http://", "https://")):
|
|
101
|
+
return target
|
|
102
|
+
return f"{default_scheme}://{target}"
|
|
103
|
+
|
|
104
|
+
def run_command(cmd, timeout=12):
|
|
105
|
+
"""Runs a system command and returns combined stdout/stderr text."""
|
|
106
|
+
try:
|
|
107
|
+
result = subprocess.run(
|
|
108
|
+
cmd,
|
|
109
|
+
capture_output=True,
|
|
110
|
+
text=True,
|
|
111
|
+
encoding="utf-8",
|
|
112
|
+
errors="replace",
|
|
113
|
+
timeout=timeout,
|
|
114
|
+
)
|
|
115
|
+
output = (result.stdout or "") + (result.stderr or "")
|
|
116
|
+
return output.strip(), result.returncode
|
|
117
|
+
except FileNotFoundError:
|
|
118
|
+
return f"Command not found: {cmd[0]}", 127
|
|
119
|
+
except subprocess.TimeoutExpired:
|
|
120
|
+
return f"Command timed out after {timeout}s.", 124
|
|
121
|
+
|
|
122
|
+
# =============================================================================
|
|
123
|
+
# 1. CRYPTOGRAPHY & SECURITY TOOLS
|
|
124
|
+
# =============================================================================
|
|
125
|
+
def password_generator():
|
|
126
|
+
"""Generates strong passwords and evaluates security strength."""
|
|
127
|
+
show_banner()
|
|
128
|
+
console.print(Panel("[bold yellow]🔑 Password Generator & Strength Evaluator[/bold yellow]", border_style="yellow"))
|
|
129
|
+
try:
|
|
130
|
+
raw_len = console.input("[bold cyan]Enter password length [default: 16]: [/bold cyan]").strip()
|
|
131
|
+
length = int(raw_len) if raw_len else 16
|
|
132
|
+
if length < 4:
|
|
133
|
+
console.print("[yellow][!] Minimum length is 4. Adjusted to 4.[/yellow]")
|
|
134
|
+
length = 4
|
|
135
|
+
except ValueError:
|
|
136
|
+
console.print("[yellow][!] Invalid number. Using default length of 16.[/yellow]")
|
|
137
|
+
length = 16
|
|
138
|
+
|
|
139
|
+
use_symbols = questionary.confirm("Include special symbols (!@#$%^&*...)?", default=True, style=MENU_STYLE).ask()
|
|
140
|
+
use_digits = questionary.confirm("Include numbers (0-9)?", default=True, style=MENU_STYLE).ask()
|
|
141
|
+
use_upper = questionary.confirm("Include uppercase letters (A-Z)?", default=True, style=MENU_STYLE).ask()
|
|
142
|
+
|
|
143
|
+
char_pool = string.ascii_lowercase
|
|
144
|
+
guaranteed = [random.choice(string.ascii_lowercase)]
|
|
145
|
+
if use_upper:
|
|
146
|
+
char_pool += string.ascii_uppercase
|
|
147
|
+
guaranteed.append(random.choice(string.ascii_uppercase))
|
|
148
|
+
if use_digits:
|
|
149
|
+
char_pool += string.digits
|
|
150
|
+
guaranteed.append(random.choice(string.digits))
|
|
151
|
+
if use_symbols:
|
|
152
|
+
symbols = "!@#$%^&*()_+-=[]{}|;:,.<>?"
|
|
153
|
+
char_pool += symbols
|
|
154
|
+
guaranteed.append(random.choice(symbols))
|
|
155
|
+
|
|
156
|
+
remaining = length - len(guaranteed)
|
|
157
|
+
password_list = guaranteed + [random.choice(char_pool) for _ in range(max(0, remaining))]
|
|
158
|
+
random.shuffle(password_list)
|
|
159
|
+
password = "".join(password_list)
|
|
160
|
+
|
|
161
|
+
# Score calculation
|
|
162
|
+
score = 0
|
|
163
|
+
if len(password) >= 16: score += 3
|
|
164
|
+
elif len(password) >= 12: score += 2
|
|
165
|
+
elif len(password) >= 8: score += 1
|
|
166
|
+
if any(c in string.ascii_lowercase for c in password): score += 1
|
|
167
|
+
if any(c in string.ascii_uppercase for c in password): score += 1
|
|
168
|
+
if any(c in string.digits for c in password): score += 1
|
|
169
|
+
if any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password): score += 1
|
|
170
|
+
|
|
171
|
+
if score >= 6: rating = "[bold green]Very Strong / Excellent[/bold green]"
|
|
172
|
+
elif score >= 4: rating = "[bold cyan]Strong / Good[/bold cyan]"
|
|
173
|
+
elif score >= 3: rating = "[bold yellow]Moderate[/bold yellow]"
|
|
174
|
+
else: rating = "[bold red]Weak[/bold red]"
|
|
175
|
+
|
|
176
|
+
table = Table(title="Generated Password Details", border_style="cyan")
|
|
177
|
+
table.add_column("Property", style="bold cyan")
|
|
178
|
+
table.add_column("Value", style="bold white")
|
|
179
|
+
table.add_row("Password", f"[bold green]{password}[/bold green]")
|
|
180
|
+
table.add_row("Length", str(len(password)))
|
|
181
|
+
table.add_row("Strength Rating", rating)
|
|
182
|
+
table.add_row("Entropy Estimate", f"~{len(password) * 6:.1f} bits")
|
|
183
|
+
console.print("\n", table)
|
|
184
|
+
pause()
|
|
185
|
+
|
|
186
|
+
def hash_tool():
|
|
187
|
+
"""Generates and verifies cryptographic hashes."""
|
|
188
|
+
show_banner()
|
|
189
|
+
console.print(Panel("[bold yellow]🔒 Hash Generator & Verifier[/bold yellow]", border_style="yellow"))
|
|
190
|
+
action = questionary.select(
|
|
191
|
+
"Select Hash Operation:",
|
|
192
|
+
choices=[
|
|
193
|
+
"1. Generate Hashes for String (MD5, SHA1, SHA256, SHA512, etc.)",
|
|
194
|
+
"2. Verify Hash Match",
|
|
195
|
+
"Back to Main Menu"
|
|
196
|
+
],
|
|
197
|
+
style=MENU_STYLE
|
|
198
|
+
).ask()
|
|
199
|
+
|
|
200
|
+
if not action or action == "Back to Main Menu":
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
if "1. Generate" in action:
|
|
204
|
+
text = console.input("\n[bold cyan]Enter text to hash: [/bold cyan]")
|
|
205
|
+
encoded = text.encode('utf-8', errors='replace')
|
|
206
|
+
table = Table(title=f"Hashes for '{text}'", border_style="cyan")
|
|
207
|
+
table.add_column("Algorithm", style="cyan bold", width=12)
|
|
208
|
+
table.add_column("Hash Hex Digest", style="green")
|
|
209
|
+
|
|
210
|
+
algorithms = [
|
|
211
|
+
("MD5", hashlib.md5(encoded).hexdigest()),
|
|
212
|
+
("SHA-1", hashlib.sha1(encoded).hexdigest()),
|
|
213
|
+
("SHA-224", hashlib.sha224(encoded).hexdigest()),
|
|
214
|
+
("SHA-256", hashlib.sha256(encoded).hexdigest()),
|
|
215
|
+
("SHA-384", hashlib.sha384(encoded).hexdigest()),
|
|
216
|
+
("SHA-512", hashlib.sha512(encoded).hexdigest()),
|
|
217
|
+
]
|
|
218
|
+
for algo, h_val in algorithms:
|
|
219
|
+
table.add_row(algo, h_val)
|
|
220
|
+
console.print("\n", table)
|
|
221
|
+
|
|
222
|
+
elif "2. Verify" in action:
|
|
223
|
+
text = console.input("\n[bold cyan]Enter plain text: [/bold cyan]")
|
|
224
|
+
target_hash = console.input("[bold cyan]Enter hash to verify against: [/bold cyan]").strip().lower()
|
|
225
|
+
encoded = text.encode('utf-8', errors='replace')
|
|
226
|
+
hash_map = {
|
|
227
|
+
"MD5": hashlib.md5(encoded).hexdigest(),
|
|
228
|
+
"SHA-1": hashlib.sha1(encoded).hexdigest(),
|
|
229
|
+
"SHA-224": hashlib.sha224(encoded).hexdigest(),
|
|
230
|
+
"SHA-256": hashlib.sha256(encoded).hexdigest(),
|
|
231
|
+
"SHA-384": hashlib.sha384(encoded).hexdigest(),
|
|
232
|
+
"SHA-512": hashlib.sha512(encoded).hexdigest(),
|
|
233
|
+
}
|
|
234
|
+
match_found = None
|
|
235
|
+
for algo, h_val in hash_map.items():
|
|
236
|
+
if h_val.lower() == target_hash:
|
|
237
|
+
match_found = algo
|
|
238
|
+
break
|
|
239
|
+
|
|
240
|
+
if match_found:
|
|
241
|
+
console.print(Panel(
|
|
242
|
+
f"[bold green]✔ MATCH CONFIRMED![/bold green]\n"
|
|
243
|
+
f"The provided hash matches [bold cyan]{match_found}[/bold cyan] of the text.",
|
|
244
|
+
title="Result", border_style="green"
|
|
245
|
+
))
|
|
246
|
+
else:
|
|
247
|
+
console.print(Panel(
|
|
248
|
+
f"[bold red]✘ MISMATCH![/bold red]\n"
|
|
249
|
+
f"The provided hash does NOT match any standard algorithm.",
|
|
250
|
+
title="Result", border_style="red"
|
|
251
|
+
))
|
|
252
|
+
pause()
|
|
253
|
+
|
|
254
|
+
# =============================================================================
|
|
255
|
+
# 2. NETWORK INTELLIGENCE & RECON TOOLS
|
|
256
|
+
# =============================================================================
|
|
257
|
+
def my_network_info():
|
|
258
|
+
"""Displays local network parameters and public IP with geo summary."""
|
|
259
|
+
show_banner()
|
|
260
|
+
console.print(Panel("[bold yellow]📡 My Network & Public IP Info[/bold yellow]", border_style="yellow"))
|
|
261
|
+
hostname = socket.gethostname()
|
|
262
|
+
try:
|
|
263
|
+
local_ip = socket.gethostbyname(hostname)
|
|
264
|
+
except Exception:
|
|
265
|
+
local_ip = "127.0.0.1"
|
|
266
|
+
|
|
267
|
+
table = Table(title="Host & Public Network Information", border_style="cyan")
|
|
268
|
+
table.add_column("Property", style="bold cyan")
|
|
269
|
+
table.add_column("Value", style="bold green")
|
|
270
|
+
table.add_row("Hostname", hostname)
|
|
271
|
+
table.add_row("Local IP", local_ip)
|
|
272
|
+
|
|
273
|
+
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True) as progress:
|
|
274
|
+
progress.add_task(description="Fetching public IP & geolocation data...", total=None)
|
|
275
|
+
try:
|
|
276
|
+
res = requests.get("http://ip-api.com/json/", timeout=6).json()
|
|
277
|
+
if res.get("status") == "success":
|
|
278
|
+
table.add_row("Public IP", res.get("query", "Unknown"))
|
|
279
|
+
table.add_row("Country", f"{res.get('country')} ({res.get('countryCode')})")
|
|
280
|
+
table.add_row("Region / City", f"{res.get('regionName')}, {res.get('city')}")
|
|
281
|
+
table.add_row("ISP / Organization", f"{res.get('isp')} / {res.get('org')}")
|
|
282
|
+
table.add_row("Coordinates", f"{res.get('lat')}, {res.get('lon')}")
|
|
283
|
+
table.add_row("Timezone", res.get("timezone", "N/A"))
|
|
284
|
+
else:
|
|
285
|
+
ip_res = requests.get("https://api.ipify.org?format=json", timeout=5).json()
|
|
286
|
+
table.add_row("Public IP", ip_res.get("ip", "Unknown"))
|
|
287
|
+
except Exception as e:
|
|
288
|
+
table.add_row("Public IP", f"[red]Failed to retrieve ({e})[/red]")
|
|
289
|
+
console.print("\n", table)
|
|
290
|
+
pause()
|
|
291
|
+
|
|
292
|
+
def target_ip_lookup():
|
|
293
|
+
"""Performs DNS resolution and reverse lookup on a target."""
|
|
294
|
+
show_banner()
|
|
295
|
+
console.print(Panel("[bold yellow]🌐 Target Website / Domain IP Lookup[/bold yellow]", border_style="yellow"))
|
|
296
|
+
target = console.input("[bold cyan]Enter domain or website URL (e.g. google.com): [/bold cyan]").strip()
|
|
297
|
+
if not target:
|
|
298
|
+
console.print("[red]No domain provided.[/red]")
|
|
299
|
+
pause()
|
|
300
|
+
return
|
|
301
|
+
|
|
302
|
+
clean_target = target.replace("https://", "").replace("http://", "").split("/")[0].split(":")[0]
|
|
303
|
+
try:
|
|
304
|
+
resolved_ip = socket.gethostbyname(clean_target)
|
|
305
|
+
try:
|
|
306
|
+
host_alias = socket.gethostbyaddr(resolved_ip)[0]
|
|
307
|
+
except Exception:
|
|
308
|
+
host_alias = "None / Unresolved PTR"
|
|
309
|
+
try:
|
|
310
|
+
addr_info = socket.getaddrinfo(clean_target, 80)
|
|
311
|
+
all_ips = sorted(list(set(item[4][0] for item in addr_info)))
|
|
312
|
+
except Exception:
|
|
313
|
+
all_ips = [resolved_ip]
|
|
314
|
+
|
|
315
|
+
table = Table(title=f"DNS Lookup: {clean_target}", border_style="cyan")
|
|
316
|
+
table.add_column("Property", style="bold cyan")
|
|
317
|
+
table.add_column("Value", style="bold green")
|
|
318
|
+
table.add_row("Target Domain", clean_target)
|
|
319
|
+
table.add_row("Primary IP", resolved_ip)
|
|
320
|
+
table.add_row("Resolved IP List", ", ".join(all_ips))
|
|
321
|
+
table.add_row("Reverse DNS (PTR)", host_alias)
|
|
322
|
+
console.print("\n", table)
|
|
323
|
+
except socket.gaierror:
|
|
324
|
+
console.print(f"\n[red]✘ Error: Could not resolve domain '{clean_target}'.[/red]")
|
|
325
|
+
except Exception as e:
|
|
326
|
+
console.print(f"\n[red]✘ Unexpected error: {e}[/red]")
|
|
327
|
+
pause()
|
|
328
|
+
|
|
329
|
+
def geoip_lookup():
|
|
330
|
+
"""Fetches GeoIP intelligence for any IP or domain."""
|
|
331
|
+
show_banner()
|
|
332
|
+
console.print(Panel("[bold yellow]🌍 GeoIP Intelligence Tracker[/bold yellow]", border_style="yellow"))
|
|
333
|
+
target = console.input("[bold cyan]Enter IP or Domain to trace (leave empty for your IP): [/bold cyan]").strip()
|
|
334
|
+
if target:
|
|
335
|
+
target = target.replace("https://", "").replace("http://", "").split("/")[0]
|
|
336
|
+
endpoint = f"http://ip-api.com/json/{target}" if target else "http://ip-api.com/json/"
|
|
337
|
+
|
|
338
|
+
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True) as progress:
|
|
339
|
+
progress.add_task(description="Querying GeoIP satellite & routing database...", total=None)
|
|
340
|
+
try:
|
|
341
|
+
res = requests.get(endpoint, timeout=6).json()
|
|
342
|
+
except Exception as e:
|
|
343
|
+
res = {"status": "fail", "message": str(e)}
|
|
344
|
+
|
|
345
|
+
if res.get("status") == "success":
|
|
346
|
+
table = Table(title=f"GeoIP Location Intel: {res.get('query')}", border_style="cyan")
|
|
347
|
+
table.add_column("Field", style="bold cyan", width=18)
|
|
348
|
+
table.add_column("Details", style="bold white")
|
|
349
|
+
table.add_row("Target IP", f"[bold green]{res.get('query')}[/bold green]")
|
|
350
|
+
table.add_row("Country", f"{res.get('country')} ({res.get('countryCode')})")
|
|
351
|
+
table.add_row("Region / State", f"{res.get('regionName')} ({res.get('region')})")
|
|
352
|
+
table.add_row("City", res.get('city', 'N/A'))
|
|
353
|
+
table.add_row("ZIP / Postal Code", res.get('zip', 'N/A'))
|
|
354
|
+
table.add_row("Coordinates (Lat, Lon)", f"{res.get('lat')}, {res.get('lon')}")
|
|
355
|
+
table.add_row("Timezone", res.get('timezone', 'N/A'))
|
|
356
|
+
table.add_row("ISP", res.get('isp', 'N/A'))
|
|
357
|
+
table.add_row("Organization", res.get('org', 'N/A'))
|
|
358
|
+
table.add_row("Autonomous System (AS)", res.get('as', 'N/A'))
|
|
359
|
+
table.add_row("Google Maps URL", f"https://www.google.com/maps?q={res.get('lat')},{res.get('lon')}")
|
|
360
|
+
console.print("\n", table)
|
|
361
|
+
else:
|
|
362
|
+
console.print(f"\n[red]✘ GeoIP lookup failed: {res.get('message', 'Target not found')}[/red]")
|
|
363
|
+
pause()
|
|
364
|
+
|
|
365
|
+
def mac_lookup():
|
|
366
|
+
"""Looks up MAC vendor and OUI organization."""
|
|
367
|
+
show_banner()
|
|
368
|
+
console.print(Panel("[bold yellow]🏷️ MAC Address Vendor & OUI Lookup[/bold yellow]", border_style="yellow"))
|
|
369
|
+
mac_input = console.input("[bold cyan]Enter MAC Address (e.g. 00:1A:2B:3C:4D:5E or 001A2B): [/bold cyan]").strip()
|
|
370
|
+
if not mac_input:
|
|
371
|
+
console.print("[red]No MAC address provided.[/red]")
|
|
372
|
+
pause()
|
|
373
|
+
return
|
|
374
|
+
|
|
375
|
+
clean_mac = mac_input.replace(":", "").replace("-", "").replace(".", "").upper()
|
|
376
|
+
if len(clean_mac) < 6:
|
|
377
|
+
console.print("[red]Invalid MAC format.[/red]")
|
|
378
|
+
pause()
|
|
379
|
+
return
|
|
380
|
+
|
|
381
|
+
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True) as progress:
|
|
382
|
+
progress.add_task(description="Querying IEEE OUI database...", total=None)
|
|
383
|
+
try:
|
|
384
|
+
url = f"https://api.maclookup.app/v2/macs/{clean_mac}"
|
|
385
|
+
res = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'}, timeout=6).json()
|
|
386
|
+
except Exception as e:
|
|
387
|
+
res = {"found": False, "error": str(e)}
|
|
388
|
+
|
|
389
|
+
if res.get("found"):
|
|
390
|
+
table = Table(title=f"MAC Lookup: {mac_input.upper()}", border_style="cyan")
|
|
391
|
+
table.add_column("Property", style="bold cyan")
|
|
392
|
+
table.add_column("Value", style="bold green")
|
|
393
|
+
table.add_row("MAC Prefix (OUI)", res.get("macPrefix", clean_mac[:6]))
|
|
394
|
+
table.add_row("Vendor / Manufacturer", res.get("company", "Unknown Vendor"))
|
|
395
|
+
table.add_row("Country", res.get("country", "N/A"))
|
|
396
|
+
table.add_row("Address", res.get("address", "N/A"))
|
|
397
|
+
console.print("\n", table)
|
|
398
|
+
else:
|
|
399
|
+
console.print(f"\n[yellow][!] MAC address prefix not found or API limit reached.[/yellow]")
|
|
400
|
+
pause()
|
|
401
|
+
|
|
402
|
+
def subnet_scanner():
|
|
403
|
+
"""Calculates CIDR subnet information and performs multi-threaded alive ping sweep."""
|
|
404
|
+
show_banner()
|
|
405
|
+
console.print(Panel("[bold yellow]🖧 CIDR Subnet Calculator & Ping Sweep[/bold yellow]", border_style="yellow"))
|
|
406
|
+
cidr_input = console.input("[bold cyan]Enter Subnet in CIDR format [default: 192.168.1.0/24]: [/bold cyan]").strip()
|
|
407
|
+
if not cidr_input:
|
|
408
|
+
cidr_input = "192.168.1.0/24"
|
|
409
|
+
try:
|
|
410
|
+
network = ipaddress.ip_network(cidr_input, strict=False)
|
|
411
|
+
except ValueError as e:
|
|
412
|
+
console.print(f"\n[red]✘ Error: Invalid CIDR notation '{cidr_input}': {e}[/red]")
|
|
413
|
+
pause()
|
|
414
|
+
return
|
|
415
|
+
|
|
416
|
+
table = Table(title=f"Subnet Overview: {cidr_input}", border_style="cyan")
|
|
417
|
+
table.add_column("Parameter", style="bold cyan")
|
|
418
|
+
table.add_column("Value", style="bold green")
|
|
419
|
+
hosts = list(network.hosts())
|
|
420
|
+
table.add_row("Network Address", str(network.network_address))
|
|
421
|
+
table.add_row("Broadcast Address", str(network.broadcast_address))
|
|
422
|
+
table.add_row("Subnet Netmask", str(network.netmask))
|
|
423
|
+
table.add_row("Wildcard Mask", str(network.hostmask))
|
|
424
|
+
table.add_row("Total IP Count", str(network.num_addresses))
|
|
425
|
+
table.add_row("Usable Host IPs", str(len(hosts)))
|
|
426
|
+
if hosts:
|
|
427
|
+
table.add_row("Usable Host Range", f"{hosts[0]} - {hosts[-1]}")
|
|
428
|
+
console.print("\n", table)
|
|
429
|
+
|
|
430
|
+
run_sweep = questionary.confirm("Run high-speed multi-threaded host discovery on this subnet?", default=True, style=MENU_STYLE).ask()
|
|
431
|
+
if run_sweep:
|
|
432
|
+
if len(hosts) > 512:
|
|
433
|
+
console.print("[yellow][!] Large subnet detected. Scanning first 256 hosts...[/yellow]")
|
|
434
|
+
target_hosts = hosts[:256]
|
|
435
|
+
else:
|
|
436
|
+
target_hosts = hosts
|
|
437
|
+
|
|
438
|
+
console.print(f"\n[bold cyan][*] Sweeping {len(target_hosts)} host(s)...[/bold cyan]\n")
|
|
439
|
+
alive_hosts = []
|
|
440
|
+
def ping_host(ip):
|
|
441
|
+
ip_str = str(ip)
|
|
442
|
+
probe_ports = [80, 443, 22, 445, 135, 8080, 21, 53]
|
|
443
|
+
for port in probe_ports:
|
|
444
|
+
try:
|
|
445
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
446
|
+
s.settimeout(0.35)
|
|
447
|
+
if s.connect_ex((ip_str, port)) == 0:
|
|
448
|
+
return ip_str, True
|
|
449
|
+
except Exception:
|
|
450
|
+
pass
|
|
451
|
+
return ip_str, False
|
|
452
|
+
|
|
453
|
+
start_time = time.time()
|
|
454
|
+
with ThreadPoolExecutor(max_workers=60) as executor:
|
|
455
|
+
futures = {executor.submit(ping_host, ip): ip for ip in target_hosts}
|
|
456
|
+
for future in as_completed(futures):
|
|
457
|
+
ip_str, is_alive = future.result()
|
|
458
|
+
if is_alive:
|
|
459
|
+
console.print(f"[bold green]✔ HOST ALIVE:[/bold green] [bold white]{ip_str}[/bold white]")
|
|
460
|
+
alive_hosts.append(ip_str)
|
|
461
|
+
elapsed = time.time() - start_time
|
|
462
|
+
console.print(f"\n[bold cyan][*] Sweep finished in {elapsed:.2f}s. Discovered {len(alive_hosts)} responsive host(s).[/bold cyan]")
|
|
463
|
+
pause()
|
|
464
|
+
|
|
465
|
+
# =============================================================================
|
|
466
|
+
# 3. SCANNING & FUZZING TOOLS
|
|
467
|
+
# =============================================================================
|
|
468
|
+
def port_scanner():
|
|
469
|
+
"""Multi-threaded high speed TCP port scanner."""
|
|
470
|
+
show_banner()
|
|
471
|
+
console.print(Panel("[bold yellow]⚡ High-Speed Multi-Threaded Port Scanner[/bold yellow]", border_style="yellow"))
|
|
472
|
+
target = console.input("[bold cyan]Enter target IP or Hostname [default: 127.0.0.1]: [/bold cyan]").strip()
|
|
473
|
+
if not target: target = "127.0.0.1"
|
|
474
|
+
clean_target = target.replace("https://", "").replace("http://", "").split("/")[0].split(":")[0]
|
|
475
|
+
try:
|
|
476
|
+
target_ip = socket.gethostbyname(clean_target)
|
|
477
|
+
except socket.gaierror:
|
|
478
|
+
console.print(f"[red]✘ Error: Could not resolve hostname '{clean_target}'.[/red]")
|
|
479
|
+
pause()
|
|
480
|
+
return
|
|
481
|
+
|
|
482
|
+
port_preset = questionary.select(
|
|
483
|
+
"Select Port Range Option:",
|
|
484
|
+
choices=[
|
|
485
|
+
"1. Common Top Ports",
|
|
486
|
+
"2. Fast Scan Top 100 Ports",
|
|
487
|
+
"3. Standard Scan (1 - 1024)",
|
|
488
|
+
"4. Custom Ports or Range"
|
|
489
|
+
], style=MENU_STYLE
|
|
490
|
+
).ask()
|
|
491
|
+
|
|
492
|
+
if "1. Common" in port_preset:
|
|
493
|
+
ports = [21, 22, 23, 25, 53, 80, 110, 135, 139, 443, 445, 1433, 3306, 3389, 5432, 8000, 8080, 8443]
|
|
494
|
+
elif "2. Fast" in port_preset:
|
|
495
|
+
ports = list(range(1, 101))
|
|
496
|
+
elif "3. Standard" in port_preset:
|
|
497
|
+
ports = list(range(1, 1025))
|
|
498
|
+
else:
|
|
499
|
+
custom_input = console.input("[bold cyan]Enter ports (e.g. 21,22,80 or 1-500): [/bold cyan]").strip()
|
|
500
|
+
if "-" in custom_input:
|
|
501
|
+
try:
|
|
502
|
+
start_p, end_p = map(int, custom_input.split("-"))
|
|
503
|
+
ports = list(range(max(1, start_p), min(65535, end_p) + 1))
|
|
504
|
+
except ValueError:
|
|
505
|
+
ports = [21, 22, 80, 443, 3306, 8080]
|
|
506
|
+
else:
|
|
507
|
+
try:
|
|
508
|
+
ports = [int(p.strip()) for p in custom_input.split(",") if p.strip()]
|
|
509
|
+
except ValueError:
|
|
510
|
+
ports = [21, 22, 80, 443, 3306, 8080]
|
|
511
|
+
|
|
512
|
+
console.print(f"\n[bold cyan][*] Launching scan on {clean_target} ({target_ip}) across {len(ports)} port(s)...[/bold cyan]\n")
|
|
513
|
+
|
|
514
|
+
def scan_port(ip, port):
|
|
515
|
+
try:
|
|
516
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
517
|
+
s.settimeout(0.8)
|
|
518
|
+
if s.connect_ex((ip, port)) == 0:
|
|
519
|
+
try: service = socket.getservbyport(port, "tcp")
|
|
520
|
+
except Exception: service = "unknown"
|
|
521
|
+
return port, True, service
|
|
522
|
+
except Exception: pass
|
|
523
|
+
return port, False, None
|
|
524
|
+
|
|
525
|
+
open_ports = []
|
|
526
|
+
start_time = time.time()
|
|
527
|
+
max_threads = min(120, len(ports)) if ports else 1
|
|
528
|
+
with ThreadPoolExecutor(max_workers=max_threads) as executor:
|
|
529
|
+
futures = {executor.submit(scan_port, target_ip, p): p for p in ports}
|
|
530
|
+
for future in as_completed(futures):
|
|
531
|
+
port, is_open, service = future.result()
|
|
532
|
+
if is_open:
|
|
533
|
+
console.print(f"[bold green]✔ OPEN[/bold green] - Port [cyan]{port:<5}/tcp[/cyan] ({service})")
|
|
534
|
+
open_ports.append((port, service))
|
|
535
|
+
open_ports.sort(key=lambda x: x[0])
|
|
536
|
+
elapsed = time.time() - start_time
|
|
537
|
+
|
|
538
|
+
table = Table(title=f"Scan Summary for {clean_target}", border_style="cyan")
|
|
539
|
+
table.add_column("Port", style="bold cyan", width=10)
|
|
540
|
+
table.add_column("Status", style="bold green", width=12)
|
|
541
|
+
table.add_column("Service Name", style="bold white")
|
|
542
|
+
for port, service in open_ports:
|
|
543
|
+
table.add_row(f"{port}/tcp", "OPEN", service)
|
|
544
|
+
console.print("\n", table)
|
|
545
|
+
console.print(f"[bold cyan][*] Scan finished in {elapsed:.2f}s.[/bold cyan]")
|
|
546
|
+
pause()
|
|
547
|
+
|
|
548
|
+
def subdomain_finder():
|
|
549
|
+
"""Multi-threaded Subdomain & DNS Enumeration."""
|
|
550
|
+
show_banner()
|
|
551
|
+
console.print(Panel("[bold yellow]🕵️ Subdomain Finder & DNS Recon[/bold yellow]", border_style="yellow"))
|
|
552
|
+
domain = console.input("[bold cyan]Enter target domain (e.g. google.com): [/bold cyan]").strip()
|
|
553
|
+
if not domain:
|
|
554
|
+
console.print("[red]No domain specified.[/red]")
|
|
555
|
+
pause()
|
|
556
|
+
return
|
|
557
|
+
domain = domain.replace("https://", "").replace("http://", "").split("/")[0].split(":")[0]
|
|
558
|
+
|
|
559
|
+
default_subdomains = ["www", "mail", "remote", "blog", "webmail", "server", "ns1", "ns2", "smtp", "secure", "vpn", "api", "dev", "staging", "test", "portal", "admin", "panel", "app", "cdn", "cloud", "shop", "m", "support", "beta", "git", "db", "dashboard", "status", "auth", "login", "v1", "backend", "internal", "corp", "assets", "monitor", "ftp", "ssh", "direct", "gateway", "proxy", "jenkins", "gitlab", "jira", "s3"]
|
|
560
|
+
custom_input = console.input("[bold cyan]Enter custom subdomains separated by comma (leave blank for default): [/bold cyan]").strip()
|
|
561
|
+
subdomains = [s.strip() for s in custom_input.split(",") if s.strip()] if custom_input else default_subdomains
|
|
562
|
+
|
|
563
|
+
console.print(f"\n[bold cyan][*] Enumerating {len(subdomains)} subdomains for {domain}...[/bold cyan]\n")
|
|
564
|
+
def check_sub(sub):
|
|
565
|
+
target_host = f"{sub}.{domain}"
|
|
566
|
+
try:
|
|
567
|
+
ip = socket.gethostbyname(target_host)
|
|
568
|
+
return target_host, ip
|
|
569
|
+
except socket.gaierror:
|
|
570
|
+
return target_host, None
|
|
571
|
+
|
|
572
|
+
found_subs = []
|
|
573
|
+
start_time = time.time()
|
|
574
|
+
with ThreadPoolExecutor(max_workers=50) as executor:
|
|
575
|
+
futures = {executor.submit(check_sub, sub): sub for sub in subdomains}
|
|
576
|
+
for future in as_completed(futures):
|
|
577
|
+
target_host, ip = future.result()
|
|
578
|
+
if ip:
|
|
579
|
+
console.print(f"[bold green]✔ FOUND:[/bold green] [bold cyan]{target_host:<32}[/bold cyan] -> [white]{ip}[/white]")
|
|
580
|
+
found_subs.append((target_host, ip))
|
|
581
|
+
elapsed = time.time() - start_time
|
|
582
|
+
console.print(f"\n[bold cyan][*] DNS Recon complete in {elapsed:.2f}s.[/bold cyan]")
|
|
583
|
+
pause()
|
|
584
|
+
|
|
585
|
+
def dir_fuzzer():
|
|
586
|
+
"""Multi-threaded Web Directory & Endpoint Fuzzer."""
|
|
587
|
+
show_banner()
|
|
588
|
+
console.print(Panel("[bold yellow]⚡ Web Directory & Path Fuzzer[/bold yellow]", border_style="yellow"))
|
|
589
|
+
target = console.input("[bold cyan]Enter target URL/Host: [/bold cyan]").strip()
|
|
590
|
+
if not target:
|
|
591
|
+
console.print("[red]No target specified.[/red]")
|
|
592
|
+
pause()
|
|
593
|
+
return
|
|
594
|
+
base_url = target if target.startswith(("http://", "https://")) else "http://" + target
|
|
595
|
+
base_url = base_url.rstrip("/")
|
|
596
|
+
|
|
597
|
+
default_paths = ["/admin", "/login", "/dashboard", "/.env", "/config.php", "/robots.txt", "/api", "/api/v1", "/backup", "/uploads", "/.git", "/swagger.json", "/health", "/console", "/actuator", "/graphql"]
|
|
598
|
+
custom_paths = console.input("[bold cyan]Enter paths separated by comma (leave blank for built-in): [/bold cyan]").strip()
|
|
599
|
+
wordlist = [p.strip() if p.strip().startswith("/") else "/" + p.strip() for p in custom_paths.split(",") if p.strip()] if custom_paths else default_paths
|
|
600
|
+
|
|
601
|
+
console.print(f"\n[bold cyan][*] Fuzzing {len(wordlist)} paths against {base_url}...[/bold cyan]\n")
|
|
602
|
+
def scan_path(path):
|
|
603
|
+
url = base_url + path
|
|
604
|
+
try:
|
|
605
|
+
r = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'}, timeout=4, allow_redirects=False)
|
|
606
|
+
return path, r.status_code, len(r.content)
|
|
607
|
+
except Exception:
|
|
608
|
+
return path, None, 0
|
|
609
|
+
|
|
610
|
+
found_paths = []
|
|
611
|
+
start_time = time.time()
|
|
612
|
+
with ThreadPoolExecutor(max_workers=30) as executor:
|
|
613
|
+
futures = {executor.submit(scan_path, path): path for path in wordlist}
|
|
614
|
+
for future in as_completed(futures):
|
|
615
|
+
path, status, length = future.result()
|
|
616
|
+
if status and status != 404:
|
|
617
|
+
status_style = "bold green" if status == 200 else "bold cyan" if status in (301, 302) else "bold yellow" if status in (401, 403) else "dim white"
|
|
618
|
+
console.print(f"[{status_style}]✔ {status:<15}[/{status_style}] | Path: [cyan]{base_url + path:<45}[/cyan]")
|
|
619
|
+
found_paths.append((path, status, length))
|
|
620
|
+
elapsed = time.time() - start_time
|
|
621
|
+
console.print(f"\n[bold cyan][*] Fuzzing finished in {elapsed:.2f}s.[/bold cyan]")
|
|
622
|
+
pause()
|
|
623
|
+
|
|
624
|
+
def header_inspector():
|
|
625
|
+
"""Inspects HTTP response headers and conducts automated security headers audit."""
|
|
626
|
+
show_banner()
|
|
627
|
+
console.print(Panel("[bold yellow]🔍 HTTP Header Inspector & Security Auditor[/bold yellow]", border_style="yellow"))
|
|
628
|
+
url = console.input("[bold cyan]Enter URL: [/bold cyan]").strip()
|
|
629
|
+
if not url:
|
|
630
|
+
console.print("[red]No URL provided.[/red]")
|
|
631
|
+
pause()
|
|
632
|
+
return
|
|
633
|
+
if not url.startswith(("http://", "https://")): url = "https://" + url
|
|
634
|
+
|
|
635
|
+
try:
|
|
636
|
+
response = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'}, timeout=6, allow_redirects=True)
|
|
637
|
+
headers_table = Table(title="HTTP Response Headers", border_style="cyan")
|
|
638
|
+
headers_table.add_column("Header", style="bold cyan")
|
|
639
|
+
headers_table.add_column("Value", style="white")
|
|
640
|
+
for key, val in response.headers.items():
|
|
641
|
+
headers_table.add_row(key, val)
|
|
642
|
+
console.print(headers_table)
|
|
643
|
+
|
|
644
|
+
security_headers = {
|
|
645
|
+
"Strict-Transport-Security": "Protects against MITM",
|
|
646
|
+
"Content-Security-Policy": "Mitigates XSS",
|
|
647
|
+
"X-Frame-Options": "Prevents Clickjacking",
|
|
648
|
+
"X-Content-Type-Options": "Stops MIME-sniffing"
|
|
649
|
+
}
|
|
650
|
+
audit_table = Table(title="Security Headers Audit", border_style="yellow")
|
|
651
|
+
audit_table.add_column("Security Header", style="bold cyan", width=28)
|
|
652
|
+
audit_table.add_column("Status", width=12)
|
|
653
|
+
for sh, desc in security_headers.items():
|
|
654
|
+
status = "[bold green]✔ PRESENT[/bold green]" if sh.lower() in [h.lower() for h in response.headers] else "[bold red]✘ MISSING[/bold red]"
|
|
655
|
+
audit_table.add_row(sh, status)
|
|
656
|
+
console.print("\n", audit_table)
|
|
657
|
+
except Exception as e:
|
|
658
|
+
console.print(f"[bold red]✘ Connection error: {e}[/bold red]")
|
|
659
|
+
pause()
|
|
660
|
+
|
|
661
|
+
# =============================================================================
|
|
662
|
+
# 4. EXTRA NETWORK DIAGNOSTIC TOOLS
|
|
663
|
+
# =============================================================================
|
|
664
|
+
def dns_record_lookup():
|
|
665
|
+
"""Looks up common DNS records using the system resolver tools."""
|
|
666
|
+
show_banner()
|
|
667
|
+
console.print(Panel("[bold yellow]DNS Record Lookup[/bold yellow]", border_style="yellow"))
|
|
668
|
+
domain = clean_host(console.input("[bold cyan]Enter domain (e.g. example.com): [/bold cyan]"))
|
|
669
|
+
if not domain:
|
|
670
|
+
console.print("[red]No domain provided.[/red]")
|
|
671
|
+
pause()
|
|
672
|
+
return
|
|
673
|
+
|
|
674
|
+
record_choices = ["A", "AAAA", "MX", "NS", "TXT", "SOA", "CNAME"]
|
|
675
|
+
selected = questionary.checkbox(
|
|
676
|
+
"Select DNS record types:",
|
|
677
|
+
choices=record_choices,
|
|
678
|
+
default=["A", "AAAA", "MX", "NS", "TXT"],
|
|
679
|
+
style=MENU_STYLE
|
|
680
|
+
).ask()
|
|
681
|
+
if not selected:
|
|
682
|
+
selected = ["A"]
|
|
683
|
+
|
|
684
|
+
table = Table(title=f"DNS Records: {domain}", border_style="cyan")
|
|
685
|
+
table.add_column("Type", style="bold cyan", width=8)
|
|
686
|
+
table.add_column("Result", style="white")
|
|
687
|
+
|
|
688
|
+
for record_type in selected:
|
|
689
|
+
if os.name == "nt":
|
|
690
|
+
output, _ = run_command(["nslookup", f"-type={record_type}", domain])
|
|
691
|
+
else:
|
|
692
|
+
output, _ = run_command(["dig", "+short", domain, record_type])
|
|
693
|
+
if not output:
|
|
694
|
+
output, _ = run_command(["nslookup", f"-type={record_type}", domain])
|
|
695
|
+
table.add_row(record_type, output or "No records found")
|
|
696
|
+
|
|
697
|
+
console.print("\n", table)
|
|
698
|
+
pause()
|
|
699
|
+
|
|
700
|
+
def http_status_checker():
|
|
701
|
+
"""Checks HTTP status, redirects, server headers, and response timing."""
|
|
702
|
+
show_banner()
|
|
703
|
+
console.print(Panel("[bold yellow]HTTP Status & Redirect Checker[/bold yellow]", border_style="yellow"))
|
|
704
|
+
raw_url = console.input("[bold cyan]Enter URL or host: [/bold cyan]").strip()
|
|
705
|
+
url = ensure_url(raw_url)
|
|
706
|
+
if not url:
|
|
707
|
+
console.print("[red]No URL provided.[/red]")
|
|
708
|
+
pause()
|
|
709
|
+
return
|
|
710
|
+
|
|
711
|
+
try:
|
|
712
|
+
start_time = time.perf_counter()
|
|
713
|
+
response = requests.get(url, headers={"User-Agent": "Mozilla/5.0"}, timeout=10, allow_redirects=True)
|
|
714
|
+
elapsed_ms = (time.perf_counter() - start_time) * 1000
|
|
715
|
+
|
|
716
|
+
table = Table(title=f"HTTP Check: {url}", border_style="cyan")
|
|
717
|
+
table.add_column("Property", style="bold cyan")
|
|
718
|
+
table.add_column("Value", style="bold white")
|
|
719
|
+
table.add_row("Final URL", response.url)
|
|
720
|
+
table.add_row("Status Code", str(response.status_code))
|
|
721
|
+
table.add_row("Reason", response.reason)
|
|
722
|
+
table.add_row("Response Time", f"{elapsed_ms:.1f} ms")
|
|
723
|
+
table.add_row("Content Length", response.headers.get("Content-Length", str(len(response.content))))
|
|
724
|
+
table.add_row("Content Type", response.headers.get("Content-Type", "N/A"))
|
|
725
|
+
table.add_row("Server", response.headers.get("Server", "N/A"))
|
|
726
|
+
console.print("\n", table)
|
|
727
|
+
|
|
728
|
+
if response.history:
|
|
729
|
+
redirects = Table(title="Redirect Chain", border_style="yellow")
|
|
730
|
+
redirects.add_column("#", style="bold cyan", width=4)
|
|
731
|
+
redirects.add_column("Status", style="bold yellow", width=8)
|
|
732
|
+
redirects.add_column("URL", style="white")
|
|
733
|
+
for idx, item in enumerate(response.history, start=1):
|
|
734
|
+
redirects.add_row(str(idx), str(item.status_code), item.url)
|
|
735
|
+
redirects.add_row(str(len(response.history) + 1), str(response.status_code), response.url)
|
|
736
|
+
console.print("\n", redirects)
|
|
737
|
+
except requests.RequestException as e:
|
|
738
|
+
console.print(f"[bold red]Connection error: {e}[/bold red]")
|
|
739
|
+
pause()
|
|
740
|
+
|
|
741
|
+
def tech_stack_detector():
|
|
742
|
+
"""Detects likely web technologies, CDN/WAF hints, cookies, and metadata."""
|
|
743
|
+
show_banner()
|
|
744
|
+
console.print(Panel("[bold yellow]HTTP Tech Stack Detector[/bold yellow]", border_style="yellow"))
|
|
745
|
+
raw_url = console.input("[bold cyan]Enter URL or host: [/bold cyan]").strip()
|
|
746
|
+
url = ensure_url(raw_url)
|
|
747
|
+
if not url:
|
|
748
|
+
console.print("[red]No URL provided.[/red]")
|
|
749
|
+
pause()
|
|
750
|
+
return
|
|
751
|
+
|
|
752
|
+
try:
|
|
753
|
+
response = requests.get(url, headers={"User-Agent": "Mozilla/5.0"}, timeout=12, allow_redirects=True)
|
|
754
|
+
except requests.RequestException as e:
|
|
755
|
+
console.print(f"[bold red]Connection error: {e}[/bold red]")
|
|
756
|
+
pause()
|
|
757
|
+
return
|
|
758
|
+
|
|
759
|
+
headers = response.headers
|
|
760
|
+
header_text = "\n".join(f"{k}: {v}" for k, v in headers.items()).lower()
|
|
761
|
+
body = response.text[:250000]
|
|
762
|
+
body_lower = body.lower()
|
|
763
|
+
detections = []
|
|
764
|
+
|
|
765
|
+
header_hints = {
|
|
766
|
+
"Server": headers.get("Server"),
|
|
767
|
+
"X-Powered-By": headers.get("X-Powered-By"),
|
|
768
|
+
"Via": headers.get("Via"),
|
|
769
|
+
"X-Generator": headers.get("X-Generator"),
|
|
770
|
+
"X-AspNet-Version": headers.get("X-AspNet-Version"),
|
|
771
|
+
}
|
|
772
|
+
for name, value in header_hints.items():
|
|
773
|
+
if value:
|
|
774
|
+
detections.append((name, value, "Header"))
|
|
775
|
+
|
|
776
|
+
cdn_patterns = [
|
|
777
|
+
("Cloudflare", ["cf-ray", "cf-cache-status", "__cf_bm", "cloudflare"]),
|
|
778
|
+
("Akamai", ["akamai", "akamaighost", "x-akamai"]),
|
|
779
|
+
("Fastly", ["fastly", "x-served-by", "x-cache-hits"]),
|
|
780
|
+
("Amazon CloudFront", ["cloudfront", "x-amz-cf-id", "x-amz-cf-pop"]),
|
|
781
|
+
("Azure Front Door", ["azurefd", "x-azure-ref"]),
|
|
782
|
+
("Vercel", ["vercel", "x-vercel-id"]),
|
|
783
|
+
("Netlify", ["netlify", "x-nf-request-id"]),
|
|
784
|
+
]
|
|
785
|
+
for tech, needles in cdn_patterns:
|
|
786
|
+
if any(needle in header_text for needle in needles):
|
|
787
|
+
detections.append((tech, "CDN/WAF/proxy hint", "Header"))
|
|
788
|
+
|
|
789
|
+
body_patterns = [
|
|
790
|
+
("WordPress", ["wp-content", "wp-includes", "wp-json"]),
|
|
791
|
+
("Drupal", ["drupal-settings-json", "/sites/default/"]),
|
|
792
|
+
("Joomla", ["content=\"joomla", "/media/system/js/"]),
|
|
793
|
+
("React", ["reactroot", "__react", "react-dom"]),
|
|
794
|
+
("Next.js", ["__next_data__", "/_next/static/"]),
|
|
795
|
+
("Vue.js", ["__vue__", "vue.js", "data-v-"]),
|
|
796
|
+
("Angular", ["ng-version", "ng-app", "angular.js"]),
|
|
797
|
+
("Svelte", ["svelte-"]),
|
|
798
|
+
("jQuery", ["jquery"]),
|
|
799
|
+
("Bootstrap", ["bootstrap.min.css", "bootstrap.min.js"]),
|
|
800
|
+
("Tailwind CSS", ["tailwind"]),
|
|
801
|
+
("Shopify", ["cdn.shopify.com", "shopify-features"]),
|
|
802
|
+
("Laravel", ["laravel_session"]),
|
|
803
|
+
("Django", ["csrftoken", "django"]),
|
|
804
|
+
]
|
|
805
|
+
for tech, needles in body_patterns:
|
|
806
|
+
if any(needle in body_lower for needle in needles):
|
|
807
|
+
detections.append((tech, "Matched page/source marker", "Body/Cookie"))
|
|
808
|
+
|
|
809
|
+
generator_match = re.search(
|
|
810
|
+
r'<meta[^>]+name=["\']generator["\'][^>]+content=["\']([^"\']+)["\']',
|
|
811
|
+
body,
|
|
812
|
+
re.IGNORECASE,
|
|
813
|
+
)
|
|
814
|
+
if generator_match:
|
|
815
|
+
detections.append(("Meta generator", generator_match.group(1), "HTML"))
|
|
816
|
+
|
|
817
|
+
script_hosts = sorted(set(re.findall(r'<script[^>]+src=["\']https?://([^/"\']+)', body, re.IGNORECASE)))
|
|
818
|
+
css_hosts = sorted(set(re.findall(r'<link[^>]+href=["\']https?://([^/"\']+)', body, re.IGNORECASE)))
|
|
819
|
+
|
|
820
|
+
summary = Table(title=f"Tech Stack Hints: {response.url}", border_style="cyan")
|
|
821
|
+
summary.add_column("Technology / Signal", style="bold cyan")
|
|
822
|
+
summary.add_column("Evidence", style="white")
|
|
823
|
+
summary.add_column("Source", style="bold yellow", width=14)
|
|
824
|
+
seen = set()
|
|
825
|
+
for name, evidence, source in detections:
|
|
826
|
+
key = (name, evidence, source)
|
|
827
|
+
if key not in seen:
|
|
828
|
+
summary.add_row(name, str(evidence), source)
|
|
829
|
+
seen.add(key)
|
|
830
|
+
if not seen:
|
|
831
|
+
summary.add_row("No strong fingerprint", "Review raw headers/source manually", "N/A")
|
|
832
|
+
console.print("\n", summary)
|
|
833
|
+
|
|
834
|
+
assets = Table(title="External Asset Hosts", border_style="yellow")
|
|
835
|
+
assets.add_column("Type", style="bold cyan", width=10)
|
|
836
|
+
assets.add_column("Hosts", style="white")
|
|
837
|
+
assets.add_row("Scripts", "\n".join(script_hosts[:20]) if script_hosts else "None found")
|
|
838
|
+
assets.add_row("Styles", "\n".join(css_hosts[:20]) if css_hosts else "None found")
|
|
839
|
+
console.print("\n", assets)
|
|
840
|
+
|
|
841
|
+
cookie_table = Table(title="Cookie Security Flags", border_style="magenta")
|
|
842
|
+
cookie_table.add_column("Cookie", style="bold cyan")
|
|
843
|
+
cookie_table.add_column("Secure", style="bold green")
|
|
844
|
+
cookie_table.add_column("HttpOnly", style="bold green")
|
|
845
|
+
cookie_table.add_column("SameSite", style="bold green")
|
|
846
|
+
raw_cookies = headers.get("Set-Cookie", "")
|
|
847
|
+
if raw_cookies:
|
|
848
|
+
for cookie_part in re.split(r", (?=[^;,]+=)", raw_cookies)[:20]:
|
|
849
|
+
name = cookie_part.split("=", 1)[0]
|
|
850
|
+
lower_cookie = cookie_part.lower()
|
|
851
|
+
same_site = re.search(r"samesite=([^;]+)", cookie_part, re.IGNORECASE)
|
|
852
|
+
cookie_table.add_row(
|
|
853
|
+
name,
|
|
854
|
+
"yes" if "secure" in lower_cookie else "no",
|
|
855
|
+
"yes" if "httponly" in lower_cookie else "no",
|
|
856
|
+
same_site.group(1) if same_site else "missing",
|
|
857
|
+
)
|
|
858
|
+
else:
|
|
859
|
+
cookie_table.add_row("None", "N/A", "N/A", "N/A")
|
|
860
|
+
console.print("\n", cookie_table)
|
|
861
|
+
pause()
|
|
862
|
+
|
|
863
|
+
def web_endpoint_auditor():
|
|
864
|
+
"""Audits well-known web endpoints, methods, CORS, and cookie flags."""
|
|
865
|
+
show_banner()
|
|
866
|
+
console.print(Panel("[bold yellow]Web Endpoint Auditor[/bold yellow]", border_style="yellow"))
|
|
867
|
+
raw_url = console.input("[bold cyan]Enter URL or host: [/bold cyan]").strip()
|
|
868
|
+
base_url = ensure_url(raw_url).rstrip("/")
|
|
869
|
+
if not base_url:
|
|
870
|
+
console.print("[red]No URL provided.[/red]")
|
|
871
|
+
pause()
|
|
872
|
+
return
|
|
873
|
+
|
|
874
|
+
session = requests.Session()
|
|
875
|
+
session.headers.update({"User-Agent": "Mozilla/5.0"})
|
|
876
|
+
paths = [
|
|
877
|
+
("robots.txt", "/robots.txt", "Public crawl rules"),
|
|
878
|
+
("sitemap.xml", "/sitemap.xml", "Public URL index"),
|
|
879
|
+
("security.txt", "/.well-known/security.txt", "Security contact policy"),
|
|
880
|
+
("change-password", "/.well-known/change-password", "Password-change discovery"),
|
|
881
|
+
("humans.txt", "/humans.txt", "Site/team metadata"),
|
|
882
|
+
("favicon.ico", "/favicon.ico", "Site fingerprint asset"),
|
|
883
|
+
(".env", "/.env", "Sensitive config exposure"),
|
|
884
|
+
(".git HEAD", "/.git/HEAD", "Sensitive repository exposure"),
|
|
885
|
+
("server-status", "/server-status", "Sensitive Apache status exposure"),
|
|
886
|
+
]
|
|
887
|
+
|
|
888
|
+
results = Table(title=f"Endpoint Audit: {base_url}", border_style="cyan")
|
|
889
|
+
results.add_column("Endpoint", style="bold cyan")
|
|
890
|
+
results.add_column("Status", style="bold yellow", width=10)
|
|
891
|
+
results.add_column("Bytes", style="white", width=10)
|
|
892
|
+
results.add_column("Notes", style="white")
|
|
893
|
+
|
|
894
|
+
for label, path, note in paths:
|
|
895
|
+
try:
|
|
896
|
+
response = session.get(base_url + path, timeout=7, allow_redirects=False)
|
|
897
|
+
status = response.status_code
|
|
898
|
+
length = len(response.content)
|
|
899
|
+
warning = note
|
|
900
|
+
if path in ("/.env", "/.git/HEAD", "/server-status") and status == 200:
|
|
901
|
+
warning = f"[bold red]POSSIBLE EXPOSURE[/bold red] - {note}"
|
|
902
|
+
elif status in (401, 403):
|
|
903
|
+
warning = f"Protected - {note}"
|
|
904
|
+
elif status in (301, 302, 307, 308):
|
|
905
|
+
warning = f"Redirects to {response.headers.get('Location', 'unknown')}"
|
|
906
|
+
results.add_row(label, str(status), str(length), warning)
|
|
907
|
+
except requests.RequestException as e:
|
|
908
|
+
results.add_row(label, "ERR", "0", str(e))
|
|
909
|
+
console.print("\n", results)
|
|
910
|
+
|
|
911
|
+
try:
|
|
912
|
+
options_response = session.options(
|
|
913
|
+
base_url,
|
|
914
|
+
timeout=7,
|
|
915
|
+
headers={"Origin": "https://example.com", "Access-Control-Request-Method": "GET"},
|
|
916
|
+
allow_redirects=False,
|
|
917
|
+
)
|
|
918
|
+
methods = options_response.headers.get("Allow", options_response.headers.get("Access-Control-Allow-Methods", "Not advertised"))
|
|
919
|
+
cors_origin = options_response.headers.get("Access-Control-Allow-Origin", "Not advertised")
|
|
920
|
+
cors_creds = options_response.headers.get("Access-Control-Allow-Credentials", "Not advertised")
|
|
921
|
+
|
|
922
|
+
policy = Table(title="HTTP Methods & CORS", border_style="yellow")
|
|
923
|
+
policy.add_column("Check", style="bold cyan")
|
|
924
|
+
policy.add_column("Result", style="white")
|
|
925
|
+
policy.add_row("OPTIONS Status", str(options_response.status_code))
|
|
926
|
+
policy.add_row("Allowed Methods", methods)
|
|
927
|
+
policy.add_row("CORS Origin", cors_origin)
|
|
928
|
+
policy.add_row("CORS Credentials", cors_creds)
|
|
929
|
+
if cors_origin == "*" and cors_creds.lower() == "true":
|
|
930
|
+
policy.add_row("CORS Risk", "[bold red]Wildcard origin with credentials advertised[/bold red]")
|
|
931
|
+
console.print("\n", policy)
|
|
932
|
+
except requests.RequestException as e:
|
|
933
|
+
console.print(f"[yellow][!] OPTIONS/CORS check failed: {e}[/yellow]")
|
|
934
|
+
|
|
935
|
+
try:
|
|
936
|
+
home = session.get(base_url, timeout=7, allow_redirects=True)
|
|
937
|
+
cookie_table = Table(title="Homepage Cookie Flags", border_style="magenta")
|
|
938
|
+
cookie_table.add_column("Cookie", style="bold cyan")
|
|
939
|
+
cookie_table.add_column("Secure", style="bold green")
|
|
940
|
+
cookie_table.add_column("HttpOnly", style="bold green")
|
|
941
|
+
cookie_table.add_column("SameSite", style="bold green")
|
|
942
|
+
raw_cookies = home.headers.get("Set-Cookie", "")
|
|
943
|
+
if raw_cookies:
|
|
944
|
+
for cookie_part in re.split(r", (?=[^;,]+=)", raw_cookies)[:20]:
|
|
945
|
+
name = cookie_part.split("=", 1)[0]
|
|
946
|
+
lower_cookie = cookie_part.lower()
|
|
947
|
+
same_site = re.search(r"samesite=([^;]+)", cookie_part, re.IGNORECASE)
|
|
948
|
+
cookie_table.add_row(
|
|
949
|
+
name,
|
|
950
|
+
"yes" if "secure" in lower_cookie else "no",
|
|
951
|
+
"yes" if "httponly" in lower_cookie else "no",
|
|
952
|
+
same_site.group(1) if same_site else "missing",
|
|
953
|
+
)
|
|
954
|
+
else:
|
|
955
|
+
cookie_table.add_row("None", "N/A", "N/A", "N/A")
|
|
956
|
+
console.print("\n", cookie_table)
|
|
957
|
+
except requests.RequestException as e:
|
|
958
|
+
console.print(f"[yellow][!] Cookie check failed: {e}[/yellow]")
|
|
959
|
+
pause()
|
|
960
|
+
|
|
961
|
+
def tcp_ping_tool():
|
|
962
|
+
"""Measures TCP connection latency to a host and port."""
|
|
963
|
+
show_banner()
|
|
964
|
+
console.print(Panel("[bold yellow]TCP Ping / Latency Tester[/bold yellow]", border_style="yellow"))
|
|
965
|
+
host = clean_host(console.input("[bold cyan]Enter host or IP [default: 127.0.0.1]: [/bold cyan]")) or "127.0.0.1"
|
|
966
|
+
try:
|
|
967
|
+
port = int(console.input("[bold cyan]Enter TCP port [default: 443]: [/bold cyan]").strip() or 443)
|
|
968
|
+
attempts = int(console.input("[bold cyan]Enter attempts [default: 5]: [/bold cyan]").strip() or 5)
|
|
969
|
+
except ValueError:
|
|
970
|
+
console.print("[yellow][!] Invalid number entered. Using port 443 and 5 attempts.[/yellow]")
|
|
971
|
+
port, attempts = 443, 5
|
|
972
|
+
|
|
973
|
+
attempts = max(1, min(attempts, 25))
|
|
974
|
+
results = []
|
|
975
|
+
console.print(f"\n[bold cyan][*] Testing TCP connectivity to {host}:{port}...[/bold cyan]\n")
|
|
976
|
+
|
|
977
|
+
for attempt in range(1, attempts + 1):
|
|
978
|
+
start_time = time.perf_counter()
|
|
979
|
+
try:
|
|
980
|
+
with socket.create_connection((host, port), timeout=3):
|
|
981
|
+
elapsed_ms = (time.perf_counter() - start_time) * 1000
|
|
982
|
+
results.append(elapsed_ms)
|
|
983
|
+
console.print(f"[bold green]OPEN[/bold green] attempt {attempt}: {elapsed_ms:.1f} ms")
|
|
984
|
+
except Exception as e:
|
|
985
|
+
console.print(f"[bold red]FAILED[/bold red] attempt {attempt}: {e}")
|
|
986
|
+
time.sleep(0.2)
|
|
987
|
+
|
|
988
|
+
if results:
|
|
989
|
+
table = Table(title=f"Latency Summary: {host}:{port}", border_style="cyan")
|
|
990
|
+
table.add_column("Metric", style="bold cyan")
|
|
991
|
+
table.add_column("Value", style="bold white")
|
|
992
|
+
table.add_row("Successful Attempts", f"{len(results)}/{attempts}")
|
|
993
|
+
table.add_row("Minimum", f"{min(results):.1f} ms")
|
|
994
|
+
table.add_row("Average", f"{sum(results) / len(results):.1f} ms")
|
|
995
|
+
table.add_row("Maximum", f"{max(results):.1f} ms")
|
|
996
|
+
console.print("\n", table)
|
|
997
|
+
pause()
|
|
998
|
+
|
|
999
|
+
def listening_ports():
|
|
1000
|
+
"""Displays local listening TCP/UDP ports using OS networking tools."""
|
|
1001
|
+
show_banner()
|
|
1002
|
+
console.print(Panel("[bold yellow]Local Listening Ports Viewer[/bold yellow]", border_style="yellow"))
|
|
1003
|
+
if os.name == "nt":
|
|
1004
|
+
cmd = ["netstat", "-ano"]
|
|
1005
|
+
filter_text = "LISTENING"
|
|
1006
|
+
else:
|
|
1007
|
+
cmd = ["ss", "-tulpen"]
|
|
1008
|
+
filter_text = "LISTEN"
|
|
1009
|
+
|
|
1010
|
+
output, code = run_command(cmd, timeout=15)
|
|
1011
|
+
if code == 127 and os.name != "nt":
|
|
1012
|
+
output, code = run_command(["netstat", "-tulpen"], timeout=15)
|
|
1013
|
+
|
|
1014
|
+
lines = [line for line in output.splitlines() if filter_text.lower() in line.lower()]
|
|
1015
|
+
if not lines:
|
|
1016
|
+
console.print(output or "[yellow]No listening ports found.[/yellow]")
|
|
1017
|
+
pause()
|
|
1018
|
+
return
|
|
1019
|
+
|
|
1020
|
+
table = Table(title="Listening Ports", border_style="cyan")
|
|
1021
|
+
table.add_column("Raw Entry", style="white")
|
|
1022
|
+
for line in lines[:200]:
|
|
1023
|
+
table.add_row(line.strip())
|
|
1024
|
+
console.print("\n", table)
|
|
1025
|
+
if len(lines) > 200:
|
|
1026
|
+
console.print(f"[yellow][!] Showing first 200 of {len(lines)} listening entries.[/yellow]")
|
|
1027
|
+
pause()
|
|
1028
|
+
|
|
1029
|
+
# =============================================================================
|
|
1030
|
+
# 5. NETWORK TRAFFIC & LIVE PACKET SNIFFING
|
|
1031
|
+
# =============================================================================
|
|
1032
|
+
def packet_sniffer():
|
|
1033
|
+
"""Real-time live packet sniffer with IP header decoding."""
|
|
1034
|
+
show_banner()
|
|
1035
|
+
console.print(Panel("[bold yellow]🦈 Real-Time Live Packet Sniffer[/bold yellow]", border_style="yellow"))
|
|
1036
|
+
console.print("[dim yellow]Note: Windows requires running terminal as Administrator.[/dim yellow]\n")
|
|
1037
|
+
try:
|
|
1038
|
+
max_packets = int(console.input("[bold cyan]Enter number of packets to capture [default: 15]: [/bold cyan]").strip() or 15)
|
|
1039
|
+
except ValueError:
|
|
1040
|
+
max_packets = 15
|
|
1041
|
+
|
|
1042
|
+
console.print(f"\n[bold red]⚡ Listening for incoming network packets... (Ctrl+C to stop)[/bold red]\n")
|
|
1043
|
+
sniffer = None
|
|
1044
|
+
try:
|
|
1045
|
+
if os.name == 'nt':
|
|
1046
|
+
sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)
|
|
1047
|
+
host_ip = socket.gethostbyname(socket.gethostname())
|
|
1048
|
+
sniffer.bind((host_ip, 0))
|
|
1049
|
+
sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
|
|
1050
|
+
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
|
|
1051
|
+
else:
|
|
1052
|
+
sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
|
|
1053
|
+
|
|
1054
|
+
protocol_map = {1: "ICMP", 2: "IGMP", 6: "TCP", 17: "UDP"}
|
|
1055
|
+
for i in range(1, max_packets + 1):
|
|
1056
|
+
raw_data, addr = sniffer.recvfrom(65535)
|
|
1057
|
+
src_ip, dst_ip, proto_name = addr[0], "Local Machine", "RAW"
|
|
1058
|
+
if len(raw_data) >= 20:
|
|
1059
|
+
iph = struct.unpack('!BBHHHBBH4s4s', raw_data[:20])
|
|
1060
|
+
proto_name = protocol_map.get(iph[6], f"Proto-{iph[6]}")
|
|
1061
|
+
src_ip = socket.inet_ntoa(iph[8])
|
|
1062
|
+
dst_ip = socket.inet_ntoa(iph[9])
|
|
1063
|
+
console.print(f"[bold green]✔ Packet #{i:02d}[/bold green] | [cyan]{src_ip}[/cyan] -> [magenta]{dst_ip}[/magenta] | [yellow]{proto_name:<5}[/yellow] | {len(raw_data)} bytes")
|
|
1064
|
+
except PermissionError:
|
|
1065
|
+
console.print("[bold red]✘ Permission Denied: Run as Administrator / root![/bold red]")
|
|
1066
|
+
except KeyboardInterrupt:
|
|
1067
|
+
console.print("\n[yellow][!] Sniffing stopped by user.[/yellow]")
|
|
1068
|
+
except Exception as e:
|
|
1069
|
+
console.print(f"[bold red]✘ Sniffer Error: {e}[/bold red]")
|
|
1070
|
+
finally:
|
|
1071
|
+
if sniffer and os.name == 'nt':
|
|
1072
|
+
try: sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
|
|
1073
|
+
except Exception: pass
|
|
1074
|
+
pause()
|
|
1075
|
+
|
|
1076
|
+
# =============================================================================
|
|
1077
|
+
# 5. ADVANCED SERIOUS NETWORK TOOLS
|
|
1078
|
+
# =============================================================================
|
|
1079
|
+
def ssl_inspector():
|
|
1080
|
+
"""Inspects SSL/TLS certificates for a given domain."""
|
|
1081
|
+
show_banner()
|
|
1082
|
+
console.print(Panel("[bold yellow]📜 SSL/TLS Certificate Inspector[/bold yellow]", border_style="yellow"))
|
|
1083
|
+
domain = console.input("[bold cyan]Enter domain (e.g. google.com): [/bold cyan]").strip()
|
|
1084
|
+
if not domain:
|
|
1085
|
+
console.print("[red]No domain provided.[/red]")
|
|
1086
|
+
pause()
|
|
1087
|
+
return
|
|
1088
|
+
domain = domain.replace("https://", "").replace("http://", "").split("/")[0].split(":")[0]
|
|
1089
|
+
|
|
1090
|
+
console.print(f"\n[bold cyan][*] Fetching SSL certificate for {domain}...[/bold cyan]\n")
|
|
1091
|
+
try:
|
|
1092
|
+
context = ssl.create_default_context()
|
|
1093
|
+
with socket.create_connection((domain, 443), timeout=5) as sock:
|
|
1094
|
+
with context.wrap_socket(sock, server_hostname=domain) as ssock:
|
|
1095
|
+
cert = ssock.getpeercert()
|
|
1096
|
+
|
|
1097
|
+
table = Table(title=f"SSL Certificate Details: {domain}", border_style="cyan")
|
|
1098
|
+
table.add_column("Property", style="bold cyan")
|
|
1099
|
+
table.add_column("Value", style="bold white")
|
|
1100
|
+
|
|
1101
|
+
subject = dict(x[0] for x in cert['subject'])
|
|
1102
|
+
issuer = dict(x[0] for x in cert['issuer'])
|
|
1103
|
+
|
|
1104
|
+
table.add_row("Subject (CN)", subject.get('commonName', 'N/A'))
|
|
1105
|
+
table.add_row("Issuer (CN)", issuer.get('commonName', 'N/A'))
|
|
1106
|
+
table.add_row("Organization", subject.get('organizationName', 'N/A'))
|
|
1107
|
+
table.add_row("Valid From", cert.get('notBefore', 'N/A'))
|
|
1108
|
+
table.add_row("Valid Until", cert.get('notAfter', 'N/A'))
|
|
1109
|
+
table.add_row("Serial Number", cert.get('serialNumber', 'N/A'))
|
|
1110
|
+
|
|
1111
|
+
sans = [san for type_, san in cert.get('subjectAltName', []) if type_ == 'DNS']
|
|
1112
|
+
table.add_row("Subject Alt Names (SANs)", "\n".join(sans) if sans else "None")
|
|
1113
|
+
console.print(table)
|
|
1114
|
+
except ssl.SSLCertVerificationError as e:
|
|
1115
|
+
console.print(f"[bold red]✘ SSL Verification Failed: {e}[/bold red]")
|
|
1116
|
+
except Exception as e:
|
|
1117
|
+
console.print(f"[bold red]✘ Error fetching certificate: {e}[/bold red]")
|
|
1118
|
+
pause()
|
|
1119
|
+
|
|
1120
|
+
def whois_lookup():
|
|
1121
|
+
"""Fetches RDAP/WHOIS domain registration data."""
|
|
1122
|
+
show_banner()
|
|
1123
|
+
console.print(Panel("[bold yellow]🕸️ RDAP / WHOIS Domain Intelligence[/bold yellow]", border_style="yellow"))
|
|
1124
|
+
domain = console.input("[bold cyan]Enter domain (e.g. example.com): [/bold cyan]").strip()
|
|
1125
|
+
if not domain:
|
|
1126
|
+
console.print("[red]No domain provided.[/red]")
|
|
1127
|
+
pause()
|
|
1128
|
+
return
|
|
1129
|
+
domain = domain.replace("https://", "").replace("http://", "").split("/")[0].split(":")[0]
|
|
1130
|
+
|
|
1131
|
+
console.print(f"\n[bold cyan][*] Querying RDAP registry for {domain}...[/bold cyan]\n")
|
|
1132
|
+
try:
|
|
1133
|
+
url = f"https://rdap.org/domain/{domain}"
|
|
1134
|
+
res = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'}, timeout=8).json()
|
|
1135
|
+
|
|
1136
|
+
table = Table(title=f"Domain Registration Data: {domain}", border_style="cyan")
|
|
1137
|
+
table.add_column("Property", style="bold cyan")
|
|
1138
|
+
table.add_column("Value", style="bold white")
|
|
1139
|
+
|
|
1140
|
+
table.add_row("Handle", res.get('handle', 'N/A'))
|
|
1141
|
+
table.add_row("LDH Name", res.get('ldhName', 'N/A'))
|
|
1142
|
+
table.add_row("Status", ", ".join(res.get('status', [])))
|
|
1143
|
+
|
|
1144
|
+
for event in res.get('events', []):
|
|
1145
|
+
table.add_row(f"Event: {event.get('eventAction', 'Unknown')}", event.get('eventDate', 'N/A'))
|
|
1146
|
+
|
|
1147
|
+
for entity in res.get('entities', []):
|
|
1148
|
+
roles = entity.get('roles', [])
|
|
1149
|
+
vcard_array = entity.get('vcardArray', [None, []])[1]
|
|
1150
|
+
fn = next((v[3] for v in vcard_array if v[0] == 'fn'), "Unknown")
|
|
1151
|
+
table.add_row(f"Entity ({', '.join(roles)})", fn)
|
|
1152
|
+
|
|
1153
|
+
ns_list = [ns.get('ldhName') for ns in res.get('nameservers', []) if ns.get('ldhName')]
|
|
1154
|
+
table.add_row("Nameservers", "\n".join(ns_list) if ns_list else "N/A")
|
|
1155
|
+
console.print(table)
|
|
1156
|
+
except Exception as e:
|
|
1157
|
+
console.print(f"[bold red]✘ Error parsing RDAP data: {e}[/bold red]")
|
|
1158
|
+
pause()
|
|
1159
|
+
|
|
1160
|
+
def traceroute_tool():
|
|
1161
|
+
"""Executes system traceroute/tracert."""
|
|
1162
|
+
show_banner()
|
|
1163
|
+
console.print(Panel("[bold yellow]🛤️ Traceroute & Route Analysis[/bold yellow]", border_style="yellow"))
|
|
1164
|
+
target = console.input("[bold cyan]Enter target IP or Domain: [/bold cyan]").strip()
|
|
1165
|
+
if not target:
|
|
1166
|
+
console.print("[red]No target provided.[/red]")
|
|
1167
|
+
pause()
|
|
1168
|
+
return
|
|
1169
|
+
|
|
1170
|
+
console.print(f"\n[bold cyan][*] Running traceroute to {target}...[/bold cyan]\n")
|
|
1171
|
+
try:
|
|
1172
|
+
cmd = ['tracert', '-d', '-w', '2', '-h', '30', target] if os.name == 'nt' else ['traceroute', '-n', '-w', '2', '-m', '30', target]
|
|
1173
|
+
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, encoding='utf-8', errors='replace')
|
|
1174
|
+
for line in process.stdout:
|
|
1175
|
+
console.print(f"[dim]{line.strip()}[/dim]")
|
|
1176
|
+
process.wait()
|
|
1177
|
+
except FileNotFoundError:
|
|
1178
|
+
console.print("[bold red]✘ Traceroute command not found on this system.[/bold red]")
|
|
1179
|
+
except Exception as e:
|
|
1180
|
+
console.print(f"[bold red]✘ Error: {e}[/bold red]")
|
|
1181
|
+
pause()
|
|
1182
|
+
|
|
1183
|
+
def network_interfaces():
|
|
1184
|
+
"""Displays network interfaces and ARP table."""
|
|
1185
|
+
show_banner()
|
|
1186
|
+
console.print(Panel("[bold yellow]📡 Network Interfaces & ARP Table[/bold yellow]", border_style="yellow"))
|
|
1187
|
+
try:
|
|
1188
|
+
if os.name == 'nt':
|
|
1189
|
+
cmd_ip, cmd_arp = ['ipconfig', '/all'], ['arp', '-a']
|
|
1190
|
+
else:
|
|
1191
|
+
cmd_ip = ['ip', 'addr'] if os.path.exists('/sbin/ip') else ['ifconfig']
|
|
1192
|
+
cmd_arp = ['ip', 'neigh'] if os.path.exists('/sbin/ip') else ['arp', '-a']
|
|
1193
|
+
|
|
1194
|
+
console.print(Panel("[bold green]IP Configuration[/bold green]", border_style="green"))
|
|
1195
|
+
console.print(f"[dim]{subprocess.run(cmd_ip, capture_output=True, text=True, encoding='utf-8', errors='replace').stdout}[/dim]")
|
|
1196
|
+
|
|
1197
|
+
console.print(Panel("[bold green]ARP / Neighbor Table[/bold green]", border_style="green"))
|
|
1198
|
+
console.print(f"[dim]{subprocess.run(cmd_arp, capture_output=True, text=True, encoding='utf-8', errors='replace').stdout}[/dim]")
|
|
1199
|
+
except Exception as e:
|
|
1200
|
+
console.print(f"[bold red]✘ Error fetching network info: {e}[/bold red]")
|
|
1201
|
+
pause()
|
|
1202
|
+
|
|
1203
|
+
# =============================================================================
|
|
1204
|
+
# 6. ACTIVATION & MAIN MENU
|
|
1205
|
+
# =============================================================================
|
|
1206
|
+
def activation_screen():
|
|
1207
|
+
"""Welcome screen and activation animation."""
|
|
1208
|
+
show_banner()
|
|
1209
|
+
name = console.input("[bold cyan]Hi user, what is your name? [/bold cyan]").strip()
|
|
1210
|
+
if not name:
|
|
1211
|
+
name = "Operative"
|
|
1212
|
+
console.print(f"\n[bold green]Welcome {name}![/bold green] Ready to access the Super Toolkit.\n")
|
|
1213
|
+
|
|
1214
|
+
for _ in track(range(100), description="[bold cyan]please wait...[/bold cyan]"):
|
|
1215
|
+
time.sleep(0.008)
|
|
1216
|
+
time.sleep(0.3)
|
|
1217
|
+
|
|
1218
|
+
def main():
|
|
1219
|
+
"""Main interactive loop."""
|
|
1220
|
+
try:
|
|
1221
|
+
activation_screen()
|
|
1222
|
+
except (KeyboardInterrupt, EOFError):
|
|
1223
|
+
clear_screen()
|
|
1224
|
+
sys.exit(0)
|
|
1225
|
+
|
|
1226
|
+
while True:
|
|
1227
|
+
try:
|
|
1228
|
+
show_banner()
|
|
1229
|
+
options = [
|
|
1230
|
+
"1. 🔑 Password Generator & Security Evaluator",
|
|
1231
|
+
"2. 🔒 Hash Generator & Verifier (MD5, SHA-256, SHA-512...)",
|
|
1232
|
+
"3. 📡 My Network & Public IP Information",
|
|
1233
|
+
"4. 🌐 Target Domain / Website IP & DNS Lookup",
|
|
1234
|
+
"5. 🌍 GeoIP Intelligence Location Tracker",
|
|
1235
|
+
"6. 🏷️ MAC Address Vendor & OUI Lookup",
|
|
1236
|
+
"7. 🖧 CIDR Subnet Calculator & Ping Sweep",
|
|
1237
|
+
"8. ⚡ Multi-Threaded Port Scanner (with Service Detection)",
|
|
1238
|
+
"9. 🕵️ Subdomain Finder & DNS Recon",
|
|
1239
|
+
"10. 📁 Web Directory & Endpoint Fuzzer",
|
|
1240
|
+
"11. 🔍 HTTP Header Inspector & Security Auditor",
|
|
1241
|
+
"12. 🦈 Real-Time Live Packet Sniffer",
|
|
1242
|
+
"13. 📜 SSL/TLS Certificate Inspector",
|
|
1243
|
+
"14. 🕸️ RDAP / WHOIS Domain Intelligence",
|
|
1244
|
+
"15. 🛤️ Traceroute & Route Analysis",
|
|
1245
|
+
"16. 📡 Network Interfaces & ARP Table Analyzer",
|
|
1246
|
+
"0. 🚪 Exit Super Toolkit",
|
|
1247
|
+
]
|
|
1248
|
+
options = [
|
|
1249
|
+
"1. Password Generator - Create strong passwords and estimate strength",
|
|
1250
|
+
"2. Hash Generator & Verifier - Generate or verify MD5/SHA hashes",
|
|
1251
|
+
"3. My Network Info - Show local/public IP and geolocation summary",
|
|
1252
|
+
"4. Domain IP Lookup - Resolve domains and reverse DNS records",
|
|
1253
|
+
"5. GeoIP Tracker - Locate an IP or domain by public GeoIP data",
|
|
1254
|
+
"6. MAC Vendor Lookup - Identify manufacturer from a MAC/OUI prefix",
|
|
1255
|
+
"7. Subnet Calculator & Sweep - Calculate CIDR details and find live hosts",
|
|
1256
|
+
"8. Port Scanner - Scan TCP ports and identify common services",
|
|
1257
|
+
"9. Subdomain Finder - Resolve common subdomains for a target domain",
|
|
1258
|
+
"10. Web Directory Fuzzer - Check common web paths and endpoints",
|
|
1259
|
+
"11. HTTP Header Inspector - View headers and audit security headers",
|
|
1260
|
+
"12. Packet Sniffer - Capture and summarize live raw IP packets",
|
|
1261
|
+
"13. SSL/TLS Inspector - Read certificate issuer, validity, and SANs",
|
|
1262
|
+
"14. RDAP / WHOIS Lookup - Fetch domain registration intelligence",
|
|
1263
|
+
"15. Traceroute - Trace network path hops to a target",
|
|
1264
|
+
"16. Interfaces & ARP - Show local adapters and ARP/neighbor table",
|
|
1265
|
+
"17. DNS Record Lookup - Query A, AAAA, MX, NS, TXT, SOA, and CNAME",
|
|
1266
|
+
"18. HTTP Status Checker - Show status, redirects, timing, and server info",
|
|
1267
|
+
"19. TCP Ping - Measure TCP connection latency to a host and port",
|
|
1268
|
+
"20. Listening Ports - List local services listening on network ports",
|
|
1269
|
+
"21. Tech Stack Detector - Fingerprint web frameworks, CDN/WAF, and cookies",
|
|
1270
|
+
"22. Web Endpoint Auditor - Check common web metadata and exposure paths",
|
|
1271
|
+
"0. Exit - Close Super Toolkit",
|
|
1272
|
+
]
|
|
1273
|
+
choice = questionary.select(
|
|
1274
|
+
"Select a Tool to Launch:",
|
|
1275
|
+
choices=options,
|
|
1276
|
+
pointer="▶ ",
|
|
1277
|
+
style=MENU_STYLE
|
|
1278
|
+
).ask()
|
|
1279
|
+
|
|
1280
|
+
if choice and choice.startswith("0."):
|
|
1281
|
+
clear_screen()
|
|
1282
|
+
console.print(Panel("[bold green]Thank you for using Super Toolkit. Stay safe & secure![/bold green]", border_style="cyan"))
|
|
1283
|
+
break
|
|
1284
|
+
|
|
1285
|
+
if not choice or "0. 🚪 Exit" in choice:
|
|
1286
|
+
clear_screen()
|
|
1287
|
+
console.print(Panel("[bold green]Thank you for using Super Toolkit. Stay safe & secure![/bold green]", border_style="cyan"))
|
|
1288
|
+
break
|
|
1289
|
+
|
|
1290
|
+
if "1. 🔑" in choice: password_generator()
|
|
1291
|
+
elif "2. 🔒" in choice: hash_tool()
|
|
1292
|
+
elif "3. 📡" in choice: my_network_info()
|
|
1293
|
+
elif "4. 🌐" in choice: target_ip_lookup()
|
|
1294
|
+
elif "5. 🌍" in choice: geoip_lookup()
|
|
1295
|
+
elif "6. 🏷️" in choice: mac_lookup()
|
|
1296
|
+
elif "7. 🖧" in choice: subnet_scanner()
|
|
1297
|
+
elif "8. ⚡" in choice: port_scanner()
|
|
1298
|
+
elif "9. 🕵️" in choice: subdomain_finder()
|
|
1299
|
+
elif "10. 📁" in choice: dir_fuzzer()
|
|
1300
|
+
elif "11. 🔍" in choice: header_inspector()
|
|
1301
|
+
elif "12. 🦈" in choice: packet_sniffer()
|
|
1302
|
+
elif "13. 📜" in choice: ssl_inspector()
|
|
1303
|
+
elif "14. 🕸️" in choice: whois_lookup()
|
|
1304
|
+
elif "15. 🛤️" in choice: traceroute_tool()
|
|
1305
|
+
elif "16. 📡" in choice: network_interfaces()
|
|
1306
|
+
|
|
1307
|
+
menu_tools = {
|
|
1308
|
+
"1.": password_generator,
|
|
1309
|
+
"2.": hash_tool,
|
|
1310
|
+
"3.": my_network_info,
|
|
1311
|
+
"4.": target_ip_lookup,
|
|
1312
|
+
"5.": geoip_lookup,
|
|
1313
|
+
"6.": mac_lookup,
|
|
1314
|
+
"7.": subnet_scanner,
|
|
1315
|
+
"8.": port_scanner,
|
|
1316
|
+
"9.": subdomain_finder,
|
|
1317
|
+
"10.": dir_fuzzer,
|
|
1318
|
+
"11.": header_inspector,
|
|
1319
|
+
"12.": packet_sniffer,
|
|
1320
|
+
"13.": ssl_inspector,
|
|
1321
|
+
"14.": whois_lookup,
|
|
1322
|
+
"15.": traceroute_tool,
|
|
1323
|
+
"16.": network_interfaces,
|
|
1324
|
+
"17.": dns_record_lookup,
|
|
1325
|
+
"18.": http_status_checker,
|
|
1326
|
+
"19.": tcp_ping_tool,
|
|
1327
|
+
"20.": listening_ports,
|
|
1328
|
+
"21.": tech_stack_detector,
|
|
1329
|
+
"22.": web_endpoint_auditor,
|
|
1330
|
+
}
|
|
1331
|
+
for prefix, tool_func in sorted(menu_tools.items(), key=lambda item: len(item[0]), reverse=True):
|
|
1332
|
+
if choice.startswith(prefix):
|
|
1333
|
+
tool_func()
|
|
1334
|
+
break
|
|
1335
|
+
|
|
1336
|
+
except (KeyboardInterrupt, EOFError):
|
|
1337
|
+
clear_screen()
|
|
1338
|
+
console.print("[bold red]\nOperation interrupted. Exiting...[/bold red]")
|
|
1339
|
+
break
|
|
1340
|
+
except Exception as e:
|
|
1341
|
+
console.print(f"[bold red]\nUnexpected error: {e}[/bold red]")
|
|
1342
|
+
pause()
|
|
1343
|
+
|
|
1344
|
+
if __name__ == "__main__":
|
|
1345
|
+
main()
|