The CVE record names one bug. The URL names three.
Ubiquiti's Security Advisory Bulletin SAB-064 covers five issues. Three of them are CVSS 10.0 with the same AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H vector: CVE-2026-34908, CVE-2026-34909, and CVE-2026-34910. NVD ingested all three in May. CVE-2026-34910 is the one CISA added to KEV with the June 26 deadline, on telemetry of Mirai-class implants reaching root on internet-exposed UniFi OS Servers (azsxd v2.0, infection tag unifi.exploit, observed from 176.65.148.183/24, per pwndefend on June 9).
The KEV listing is for the command-injection CVE because that is the link in the chain that produces code execution. It is not the link that admits the unauthenticated request. The unauth admission is CVE-2026-34908 (access control flaw in the Nginx auth gateway) and CVE-2026-34909 (path-traversal normalization in the same layer). The nuclei template's one URL exercises all three in series, because the only network-adjacent attacker is one with no credentials, and only the composition produces unauthenticated RCE. Bishop Fox's chain writeup names this explicitly. NVD's three records do not.
This post traces the URL by its three answers, in the order Nginx processes them.
/api/auth/validate-sso/ was the auth-exempt prefix
UniFi OS Server's request lifecycle runs every inbound request through an Nginx-internal auth_request directive. The gate is a Node.js handler whose contract with Nginx is the standard one: return 200 to let the request through to its real handler, return 401 to short-circuit. The relevant fragment of the gate, from the 5.0.6 binary:
authCheck = async (req, res) => {
let uri = getHeader(req.headers, "x-original-uri");
if (publicRoutes.has(`${method} ${uri}`) ||
uri?.startsWith("/api/auth/validate-sso/"))
return res.statusCode = 200, res.end();
// ...further checks below this line require a valid session.
};
Two operations are visible. The first is a Set lookup against an enumerated allowlist of method-plus-URI pairs. The second is a startsWith against the literal prefix /api/auth/validate-sso/. The prefix is correct in isolation: the SSO validation endpoint is the callback an unauthenticated single-sign-on flow reaches when the IdP redirects the user's browser back to UniFi OS, and the handler that processes the SSO assertion has to be reachable without a session, because the request that establishes the session is the one being processed.
x-original-uri is the request-URI header Nginx populates for auth_request subrequests. It carries the URI exactly as the client sent it on the wire. Percent-encoded bytes are still percent-encoded. Path-traversal segments are still present. The gate reads that string.
Nginx, having received 200 from the gate, then dispatches the same request to its real upstream. The dispatch uses Nginx's own $uri variable, which holds the URI after Nginx's normalization passes: percent-decoded, dot-segments resolved, multiple slashes collapsed. The same request is now two different strings: the one the gate inspected, and the one the router will route. The nuclei template's URL is the eight bytes that make them disagree.
The raw form is /api/auth/validate-sso/..%2f..%2f..%2fproxy/users/api/v2/ucs/update/latest_package. The startsWith returns true; the gate emits 200. Nginx then normalizes: %2f decodes to /, .. resolves against the preceding segment, and the path collapses to /proxy/users/api/v2/ucs/update/latest_package. The router matches that against the /proxy/users/ location block, which forwards to the authenticated users-api upstream as if the gate had approved it for that destination. The gate never saw that destination. The router never saw the prefix the gate approved.
This is the Gate Before Canonicalize pattern in its purest HTTP form. The catalog's prior exhibits split the two parses across Tomcat vs Spring (Alfresco's ..;/), s3-proxy's auth middleware vs its handler (CVE-2026-42882), or Nitro's router vs an unspecified downstream (CVE-2026-44373). UniFi splits them across the smallest possible boundary: two variables Nginx itself populates from the same request, one before normalization and one after. The check ran against the wrapped representation; the action ran against the unwrapped one.
The traversal reached a handler that shelled out its query string
Inside the authenticated zone, /proxy/users/api/v2/ucs/update/latest_package maps to the package-update service. The Go handler reads the pkg_name query parameter and constructs the command that will fetch the requested package's latest version:
cmd := exec.Command("sh", "-c",
fmt.Sprintf("sudo /usr/bin/uos runnable latest-versions %v", pkgName))
The %v verb interpolates the caller-supplied string into the shell command. The shell is /bin/sh -c. The semicolons in the nuclei template's pkg_name=%3b+nslookup+...+%3b decode to ;, which sh -c parses as command separators. The injected command runs.
The sh -c wrapper is the textbook shell-injection sink, the kind every input-validation curriculum names first. The defensive control the handler relied on was that no unauthenticated caller could ever reach it. That control was the gate two paragraphs up. The gate did not hold.
The command runs as the ucs-update service account. Bishop Fox documents that account's sudoers entry: passwordless sudo for /usr/bin/dpkg, /bin/chmod, /bin/systemctl, /usr/bin/uos. The path to root is sudo -n dpkg -i evil.deb with a postinst script that reads /etc/shadow or writes whatever the operator wants written, executed by dpkg's package manager as root. The privilege escalation is documented by the operating system. Bishop Fox shipped a five-line Debian package to demonstrate the chain end to end.
The CVE description for 34910 reads improper input validation vulnerability ... to execute a Command Injection. The CWE is CWE-20. Neither the description nor the CWE distinguishes "the handler shells out its query parameter" from "the handler is unauthenticated and shells out its query parameter." The handler is one of those, structurally. The CVE chain is the other.
The patch ships three independent fixes
UniFi OS Server 5.0.8 (and the 5.1.10 / 5.1.11 / 5.1.12 line for the appliance variants) contains three independent changes, each addressing one link:
# 1. Nginx-layer canonicalize-before-gate (CVE-2026-34908/34909)
- auth_request /auth/check;
+ auth_request /auth/check;
+ # Reject any request whose raw and normalized service segments diverge.
+ if ($request_uri ~ "/(\.\.|%2[fF]|%5[cC])") { return 400; }
# 2. Go-layer package-name allowlist + argv array (CVE-2026-34910)
- cmd := exec.Command("sh", "-c",
- fmt.Sprintf("sudo /usr/bin/uos runnable latest-versions %v", pkgName))
+ if !packageNameRe.MatchString(pkgName) {
+ return errors.New("invalid package name")
+ }
+ cmd := exec.Command("sudo", "/usr/bin/uos", "runnable",
+ "latest-versions", pkgName)
# 3. Sudoers narrowing (partial mitigation)
- ucs-update ALL=(root) NOPASSWD: /usr/bin/dpkg, /bin/chmod, /bin/systemctl, /usr/bin/uos
+ ucs-update ALL=(root) NOPASSWD: /bin/systemctl, /usr/bin/uos
The three fixes have three different owners. The Nginx rule is an ops change. The Go allowlist plus argv refactor is an application change. The sudoers narrowing is a system-packaging change. The chain is an Emergent Primitive: no single component was wrong in isolation. The SSO callback is supposed to be reachable without a session. The package-update handler is supposed to take a package name. dpkg is supposed to run a .deb's postinst as root because that is what package managers do. The primitive lives in the composition the gate failed to defend.
This is the catalog's first network-appliance exhibit of gate-before-canonicalize. The prior exhibits all live in web-framework processes (Spring, Tomcat, Express, FastAPI, Go HTTP routers). UniFi OS Server is a Linux box behind a vendor's Nginx config, with a Node.js auth daemon proxying to a Go services tier. The shape is identical. The substrate is a switch / NVR / WiFi controller / NAS sitting on the WAN side of every small business that bought one. The shape is what predicts the next exhibit; the substrate is what determines how many devices are exposed.
The patch closes the door. The key the door leaked still signs
Bishop Fox notes one operational detail in passing that neither the CVE record nor SAB-064 emphasizes. Every UniFi OS Server signs admin session tokens with a key stored at /data/unifi-core/config/jwt.yaml. The 5.0.8 patch closes the unauth path that lets an attacker read that file. The patch does not invalidate the key. The patch does not generate a new key. The patch does not require the operator to do either.
The mass exploitation observed in the wild has been ongoing since at least early June. Every UniFi OS Server reachable from the internet during that window is, until proven otherwise, an instance whose JWT signing key has been exfiltrated by the Mirai operator (or any of the other operators known by now to have the chain). Applying the patch in this state restores the unauth perimeter and leaves the post-compromise persistence vector intact: a forged JWT minted with the leaked key still authenticates as admin, against the patched code, indefinitely. This is the Revocation Gap, measured here in operator decisions the vendor's advisory does not name. The defender who patches and reboots has closed CVE-2026-34910. They have not closed any session minted against the key the bug leaked.
The recovery shape is the one the advisory should have led with: patch, rotate the JWT signing key, invalidate all existing admin sessions, audit the controller's /data for unauthorized changes since the device was first reachable. The advisory's recommendation, per its public text, is "update to the latest version." The nuclei template's verified: true annotation and KEV's June 26 deadline will produce a wave of patched-but-still-compromised devices over the next 72 hours, indistinguishable from the wave of patched-and-clean devices except by the actions taken inside the next session.
What the record says, and what the URL says
The CVE record, the NVD entry, the CWE assignment, and the KEV annotation all describe CVE-2026-34910 as command injection on UniFi OS. The nuclei template's URL is one HTTP request. The request decodes as three independent bugs in series: an authentication gate that read the raw URI, a router that read the normalized one, and a handler that interpolated the query string into sh -c. Each of those three is named by its own CVE. The chain is named by none. The KEV deadline is named for the third link.
PoC: projectdiscovery/nuclei-templates
The patch closes the door. The signing key the door leaked still signs valid admin tokens.