# ZAC Node Server /sso Endpoint Allowing Unauthenticated Admin Session-Token Theft

**URL:** <https://openziti.discourse.group/t/zac-node-server-sso-endpoint-allowing-unauthenticated-admin-session-token-theft/6143>\
**Category:** Security Adivsories\
**Created:** [September 29, 2026, 7:44pm UTC](https://openziti.discourse.group/t/zac-node-server-sso-endpoint-allowing-unauthenticated-admin-session-token-theft/6143 "2026-09-29T19:44:47Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![rgalletto](https://yyz2.discourse-cdn.com/free1/user_avatar/openziti.discourse.group/rgalletto/32/1508_2.png) [@rgalletto](https://openziti.discourse.group/u/rgalletto)\
**Post date:** [September 29, 2026, 7:44pm UTC](https://openziti.discourse.group/t/zac-node-server-sso-endpoint-allowing-unauthenticated-admin-session-token-theft/6143/1 "2026-09-29T19:44:47Z")

</div>

## Summary

The ZAC Node backend exposes an unauthenticated `GET /sso` endpoint that creates a fully authenticated ZAC session from query parameters, with no allowlist check on the target controller. Because the Node server holds the controller connection in process-global variables shared across all users, an unauthenticated attacker can both log in and redirect every user's upstream controller URL — causing a logged-in administrator's controller session token to be forwarded to the attacker.

## Affected Component

- **Package:** `openziti/ziti-console` (Ziti Admin Console) — Node backend
- **File:** `server.js` — the `/sso` handler
- **Root of the cross-user effect:** the process-global `baseUrl` / `serviceUrl` connection state

## Vulnerability Class

- **Primary:** CWE-306 — Missing Authentication for Critical Function
- **Secondary:** CWE-384 — Session Fixation; CWE-441 — Unintended Proxy or Intermediary ('Confused Deputy')

## Attacker Model

An unauthenticated remote attacker with network access to the ZAC Node server. No credentials are required to create a session or to redirect the shared controller URL; the token-theft escalation additionally requires a logged-in administrator to make a request while the upstream is redirected.

## Preconditions

ZAC running via the Node backend (`server.js`) in `node-api` or `classic` mode (the default container entrypoint). Deployments that serve only the static Angular application are **not** affected.

## Root Cause

`GET /sso` creates an authenticated ZAC session from query parameters and, unlike `POST /api/login`, performs no allowlist check on the target controller. Because the Node server stores the controller connection in process-global variables (`baseUrl` / `serviceUrl`) shared across all users, whoever calls `/sso` last controls where every user's authenticated requests are sent.

## Impact

An unauthenticated remote attacker can (1) obtain a logged-in ZAC session, and (2) redirect the upstream controller URL to a host they control. On the next request from any logged-in administrator, ZAC forwards that admin's controller session token to the attacker's host. This can result in compromise of admin sessions and grant an attacker access to the management plane.

- **CVSS v3.1:** 9.0 (Critical)
- **Vector:** `AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H`

**Not impacted:** static-only (SPA) deployments that never run `server.js`.

## Package

`openziti/ziti-console` (app-ziti-console, Node backend / `node-api` mode)

## Affected Versions

- app-ziti-console `>= 2.6.4, < 4.5.3`

## Patched Versions

- app-ziti-console `4.5.3`

## Remediation

Upgrade to 4.5.3, which removes the unused `/sso` handler; the data-proxy endpoints were additionally hardened to resolve the upstream controller from the server-side allowlist rather than shared global state. If you cannot upgrade, block `GET /sso` at a reverse proxy, or run ZAC in static-asset (SPA) mode instead of the Node proxy.

## References

- GHSA: [https://github.com/openziti/ziti-console/security/advisories/GHSA-p7rg-vcfw-3gx7](https://github.com/openziti/ziti-console/security/advisories/GHSA-p7rg-vcfw-3gx7)
- CVE: (pending)

## Credits

Reported by dmuensterer and sajdakabir.

---

<div class="post-metadata">

**Author:** ![frm](https://avatars.discourse-cdn.com/v4/letter/f/b9bd4f/32.png) [@frm](https://openziti.discourse.group/u/frm)\
**Post date:** [September 30, 2026, 5:13am UTC](https://openziti.discourse.group/t/zac-node-server-sso-endpoint-allowing-unauthenticated-admin-session-token-theft/6143/2 "2026-09-30T05:13:41Z")

</div>

> [@rgalletto](#):
>
> or run ZAC in static-asset (SPA) mode instead of the Node proxy.

Can you please share some example configuration snippets?

---

<div class="post-metadata">

**Author:** ![TheLumberjack](https://yyz2.discourse-cdn.com/free1/user_avatar/openziti.discourse.group/thelumberjack/32/113_2.png) [@TheLumberjack](https://openziti.discourse.group/u/TheLumberjack)\
**Post date:** [September 30, 2026, 1:20pm UTC](https://openziti.discourse.group/t/zac-node-server-sso-endpoint-allowing-unauthenticated-admin-session-token-theft/6143/3 "2026-09-30T13:20:39Z")

</div>

@frm you mean to use ZAC hosted by the controller? "in static-asset (SPA) mode" as Ryan put it.

With 2.1.x you can run `ziti run quickstart --zac` which will bring zac down for you. When you look at the controller config file you'll see this entry under the web bindings:

```auto
      - binding: spa
        options:
          path: zac
          location: C:/Users/clint/AppData/Local/Temp/quickstart3731527080/console
          indexFile: index.html

```

So get your ZAC, unzip it somewhere and add that entry.and you'll have access to zac at `/zac` on your controller:

 ![image](https://global.discourse-cdn.com/free1/uploads/netfoundry/original/2X/2/2955f567834de0dbf40d7ec28036d0883ae62f5c.png)

(if that's what you meant)
