# REPRO-2026-00237: OpenZiti privilege escalation via overpermissive enrollment creation ## Summary Status: published Severity: high Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00237 CVE: CVE-2026-58165 ## Package Name: Unknown Ecosystem: Unknown Affected: Unknown Fixed: Unknown ## Root Cause # CVE-2026-58165 Root Cause Analysis ## Summary OpenZiti through 2.0.0 allows an authenticated non-admin identity that has been granted fine-grained enrollment-management permissions to create, read, refresh, and delete OTT enrollments for any identity, including identities with the `isAdmin` flag. Because an enrollment mints the one-time token (and JWT) used to authenticate as the target identity, a non-admin with `enrollment` permission can create an enrollment for an admin identity, redeem it through the unauthenticated client `/enroll` endpoint, and obtain a client certificate that authenticates as the targeted admin. This yields full administrative control over the controller and the zero-trust overlay it manages. ## Impact - **Product/component:** OpenZiti controller (`ziti-controller`), specifically the enrollment management REST API (`/edge/management/v1/enrollments`). - **Affected versions:** OpenZiti through 2.0.0 (vulnerable commit `c2e6c8ee3`). - **Fixed version:** Commit `3027fdffd` ("Prevent enrollment-based privilege escalation to admin identities. Fixes #4010"). - **Risk level:** High. Any authenticated non-admin user with the `enrollment` permission can escalate to an arbitrary admin identity, including the default administrator, bypassing all role-based access controls. - **Consequences:** Full administrative compromise of the controller, including ability to create/modify identities, services, policies, routers, and CA configurations. ## Impact Parity - **Disclosed/claimed maximum impact:** Privilege escalation from a low-privileged identity to an admin identity via enrollment creation. - **Reproduced impact from this run:** Confirmed. A non-admin identity with only `enrollment` permission successfully created an OTT enrollment for a newly created admin identity, redeemed the enrollment, and used the resulting certificate to perform an admin-only operation (creating another identity through the management API). The same workflow on the fixed commit is rejected with HTTP 401 Unauthorized. - **Parity:** `full`. - **Not demonstrated:** N/A. The reproduction reaches the claimed administrative impact end-to-end. ## Root Cause The enrollment creation, read, refresh, and delete handlers in `controller/internal/routes/enrollment_router.go` only verified that the caller had the `enrollment` entity permission and that the target identity existed. They did not check whether the target identity was an administrator or whether the caller was authorized to manage enrollments on behalf of an admin identity. Specifically, `EnrollmentRouter.Create` called `MapCreate` without any guard against admin-targeted enrollments. The fix (commit `3027fdffd`) adds two helpers: - `allowNonAdminEnrollmentForIdentity` — returns an unauthorized error when a non-admin tries to create an enrollment targeting an admin identity. - `allowNonAdminAccessToEnrollment` — returns an unauthorized error when a non-admin tries to read, refresh, or delete an enrollment that belongs to an admin identity. The list handler is also modified to filter out admin-identity enrollments for non-admin callers by adding `not (identity.isAdmin = true)` to the query predicate. ## Reproduction Steps The reproduction is fully automated in `bundle/repro/reproduction_steps.sh`. The script performs the following steps: 1. Reads `bundle/project_cache_context.json` and uses the prepared project cache directory (`/data/pruva/project-cache/92b8f1e5-395e-41fd-8f92-ae0d5f2888dc`). 2. Installs Go 1.26.4 into the project cache if it is not already present. 3. Clones (or reuses) the `openziti/ziti` repository at `/repo`. 4. Resolves the vulnerable commit (`3027fdffd^`) and the fixed commit (`3027fdffd`). 5. Verifies that the fixed commit contains the patch to `controller/internal/routes/enrollment_router.go` and that the vulnerable commit does not. 6. Copies `bundle/repro/cve_2026_58165_repro_test.go` into the repo's `tests/` directory at runtime. 7. Runs the Go integration test `Test_CVE202658165_AdminEnrollmentEscalation` against the vulnerable commit, capturing the result to `bundle/repro/vulnerable_result.json`. 8. Runs the same test against the fixed commit, capturing the result to `bundle/repro/fixed_result.json`. 9. Compares the two results and writes `bundle/repro/runtime_manifest.json`. **Expected evidence of reproduction:** - `bundle/repro/vulnerable_result.json` shows `"vulnerable": true`, `"create_status_code": 201`, and a non-empty `created_identity_id` proving an admin-only operation succeeded. - `bundle/repro/fixed_result.json` shows `"fixed": true`, `"create_status_code": 401`, and the response body contains `"non-admins may not manage enrollments for admin identities"`. ## Evidence - `bundle/logs/reproduction_steps.log` — full script output with commit resolution, test execution, and summary. - `bundle/logs/vulnerable_test.log` — Go test output for the vulnerable commit. - `bundle/logs/fixed_test.log` — Go test output for the fixed commit. - `bundle/repro/vulnerable_result.json` — captured result proving the escalation succeeded. - `bundle/repro/fixed_result.json` — captured result proving the escalation is blocked. - `bundle/repro/runtime_manifest.json` — runtime evidence manifest describing the API surface and artifacts. Key excerpt from the vulnerable result: ```json { "vulnerable": true, "create_status_code": 201, "created_identity_id": "...", "notes": "non-admin created an enrollment for an admin identity, redeemed it, and performed an admin-only operation" } ``` Key excerpt from the fixed result: ```json { "fixed": true, "create_status_code": 401, "create_response_body": "{\"error\":{\"cause\":{\"code\":\"UNHANDLED\",\"message\":\"non-admins may not manage enrollments for admin identities\"},\"code\":\"UNAUTHORIZED\",...}}", "notes": "non-admin create enrollment for admin identity returned 401 (fixed behavior)" } ``` Environment captured during the run: - Go 1.26.4 (installed from `https://go.dev/dl/go1.26.4.linux-amd64.tar.gz`) - Repository: `github.com/openziti/ziti` - Vulnerable commit: `c2e6c8ee3da42321ef22442c8d25d0413caf4a4f` - Fixed commit: `3027fdffd3e57884487b7c46e5e669cfbc8becdf` ## Recommendations / Next Steps - **Upgrade:** Upgrade the OpenZiti controller to a build that includes commit `3027fdffd` or later. - **Temporary mitigation:** Remove the `enrollment` permission from any non-admin identities or role attributes until the patch is deployed. - **Testing:** Add the regression test `tests/permissions_enrollment_test.go::Test_Permissions_Enrollment_AdminIdentityEscalation` (introduced in the fix) to CI to prevent future regressions. - **Audit:** Review other entity-level permissions for similar authorization gaps where a non-admin can manage resources that belong to admin identities. ## Additional Notes - The reproduction script was executed twice consecutively and produced the same vulnerable/fixed divergence both times, confirming idempotency. - The Go test harness starts a real OpenZiti controller using the checked-out source code, so the reproduction exercises the actual product code path rather than a mock. - The custom test file is copied into the repo at runtime and removed after each run to avoid permanently altering the source checkout. - Build outputs are cached in the durable project cache, so subsequent runs reuse the already-installed Go toolchain and already-cloned repository. ## Reproduction Details Reproduced: 2026-07-06T08:21:14.683Z Duration: 1359 seconds Tool calls: 211 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00237 pruva-verify CVE-2026-58165 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00237&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00237/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-58165 - Source: https://github.com/openziti/ziti/issues/4010 ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 5699 bytes) - bundle/repro/rca_report.md (analysis, 7491 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 6461 bytes) - bundle/vuln_variant/rca_report.md (analysis, 9916 bytes) - bundle/artifact_promotion_manifest.json (other, 8005 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 1307 bytes) - bundle/repro/vulnerable_result.json (other, 476 bytes) - bundle/repro/fixed_result.json (other, 644 bytes) - bundle/logs/reproduction_steps.log (log, 5110 bytes) - bundle/repro/runtime_manifest.json (other, 623 bytes) - bundle/repro/validation_verdict.json (other, 847 bytes) - bundle/logs/vulnerable_test.log (log, 110 bytes) - bundle/logs/fixed_test.log (log, 110 bytes) - bundle/vuln_variant/variant_manifest.json (other, 3339 bytes) - bundle/vuln_variant/validation_verdict.json (other, 1737 bytes) - bundle/vuln_variant/vulnerable_result.json (other, 740 bytes) - bundle/vuln_variant/latest_result.json (other, 712 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 7079 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 777 bytes) - bundle/vuln_variant/original_fix_result.json (other, 713 bytes) - bundle/logs/vuln_variant/reproduction_steps.log (log, 20871 bytes) - bundle/logs/vuln_variant/vulnerable_test.log (log, 110 bytes) - bundle/logs/vuln_variant/original_fix_test.log (log, 110 bytes) - bundle/logs/vuln_variant/latest_test.log (log, 110 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00237 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00237/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00237 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev