# CVE-2026-58165 Variant Root Cause Analysis

## Summary

This variant analysis tested whether the privilege escalation described in CVE-2026-58165 can be reached through materially different entry points or data paths than the original reproduction (OTT enrollment creation for an admin identity). The tested variants are: OTT-CA enrollment creation, UPDB enrollment creation, list-query injection to leak admin enrollments, direct detail/refresh/delete of an admin-owned enrollment, and redemption of a pre-existing admin enrollment token. None of these paths bypass the original fix (`3027fdffd`) or the latest main branch (`45b5046f5`). The vulnerable commit (`c2e6c8ee3`) still allows every tested variant. This report therefore concludes with a negative bypass verdict but provides a concrete candidate matrix and recommends the hardening already applied in follow-up commit `5fb197935`.

## Fix Coverage / Assumptions

The original fix enforces a single invariant: **a non-admin caller must not be able to create, read, refresh, delete, or list enrollments that target an admin identity.** The implementation assumes:

- All identity-targeted enrollment creation goes through `EnrollmentRouter.Create` in `controller/internal/routes/enrollment_router.go`.
- The only other enrollment-creating path (`IdentityRouter.Create` with bundled enrollments`) is already blocked by the identity router for admin identities.
- The target identity's admin status is reliably available via `ae.Managers.Identity.Read(identityId)`.
- A non-admin cannot hold `permissions.AdminPermission`.

The follow-up commit `5fb197935` removes the assumption that read errors can be safely ignored: it now denies access when `Identity.Read` or `Enrollment.Read` returns an error instead of treating the error as "not admin / not found."

## Variant / Alternate Trigger

The variant harness exercises the following distinct paths:

1. **OTT-CA enrollment creation** (`POST /edge/management/v1/enrollments` with `method: ottca`) — targets the same sink as the original OTT reproduction but through a different enrollment method.
2. **UPDB enrollment creation** (`POST /edge/management/v1/enrollments` with `method: updb`) — another enrollment method variant.
3. **List query injection** (`GET /edge/management/v1/enrollments` with custom `filter` queries such as `true`, `identity.isAdmin = true`, `not (identity.isAdmin = true)`, `identity.isAdmin = false`) — attempts to bypass the server-side `not (identity.isAdmin = true)` filter.
4. **Detail/Refresh/Delete** (`GET/POST/DELETE /edge/management/v1/enrollments/{id}`) on an admin-owned enrollment — tests whether the per-enrollment guard is effective.
5. **Residual token redemption** (`POST /enroll` with a valid admin enrollment token) — tests whether the unauthenticated redeem endpoint still accepts an admin enrollment token created by an admin.

All paths reach the same underlying authorization gap: in the vulnerable commit, the enrollment management API does not check whether the target identity is an admin before minting or exposing an enrollment token.

## Impact

- **Product/component:** OpenZiti controller (`ziti-controller`), enrollment management REST API (`/edge/management/v1/enrollments`).
- **Affected versions:** OpenZiti through commit `c2e6c8ee3` (the vulnerable commit tested). Fixed from commit `3027fdffd` onward; strengthened from commit `5fb197935` onward.
- **Risk level:** High on vulnerable versions. The same privilege-escalation impact (non-admin with `enrollment` permission becoming an admin) is reachable through all tested enrollment creation methods and the list/detail/refresh/delete paths leak or mutate admin enrollment tokens.
- **Consequences:** Full administrative compromise of the controller if any variant succeeds.

## Impact Parity

- **Disclosed/claimed maximum impact:** Privilege escalation from a non-admin identity with enrollment permission to an admin identity via enrollment creation.
- **Reproduced impact from this variant run:** The variant harness confirmed that the same escalation is reachable through OTT-CA and UPDB enrollment creation on the vulnerable commit, and that list/detail/refresh/delete of admin enrollments are also unguarded. The actual token redemption step was not successfully exercised in the harness (returned 400/404), so end-to-end admin takeover via the variant methods was not fully demonstrated.
- **Parity:** `partial` — the authorization gap was reproduced across multiple methods and CRUD paths, but the final redemption-to-admin-cert step was not completed for the variant methods.
- **Not demonstrated:** Complete end-to-end admin takeover using the OTT-CA or UPDB variant methods; the original OTT reproduction already demonstrated that end-to-end path.

## Root Cause

The root cause is the same as the parent CVE: the enrollment management API treats the ability to manage enrollments as permission to manage enrollments for **any** identity, including admin identities. Because an enrollment token mints a credential that authenticates as the target identity, any non-admin with the `enrollment` permission can effectively become any admin.

The fix adds an admin-target guard in `EnrollmentRouter` before the create/read/refresh/delete/list handlers. The follow-up commit `5fb197935` hardens the guard by making error responses from `Identity.Read` / `Enrollment.Read` deny access rather than falling through. The relevant code paths are:

- `controller/internal/routes/enrollment_router.go`:
  - `Create` → `checkNonAdminEnrollmentForIdentity`
  - `Detail` / `Delete` / `Refresh` → `checkNonAdminAccessToEnrollment`
  - `List` → query predicate `not (identity.isAdmin = true)`

## Reproduction Steps

The reproduction is automated in `bundle/vuln_variant/reproduction_steps.sh`. It performs the following steps:

1. Uses the prepared project cache (`/data/pruva/project-cache/92b8f1e5-395e-41fd-8f92-ae0d5f2888dc`).
2. Installs Go 1.26.4 if not already present.
3. Clones or reuses the `openziti/ziti` repository.
4. Resolves the vulnerable commit (`c2e6c8ee3`), the original fix commit (`3027fdffd`), and the latest main commit (`45b5046f5`).
5. Copies `bundle/vuln_variant/cve_2026_58165_variant_test.go` into the repo's `tests/` directory at runtime.
6. Runs `Test_CVE202658165_Variants` against each of the three commits, capturing JSON results to `bundle/vuln_variant/{vulnerable,original_fix,latest}_result.json`.
7. Restores the repo checkout to the original fixed commit (`3027fdffd`) used by the repro stage.
8. Compares results and writes `bundle/vuln_variant/runtime_manifest.json`.

**Expected evidence:**
- `vulnerable_result.json`: `"vulnerable": true` with OTT-CA/UPDB create statuses of 201, list leak `true`, and detail/refresh/delete statuses of 200.
- `original_fix_result.json` and `latest_result.json`: `"fixed": true` with all admin-targeted create/read/refresh/delete paths returning 401 and list leak `false`.

## Evidence

- `bundle/logs/vuln_variant/reproduction_steps.log` — full script output with commit resolution and summary.
- `bundle/logs/vuln_variant/{vulnerable,original_fix,latest}_test.log` — Go test logs for each commit.
- `bundle/vuln_variant/vulnerable_result.json` — proves variants succeed on the vulnerable commit.
- `bundle/vuln_variant/original_fix_result.json` — proves variants are blocked by the original patch.
- `bundle/vuln_variant/latest_result.json` — proves variants remain blocked in latest main.
- `bundle/vuln_variant/runtime_manifest.json` — runtime evidence manifest.

Key excerpts from `vulnerable_result.json`:

```json
{
  "variant_ott_ca_status": 201,
  "variant_updb_status": 201,
  "variant_list_leak_leaked": true,
  "variant_detail_status": 200,
  "variant_refresh_status": 200,
  "variant_delete_status": 200,
  "vulnerable": true
}
```

Key excerpts from `latest_result.json`:

```json
{
  "variant_ott_ca_status": 401,
  "variant_updb_status": 401,
  "variant_list_leak_leaked": false,
  "variant_detail_status": 401,
  "variant_refresh_status": 401,
  "variant_delete_status": 401,
  "fixed": true
}
```

Environment:
- Go 1.26.4
- Repository: `github.com/openziti/ziti`
- Vulnerable commit: `c2e6c8ee3da42321ef22442c8d25d0413caf4a4f`
- Original fix: `3027fdffd3e57884487b7c46e5e669cfbc8becdf`
- Latest main: `45b5046f5259e685d7d99e54564c7e38eec9f1f7`

## Recommendations / Next Steps

- **Upgrade to latest main or a release containing `5fb197935`:** The original fix (`3027fdffd`) is functionally complete for the tested attack surface, but the follow-up commit removes the theoretical error-handling gap. Operators should deploy a build that includes the strengthened guard.
- **Audit error-handling in authorization helpers:** Apply the same "deny on read error" pattern to any other entity-level permission checks that may silently ignore read errors.
- **Extend regression tests:** The existing `Test_Permissions_Enrollment_AdminIdentityEscalation` covers OTT. Consider adding explicit OTT-CA and UPDB cases to the regression suite to prevent method-specific regressions.
- **Clarify redeem-endpoint semantics:** Document that redemption of an existing admin enrollment token is expected behavior for anyone holding the token, and that the security boundary is the management API's creation/exposure of such tokens.

## Additional Notes

- The variant script was executed twice and produced the same vulnerable/fixed divergence both times, confirming idempotency.
- The repo checkout was restored to the original fixed commit (`3027fdffd`) after the variant runs to avoid altering the state used by the repro stage.
- The variant test file is copied into the repo at runtime and removed after each run.
- No concrete bypass of the fixed code was demonstrated. The residual `/enroll` redemption test returned 400/404 and is considered expected behavior, not a vulnerability.
