A safe detector for the unauthenticated remote code execution chain in UniFi OS Server ≤ 5.0.6, disclosed in Ubiquiti Security Advisory Bulletin 064:
| CVE | Class | Role in the chain |
|---|---|---|
| CVE-2026-34908 / CVE-2026-34909 | Improper access control / path traversal | Auth-gateway bypass (the foothold) |
| CVE-2026-34910 | Improper input validation → command injection | Unauthenticated RCE reached via the bypass |
Fixed in UniFi OS Server 5.0.8.
Is it Safe to Run?
Yes. The detector is designed for production and assessment use:
- No command is executed. The probe reaches the vulnerable endpoint without a required parameter, so the handler rejects the missing input and we confirm the bug from that validation error.
- No target state is changed. Every request is a plain
GET. - False-positive guard. A
VULNERABLEverdict requires both that the auth-bypass reached the vulnerable handler and that a baseline request (same endpoint, no bypass) was correctly rejected with401.
How it Works
UniFi OS fronts its services with nginx, which enforces authentication via a subrequest. That
auth check treats any request whose raw URI begins with /api/auth/validate-sso/ as public,
but nginx routes to the backend using the normalized URI (it percent-decodes %2f→/ and
collapses ../). A request like:
GET /api/auth/validate-sso/..%2f..%2f..%2fproxy/users/api/v2/ucs/update/latest_package
is therefore treated as public (raw prefix) yet routed to the authenticated internal
package-update endpoint (normalized path). With no pkg_name, that endpoint replies
query param pkg_name required — proving the bypass works and the vulnerable handler is
reachable.
| Probe response | Verdict |
|---|---|
200 + handler error markers |
VULNERABLE |
400 (nginx rejects the divergence) |
PATCHED (5.0.8+) |
401, UniFi OS fingerprint present |
INCONCLUSIVE (auth enforced / bypass blocked) |
401, no UniFi OS fingerprint |
UNAFFECTED (not a UniFi OS Server) |
| anything else, no UniFi OS fingerprint | UNAFFECTED (not a UniFi OS Server) |
| anything else, UniFi OS fingerprint present | INCONCLUSIVE (unexpected response) |
The detector also sends a baseline request to the same endpoint without the bypass and
requires it to return 401 before declaring a host vulnerable. The check tests the
vulnerable behavior directly — a VULNERABLE verdict means the bypass actually reached the sink.
Confirming the target is UniFi OS before any not-vulnerable verdict
Whenever the probe does not positively confirm VULNERABLE or PATCHED, the detector fetches
the root page and looks for the UniFi OS management-portal fingerprint (
<title>UniFi OS</title>, window.UNIFI_OS_MANIFEST,id="portal-root",
id="site-manager_portal-container"). This applies to a 401 too: a UniFi OS host that
enforces auth on the bypass is INCONCLUSIVE, but a non-UniFi proxy that simply 401s
everything carries no fingerprint and is reported UNAFFECTED. So every "not vulnerable"
outcome is grounded in the fingerprint: markers present → INCONCLUSIVE; markers
absent → UNAFFECTED (the CVE does not apply).
Requirements
- Python 3.7+, standard library only — no third-party packages.
Usage
# single host (default port 11443)
./cve_2026_34908_check.py 192.168.1.10
# explicit port / URL forms
./cve_2026_34908_check.py host-a:11443 https://host-b
# scan a list, one target per line ('#' comments allowed), compact output
./cve_2026_34908_check.py -f targets.txt --brief
# machine-readable output for pipelines
./cve_2026_34908_check.py -f targets.txt --json > results.json
Options
| Flag | Description |
|---|---|
targets |
One or more host, host:port, or https://host:port |
-f, --targets-file FILE |
Read targets from a file (one per line; # comments) |
--brief |
Single aligned line per target — ideal for scanning many hosts |
--json |
Emit structured JSON results |
--timeout SECS |
Per-request timeout (default: 10) |
--no-color |
Disable coloured output (also honours NO_COLOR and non-TTY) |
Examples
A vulnerable console (verbose, the default). The second line is the verdict's
detail; the [!] marker and VULNERABLE render red on a TTY:
$ ./cve_2026_34908_check.py 192.168.1.10
[!] 192.168.1.10:11443: VULNERABLE
auth bypass reached the vulnerable handler (no command executed); baseline correctly 401
A patched console (5.0.8+):
$ ./cve_2026_34908_check.py 192.168.1.11
[+] 192.168.1.11:11443: PATCHED
nginx rejected the normalized-path divergence (HTTP 400) — 5.0.8+ behavior
A confirmed UniFi OS host the probe couldn't classify → INCONCLUSIVE. The detail
line distinguishes the two cases (auth enforced vs. unexpected response):
$ ./cve_2026_34908_check.py 192.168.1.12 192.168.1.13
[?] 192.168.1.12:11443: INCONCLUSIVE
bypass blocked / auth enforced (HTTP 401) on a confirmed UniFi OS host — not confirmed vulnerable
[?] 192.168.1.13:11443: INCONCLUSIVE
UniFi OS detected, but probe returned an unexpected response: HTTP 302 text/html
A host that isn't UniFi OS at all → UNAFFECTED (the advisory does not apply):
$ ./cve_2026_34908_check.py 203.0.113.9:443
[-] 203.0.113.9:443: UNAFFECTED
no UniFi OS web fingerprint on / (probe HTTP 404) — target is not a UniFi OS Server
Scan a list, one aligned line per host (--brief). Exit status is 1 if any host
is VULNERABLE, else 0 — handy in scripts:
$ ./cve_2026_34908_check.py -f targets.txt --brief; echo "exit: $?"
VULNERABLE 192.168.1.10:11443
PATCHED 192.168.1.11:11443
INCONCLUSIVE 192.168.1.12:11443
UNAFFECTED 203.0.113.9:443
ERROR 10.0.0.1:11443 timeout
exit: 1
Machine-readable output for pipelines (--json). Each result carries the
verdict plus the bypass.state/bypass.detail behind it; ERROR results carry an
error field instead of bypass:
$ ./cve_2026_34908_check.py 192.168.1.10 192.168.1.11 10.0.0.1 --json
[
{
"target": "192.168.1.10:11443",
"bypass": {
"state": "vulnerable",
"detail": "auth bypass reached the vulnerable handler (no command executed); baseline correctly 401"
},
"verdict": "VULNERABLE"
},
{
"target": "192.168.1.11:11443",
"bypass": {
"state": "patched",
"detail": "nginx rejected the normalized-path divergence (HTTP 400) — 5.0.8+ behavior"
},
"verdict": "PATCHED"
},
{
"target": "10.0.0.1:11443",
"verdict": "ERROR",
"error": "timeout"
}
]
Verdicts
| Verdict | Meaning |
|---|---|
VULNERABLE |
Auth bypass reached the vulnerable handler unauthenticated. Patch to 5.0.8+. |
PATCHED |
nginx rejected the normalized-path divergence (5.0.8+ behavior). |
UNAFFECTED |
No UniFi OS portal fingerprint on the root page — the target is not a UniFi OS Server, so the advisory does not apply. Includes hosts that return 401 without the fingerprint (e.g. an unrelated proxy that rejects everything). |
INCONCLUSIVE |
Confirmed UniFi OS host that couldn't be classified — verify the version manually. The verdict's detail line says which case: (a) the bypass was blocked / auth enforced (401), or (b) the probe returned an unexpected response. |
ERROR |
Connection/timeout/TLS failure. |
Exit codes
| Code | Meaning |
|---|---|
0 |
No target returned VULNERABLE |
1 |
At least one target is VULNERABLE |
2 |
Usage error (bad arguments / unreadable targets file) |
Limitations
INCONCLUSIVEis not a guarantee of safety — it confirms a UniFi OS host but couldn't classify it (auth enforced on the bypass, or an unexpected response). A host that is heavily filtered or fronted by an unexpected proxy may not be classifiable from the network alone. Confirm the running version where possible.UNAFFECTEDmeans the root page carried no UniFi OS portal fingerprint (including hosts that401without it). A UniFi OS console hidden behind a reverse proxy that strips or rewrites the portal HTML could therefore be misreported; confirm directly if the target is expected to be UniFi OS.- The detector reports reachability of the vulnerable behavior.
Remediation
Update UniFi OS Server to version 5.0.8 or later. The fix adds an nginx raw-vs-normalized URI comparison (closing the auth bypass) and input validation in the package-update path (closing the command injection).
Legal Disclaimer
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.
Comments