Safely detect whether a UniFi Network Application controller is vulnerable to CVE-2026-22557 without causing any disruption. See the full write-up at the Bishop Fox blog.
CVE-2026-22557 is an unauthenticated path-traversal vulnerability in the UniFi Network Application's guest captive portal that allows remote attackers to read arbitrary files as the controller process user (CVSS 10.0). The vulnerability affects UniFi Network Application 10.1.85 and earlier on the 10.1.x branch, and corresponding earlier releases on the 10.2.x and 9.0.x branches. Ubiquiti shipped fixes in 10.1.89, 10.2.97, and 9.0.118 (SAB-062).
The guest portal's failure-page renderer reads the page_error request parameter and uses it verbatim
as a relative path on the portal resource loader, with no validation. When portal_customized: true is
set on the queried site (the configuration any branded captive portal, such as a hotel, retail, or café
deployment, ends up with), the loader resolves that path against the on-disk portal directory, and the
path can escape it.
This tool performs non-destructive vulnerability testing by:
- Reading the controller's reported version from
/statusand comparing it against the SAB-062 fix lines - Confirming the guest portal is dispatched on the supplied URL
- Probing the
page_errorparameter on/guest/<site>/wechat/signat../depths 1-8 against a dual-branch filename, to determine whether the traversal fires at all - If the traversal fires, performing one additional read against a non-sensitive runtime catalog file
(
firmware.json) to determine whether the customized-portal filesystem branch is active
The tool requests only the runtime catalog file as a branch-active oracle. It does not read credentials, TLS material, database state, or backups, and does not write any file content to stdout or disk. Only the verdict is printed.
- Vulnerable controllers with
portal_customized: trueon the queried site return the catalog file at the calibrated depth, confirmingVULNERABLE. - Partially exposed controllers running a vulnerable build but with
portal_customized: falseon the queried site trigger the traversal at the calibration step but do not return disk content at the confirmation step →PARTIALLY EXPOSED. The application binary is still vulnerable; patching is required even though this site is not in the exploitable state. - Patched controllers (10.1.89+ / 10.2.97+ / 9.0.118+) reject the traversal at every depth →
NOT VULNERABLE. - Unreachable sites or connectors yield
NOT EXPOSED.
git clone https://github.com/BishopFox/CVE-2026-22557-check
cd CVE-2026-22557-checkPython 3.9+ is required. The tool uses only the Python standard library, so no pip install step is needed.
Test a UniFi Network Application controller at <TARGET>. Point the tool at whichever URL reaches the
controller's web UI; the vulnerable handler is dispatched on the admin port (default 8443/TCP) as well
as the dedicated guest-portal ports (8843, 8880).
python3 cve_2026_22557_check.py <BASE_URL> [--site NAME]The default site slug is default. UniFi deployments with multiple sites configured should re-run the
tool with --site <name> for each one. A controller is exploitable as soon as any one of its sites
has portal_customized: true.
The examples below use --brief for a one-line-per-host result. Run without --brief to get the full
banner, step-by-step progress, and a result block that includes recommended remediation actions.
The controller is on a vulnerable build and the customized-portal precondition is active for the queried site, so the unauthenticated read path is reachable.
$ python3 cve_2026_22557_check.py https://192.168.1.100:8443 --brief
[VULNERABLE] https://192.168.1.100:8443 site=defaultThe controller is on a vulnerable build, but the queried site does not have portal_customized: true
enabled, so the disk-read precondition is not met for this site. The application binary still needs to
be patched; another site on the same controller may still be in the exploitable state.
$ python3 cve_2026_22557_check.py https://192.168.1.100:8443 --brief
[PARTIAL] https://192.168.1.100:8443 site=defaultThe controller is on a fixed build (10.1.89, 10.2.97, 9.0.118, or later), so the traversal does not fire on any queried depth.
$ python3 cve_2026_22557_check.py https://192.168.1.200:8443 --brief
[NOT VULNERABLE] https://192.168.1.200:8443 site=default--site NAME UniFi site slug to probe (default: "default")
--max-depth N maximum ../ traversal depth to try (default: 8)
--no-tls-recon skip TLS certificate subject/SAN display
--no-color disable ANSI color output (also honored: NO_COLOR env var)
--timeout N per-request timeout in seconds (default: 20)
--brief print a single-line result, no banner or steps
Exit codes: 0 VULNERABLE, 1 partially exposed / not vulnerable / not exposed, 2 transport error.
The tool can only return a VULNERABLE verdict if the following conditions are met:
- The target is running a vulnerable UniFi Network Application build (10.1.85 or earlier on the 10.1.x branch, or the equivalent earlier point releases on 10.2.x / 9.0.x).
- The guest portal is reachable at the supplied URL. The
/guest/*handler is dispatched on the admin port (8443) as well as the dedicated guest-portal ports (8843, 8880); the tool only probes the URL it is given. - The queried site has
portal_customized: truein itsguest_accesssetting, which is the precondition for the disk-read code path. A controller with multiple sites is exploitable if any of its sites has this enabled.
If any condition is not met, the tool falls through to a more conservative verdict. A
PARTIALLY EXPOSED result still means the application is vulnerable and must be patched; the queried
site simply happens not to expose the disk-read path.
- Upgrade to UniFi Network Application 10.1.89, 10.2.97, 9.0.118, or later.
- If you cannot patch immediately, restrict network access to ports 8443 / 8843 / 8880 (and any reverse proxy fronting them) so they are reachable only from trusted management networks.
- After patching, rotate any credentials that may have been disclosed while the controller was reachable: administrator passwords, the SSH credential the controller pushes to managed devices, the controller's TLS keystore, RADIUS shared secrets, VPN / WireGuard keys, SSL-inspection CA material, SMTP relay passwords, and any payment-gateway keys.
The vulnerable request pattern is:
GET /guest/s/<site>/wechat/sign?page_error=<value>
Legitimate clients reach /guest/s/<site>/wechat/sign only via POST (which the dispatcher accepts
without raising the failure-page renderer). Any GET of this endpoint carrying a page_error= query
parameter, particularly one containing ../, %2e%2e, or a leading /, is anomalous and should be
treated as an exploitation attempt. Tomcat's standard access log captures the full request line
including the query string.
This code is distributed under an MIT license.
Usage of this tool for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state, and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program.