Popping Root on UniFi OS Server: Unauthenticated RCE Chain Detection & Analysis
TL;DRUbiquiti’s Security Advisory Bulletin 064 c 2026-6-5 13:0:0 Author: bishopfox.com(查看原文) 阅读量:3 收藏

TL;DR
Ubiquiti’s Security Advisory Bulletin 064 covers vulnerabilities across the UniFi OS device family that chain into unauthenticated remote code execution. An attacker bypasses the front-end authentication gateway to reach an internal API endpoint that runs an attacker-controlled value as a shell command, all without credentials.

We confirmed the full chain end-to-end, turning a single request into a reverse shell with full root privileges. The severity comes from what the appliance controls: it is the management plane for the network it runs. Root on it, therefore, exposes every stored secret, lets an attacker forge admin sessions that survive the patch, and, in physical deployments, can compromise managed devices including door controls and security cameras.

The only precondition for exploitation is access to the admin interface. Patch to UniFi OS Server (UOS) 5.0.8 or later, then rotate secrets, because patching does not evict an attacker who already got in. Bishop Fox has published a safe detection tool; read on for the detection method, the preconditions, and the full post-exploitation picture.

Background

UniFi OS Server is Ubiquiti’s software distribution of UniFi OS, the management platform that fronts the UniFi family of applications (Network, Protect, and the identity and update services that ship with it). It runs as a collection of localhost-bound backend services behind a single Nginx front end that terminates TLS, authenticates requests, and proxies them to the right internal services. This proxy function is the part that matters here: the entire security model depends on Nginx correctly deciding which requests are public and which require authentication.

On May 21, 2026, Ubiquiti published Security Advisory Bulletin 064 (SAB-064), covering five vulnerabilities across the UniFi OS device family. The three that matter most to an unauthenticated attacker are an improper access control flaw and path traversal flaw in the authentication gateway (CVE-2026-34908 and CVE-2026-34909, both rated CVSS 10.0 Critical, vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H) and an improper input validation (command injection) flaw in the package-update service (CVE-2026-34910, also CVSS 10.0 with the same vector). Chained together, they give unauthenticated command execution. The advisory credits the access control flaw to Duc Anh Nguyen (@heckintosh_), the path traversal to Abdulaziz Almadhi of Catchify Security, and the command injection to John Carroll.

The vendor’s own 10.0 rating is worth a second look because the vector tells the same story this post does: a network-reachable attacker with no privileges (AV:N, PR:N) and no user interaction (UI:N) can cause high confidentiality, integrity, and availability impacts (C:H/I:H/A:H) that cross a scope boundary (S:C). The changed scope is the vendor acknowledging that the impact escapes the vulnerable component, which is exactly the story we tell below.

The advisory bundles two more CVEs worth noting briefly. CVE-2026-33000 (CVSS 9.1, credited to V3rlust) is the same command injection sink reached with a valid view:identity:update token, so it is the authenticated sibling of CVE-2026-34910; the gateway bypass is what removes the token requirement and turns it into the unauthenticated CVE-2026-34910. CVE-2026-34911 (CVSS 7.7, credited to Hakai Security) is a separate low-privilege path traversal file read that we did not pursue.

For this post, we focus on the unauthenticated chain (CVE-2026-34908 and CVE-2026-34909 into CVE-2026-34910), because it is the one an attacker can drive from the outside with no credentials. We’ll concentrate on four things: how defenders can detect exposure safely, the preconditions that decide whether a given UniFi OS Server is actually reachable and exploitable, how an attacker drives the chain to root, and what root on this appliance actually puts at risk, which is considerably more than the host itself.

Our detection tool is available at https://github.com/BishopFox/CVE-2026-34908-check.

Immediate Actions for Defenders

  • Patch. Update UniFi OS Server to 5.0.8 or later (unifi-core 5.0.153); builds at or below 5.0.6 (unifi-core 5.0.126) are affected. If you run UniFi OS hardware rather than the Server software, the same CVEs apply per the advisory, with per-product fixed versions: most Cloud Gateways, Dream Machines, NVRs, and similar appliances move to 5.1.12 or later, the UNAS line to 5.1.10 or later, the Dream Machine Beast to 5.1.11 or later, and UniFi Express to 4.0.14 or later. Check the advisory for your exact model.
  • If you cannot patch immediately, restrict reachability. The chain is reachable wherever the UniFi OS Server web interface is reachable. That interface commonly listens on TCP 11443. Block external access to it and, where possible, restrict it to a management network, so that an unauthenticated request from the internet (or from a guest VLAN) cannot reach the gateway.
  • Assume root-level compromise on any instance that was exposed pre-patch. The chain reaches root (we confirmed it) with no credentials and no user interaction, so there is no failed-login trail to look for. Patching is necessary, but it is not remediation on its own: it closes the entry but does not undo anything an attacker did while they had root. Treat a reachable pre-patch console as compromised until proven otherwise, and work the steps below.
  • Rotate every secret, starting with the JWT signing key. Root reads the whole secret store (signing key, TLS keys, cloud tokens, the user database, RADIUS/WiFi/VPN material), so rotate all of it, force-logout sessions, and reset database credentials. Start with the signing key, as it can keep minting valid admin sessions after you patch, and only rotation invalidates them. Rotation needs a service restart or reboot to take effect (on a console group, rotate on the primary). Treat any biometric or NFC data as disclosed; it cannot be rotated. On UniFi OS Server, rotating the signing key looks like this:
    # Enable SSH + set password in the UI first, then:
    ssh root@<device> 
    openssl rand -hex 32                                   # new key 
    # edit /data/unifi-core/config/jwt.yaml -> secret: <new hex> 
    reboot                                                  # reload all token consumers 
    
  • For a confirmed compromise, rebuild rather than rotate-and-hope. A forged token could already have enabled SSH and set a root password that outlives key rotation, and root could have planted other persistence. For any console you believe was actually exploited, not merely exposed, rebuild from a known-good image.
  • Scan your network. Run our detection tool against any UniFi OS Server whose web interface is reachable, including from inside the network.

Reverse Engineering the Patch

Bishop Fox worked from the vendor advisory and a diff of the extracted 5.0.6 and 5.0.8 root filesystems. The interesting code lived in three places that a package-level diff alone could not surface:

  1. The Nginx configuration (small text files that diffed cleanly)
  2. A 1.2 MB obfuscated service.js JavaScript file implementing the unifi-core authentication handler (requiring string and structure diffing)
  3. Several large Go binaries implementing the package-update backend (requiring symbol and string diffing, with decompilation of the relevant handlers)

Renaming the decompiled identifiers to readable forms is what made the attack chain legible, and the snippets later in this post use those readable names. Armed with these (now comprehensible) diffs, we confirmed the root cause across all three layers, validated the full unauthenticated chain against a live UniFi OS Server 5.0.6 target, escalated from the command injection to a root shell on that target, and confirmed the fix on a patched 5.0.8 target.

One scope note before the details: The advisory lists the bypass and command-injection CVEs as affecting the broad UniFi OS hardware lineup as well (Dream Machines, Cloud Gateways, the UNVR and UNAS lines, UniFi Express, and more), each with its own fixed version. Our analysis and validation targeted the UniFi OS Server software distribution specifically, so the hardware appliances are in scope per the vendor advisory even though we did not validate the chain on them directly.

The Vulnerability

The chain has three parts: getting past the authentication gateway, reaching the command-injection sink behind it, and escalating from the service account that sink runs as to root.

Part One: The Authentication Gateway Bypass (CVE-2026-34908 / 34909)

Nginx fronts everything and enforces authentication with an auth_request subrequest to the unifi-core Node service. That handler decides whether a request is public or must be authenticated. In 5.0.6 it makes that decision from the request URI:

authCheck = async (req, res) => { 
  let uri = getHeader(req.headers, "x-original-uri");   // the RAW $request_uri 
  ... 
  if (publicRoutes.has(`${method} ${uri}`) || 
      uri?.startsWith("/api/auth/validate-sso/"))        // public / auth-exempt prefix 
      return res.statusCode = 200, res.end(); 
  ... 
  return res.statusCode = 401, res.end(); 
};

The root cause is a divergence between two different views of the same URI. The public-exemption check is a prefix match on the raw x-original-uri (Nginx’s $request_uri, exactly as the client sent it, still percent-encoded). But Nginx selects the upstream location and, therefore, which backend the request is proxied to, using the normalized $uri (percent-decoded, so %2f becomes /, and with ../ segments collapsed). When those two views disagree, the gate and the router are reading different requests.

That is the whole bug – why this was registered as two separate CVEs is unclear to us. A request can be crafted, so its raw form begins with the auth-exempt /api/auth/validate-sso/ prefix (so the gateway returns 200 and waves it through), while its normalized form resolves to an authenticated internal /proxy/<service>/ route (so Nginx proxies it to a backend that assumes authentication already happened).

We confirmed the bypass against a live 5.0.6 virtual machine. Requests built this way reached internal backends that are supposed to require authentication: the identity (ulp-go) backend answered with its own application-level JSON, and the Network application answered with its own login-required error, in both cases the backend’s response rather than Nginx’s 401. Control requests pinned down the cause: the identical traversal behind a non-exempt prefix returned 401, and the untraversed /proxy/<service>/ path returned 401. Only the validate-sso exemption produced the bypass, which is exactly what the root-cause analysis predicted.

Part Two: The Command-injection Sink (CVE-2026-34910 / 33000)

Behind the gateway sits a package-update route in the shared identity and update backend (present in both the ulp-go application and the identity-update application). The handler retrieves the “latest package” for a named package and, when asked to do so, resolves that version by shelling out to a local command.

The data flow from the request to the shell is short. The handler takes a caller-supplied package name and an option that forces the command code path and passes the package name down through a small chain of helpers to a sink that builds a command string and executes it. The sink builds the string with fmt.Sprintf against a format like:

sudo /usr/bin/uos runnable latest-versions %v

where %v is the caller’s package name, interpolated verbatim. In 5.0.6 there is no validation of that name, and the resulting string is handed to a helper that runs it through a sh -c shell wrapper. Because the package name reaches a shell with no sanitization, shell metacharacters in it are interpreted rather than treated as data.

This same route and sink is the basis for two CVEs. Reached with a valid view:identity:update token, it is CVE-2026-33000 (a privileged-access command injection). Reached without any token by way of the gateway bypass described above, it is CVE-2026-34910, the unauthenticated case which is the focus of this post.

We validated the unauthenticated path against our live 5.0.6 test target using a benign, time-based oracle: a request whose injected portion simply causes the server to pause for a fixed interval before responding. On 5.0.6, the response was delayed by the injected interval (and returned HTTP 200); on patched 5.0.8, the same request returned HTTP 400 with no delay. That differential, slow 200 on 5.0.6 vs. fast 400 on 5.0.8, is what confirmed both the vulnerability and the fix. 

Part Three: Privilege Escalation to Root (Over-privileged Service Account)

The command injection does not execute as root directly. It runs as the service account behind the package-update runnable, ucs-update on the live latest_package path. That account is not sandboxed, though. Its sudoers entitlements include passwordless sudo on /usr/bin/dpkg, /bin/chmod, /bin/systemctl, and /usr/bin/uos, and each one provides an independent, one-step path to root. Passwordless sudo dpkg is the simplest, allowing installation of a package whose maintainer script runs as root.

We took that step end-to-end on our 5.0.6 test target. We built a throwaway .deb package whose post-install script reads /etc/shadow, installed it with sudo -n dpkg -i, and recovered the file’s contents, confirming the script had run as root. This proved the chain does not stop at code execution inside a constrained service account; it lands a full root shell, reached from a single unauthenticated request, and privilege escalation here is a formality rather than a hurdle.

Detecting it Safely

A production-safe detection here must give a definitive answer without harming the target or pulling anything sensitive into scan output. Our tool does that by reaching the vulnerable code path without running an injected command.

The Probe

The check sends the gateway-bypass request to the package-update endpoint without the parameters a real injection would carry. On a vulnerable target, the request reaches the package-update handler but is rejected because of the missing parameters. That handler-specific error, returned with an HTTP 200, is proof that the bypass reached the sink, with nothing executed. A patched target rejects the request at the gateway with an HTTP 400 instead. Our detection tool also sends the same request without the bypass as a control, which should return 401, to guard against false positives on an open or misconfigured host.

This is safe in the ways that matter: no command runs on the target, no file or secret is read, and the target’s state is unchanged. When the probe does not produce a clean vulnerable-or-patched result, the tool fetches the root page and looks for UniFi OS portal markers, which lets it separate a host that is not a UniFi OS Server at all from one that is but blocked the probe. Each target lands in one of four verdicts: vulnerable, patched, unaffected (not a UniFi OS Server), or inconclusive (a UniFi OS host that needs manual review).

Running at Scale

  • Target the right port. The tool defaults to TCP 11443, the port that commonly fronts the UniFi OS Server UI; pass host:port or a URL to override it. A negative only means something if you actually reached the front end, which the unaffected vs. inconclusive verdicts are designed to make clear.
  • Sweeping many hosts. Pass targets on the command line or with -f targets.txt, and use --brief or --json for compact or machine-readable output. The tool exits non-zero if any target comes back vulnerable.

Detecting exploitation, not just exposure

The exploitation signature is clean and worth alerting on, and it is the single highest-value control here because it fires at the entry, before privilege escalation, secret theft, or log tampering can happen. That ordering matters. Once the attacker has root, they can clear the on-host logs, and one of the runnable service accounts is already configured such that its sudo actions are not logged at all. Any detection that depends on post-foothold artifacts is racing against an attacker who can erase them. The inbound request cannot be erased if you are watching for it or shipping logs off the box.

The bypass leaves a recognizable request shape: a request URI that contains the /api/auth/validate-sso/ exemption prefix together with encoded path-traversal sequences (..%2f, ..%2e, %2e%2e, and similar). A legitimate validate-sso request never carries encoded traversal, so any such request is suspect regardless of response code. Behind the bypass, watch the package-update route (.../ucs/update/latest_package) for parameters carrying shell metacharacters, and watch the host for unexpected child processes (and anomalous sudo dpkg, chmod, or systemctl calls) under the ucs-update service account.

Weaponizing It

The three parts above land a root shell on the appliance from one unauthenticated request. What decides severity is what that root shell sits on top of, and that is the rest of this section. Each step below is grounded in what we observed on the live 5.0.6 target. We note where a capability is something root simply confers versus something we exercised directly, and we are describing impact to scope it, not publishing a turnkey recipe.

What Root on This Appliance Means

A UniFi OS Server is not a generic Linux box; it is the management plane for an organization’s network, including, where those devices are deployed, its physical-access doors, surveillance cameras, and the identities tied to them. Root on the appliance is administrative control over everything the console governs. The rest of this section follows that idea outward, from the secrets on disk to the network, to the physical world, to persistence that outlives the patch.

The Secret Store

As root, every secret the appliance keeps is readable with a plain file read or database dump, no further bug required. On our test target, that set included:

  • The session-token signing key (jwt.yaml), which is the basis for the forged-session attack below.
  • The appliance’s TLS private keys, which enable passive decryption, machine-in-the-middle attacks, and impersonation of the console.
  • Cloud-access tokens for remote management, which open the multi-site pivot below.
  • The user store in the local PostgreSQL database: accounts, password material, sessions, SSO bindings, and role and scope assignments.
  • The credential server’s holdings: RADIUS secrets, WiFi credentials, VPN and WireGuard configurations, NFC card data, and facial-recognition templates for UniFi Access.
  • /etc/shadow, the local OS password hashes, for offline cracking.

Compromised data from the credential server, in particular, deserves a defender’s attention beyond “rotate it.” The RADIUS, WiFi, and VPN secrets are keys to the protected network, not just the management VLAN, so they are lateral-movement enablers. Biometric and NFC data cannot be rotated at all; once disclosed, they stay disclosed, which makes them the most consequential and least recoverable items in the set.

Sessions that Survive the Patch

The signing key turns a one-time remote code execution into durable, credential-backed access, and this is the one post-exploitation step we did prove out. Using the key recovered from the appliance, we built a session token asserting the Owner identity and authenticated to the console API with no password and no second factor. It returned the owner’s own profile, role: owner with the super-admin flags set. The owner record carries the full scope set (network management, the door and camera capabilities below, cloud access), so the forged token is a working administrative login, not a partial one.

The mechanism is what makes this durable rather than incidental. The console signs and verifies its session tokens with a symmetric HS256 secret, and the verification does not pin the algorithm, issuer, or audience. The only server-side check is a per-user revision value that is itself read from an on-disk cache, so a valid token is derivable entirely offline once the key is in hand. Crucially, that verification design is unchanged in 5.0.8. We confirmed the forged token against both a vulnerable 5.0.6 and a patched 5.0.8 console. The patch closes the remote access vector that yields a valid key, but it does not alter the token model. So any disclosure of the signing key (by this chain on an unpatched box, or by any other means) yields admin tokens that work against patched consoles too.

This is the reason patching alone is not remediation. Upgrading to 5.0.8 does nothing to a signing key an attacker already copied, and until that key is rotated, the forged admin tokens keep working across reboots, password resets, and the upgrade itself. Worse, a forged owner token can drive the console’s own SSH-enable and root-password-set workflow, which establishes an interactive root login that survives even a rotation of the JWT key. That is the step that turns “rotate the key and you are clear” into “rebuild the console,” and it is covered in the defender actions above.

The Network, the Doors, and the Cameras

From an administrative position on the console, whether by forged token or by direct database and API access, the attacker inherits the controller’s actual job. On the network side, that means pushing device configurations, rewriting firewall, routing, and DNS rules, enabling VPN ingress, creating rogue admin accounts, and re-adopting or de-authorizing managed gateways, switches, and access points. The compromise pivots from the management plane to the data plane, and the managed fleet should be treated as downstream-compromised.

Where UniFi Access and UniFi Protect are in use, the consequences leave the digital world entirely. The owner scope set includes door-unlock capabilities and the full camera capability set, so the same position that reconfigures the network can unlock physical doors, change or disable access schedules and lockdowns, enroll or clone NFC and face credentials, view live camera feeds, and disable cameras or delete recorded footage. For a vulnerability that looks on paper like a network-appliance bug, an unauthenticated attacker opening doors and erasing surveillance is the consequence that should carry the most weight for a non-specialist reader.

Cloud, Multi-site, and Just Breaking Things

Two more directions are worth stating briefly. If the console is cloud-connected, the cloud-access tokens from the secret store can pivot to the Ubiquiti cloud account and its remote-management surface, and in a multi-site organization that can reach other sites under the same account from the internet, turning a single-site LAN compromise into an organization-wide, internet-facing one. Containment that stops at the local box misses that blast radius. And because the attacker holds root, the destructive option is always on the table: wipe configurations and databases, delete backups, factory-reset, push bricking firmware, or hold the environment for ransom. That availability impact is the A:H in the vendor’s CVSS vector, and it means an attacker who is not interested in stealth can simply take the network, the doors, and the cameras offline.

The Patch

UniFi OS Server 5.0.8 closes the chain with three independent changes, one addressing each part: the gateway bypass, the command injection, and the privilege escalation:

  1. Nginx URI-normalization guard (fixes CVE-2026-34908 / 34909). The patched configuration adds a normalized-service-name mapping and compares the raw service name against the normalized one, rejecting the divergence with HTTP 400. The patch comment names the bug class directly, citing “path traversal attacks where the raw and normalized service names diverge.” It also broadens the service-name character class, so the comparison is robust. This removes the gateway bypass by making the gate and the router agree on what the request is.
  2. Go input validation (fixes CVE-2026-34910 / 33000). The package-update backend gains a package-name allowlist and a name-validation check, and the sink is rewritten to execute via an argument-array helper with no shell. Removing the sh -c wrapper means shell metacharacters in any input are no longer interpreted, and the allowlist rejects unexpected names before they ever reach execution.
  3. sudoers hardening (defense in depth). The ucs-update sudoers entry drops /usr/bin/dpkg and /bin/chmod from its passwordless list. This does not fix either code-level bug, but it removes the most obvious root primitive that turned the foothold into root, shrinking the blast radius of command execution in this subsystem. It is a partial hardening rather than a complete one. A second runnable service account retains a root-capable passwordless grant that this change does not touch, so the over-privileged service account weakness is reduced but not eliminated. That residual grant is a defense-in-depth gap worth raising with the vendor and mitigating locally, but it is not a viable exploit path on a patched build, because the initial access vector is closed.

We confirmed each change by diffing the 5.0.6 and 5.0.8 artifacts, and we confirmed behaviorally that the patched build returns HTTP 400 (with no command execution and no delay) for the request that executed on 5.0.6.

One limit of the patch is worth stating plainly, because it changes incident response. The fix closes the way in, but it does not reach back and undo what an attacker already did with root on an instance that was exposed beforehand. In particular, it does not invalidate a session-token signing key that was already exfiltrated, so forged admin tokens minted from that key keep working against a fully patched console until the key is rotated. Patching is the first step, not the last.

Conclusion

This chain earns its 10.0 severity score from how little it asks and how much it returns: with no credentials and no user interaction, a single request to a reachable UniFi OS Server becomes a root shell, which we validated end to end on version 5.0.6 and confirmed closed on 5.0.8.

What makes that critical is what the appliance is. As the management plane for the network and (where physical security devices are deployed) the doors and cameras behind it, root access means administrative control of everything the console governs, from the secrets on disk to the physical world.

Patch to 5.0.8, or the fixed version for your hardware model, but treat that as the first move rather than the whole response. It closes the entry without undoing what an attacker with root already did. Any instance exposed pre-patch should be treated as compromised, its secrets rotated and its network, door, and camera activity reviewed.

Cosmos customers were notified about our research into this vulnerability shortly after the vendor advisory. If you are interested in learning more about managed services delivered through our Cosmos platform, visit bishopfox.com/services/cosmos.

Our detection tool is available on GitHub: https://github.com/BishopFox/CVE-2026-34908-check. For more vulnerability intelligence insights, visit the Bishop Fox Blog.


文章来源: https://bishopfox.com/blog/popping-root-on-unifi-os-server-unauthenticated-rce-chain-detection-analysis
如有侵权请联系:admin#unsafe.sh