CVE-2026-58165: Verified Repro With Script Download
CVE-2026-58165: OpenZiti privilege escalation via overpermissive enrollment creation
CVE-2026-58165 is verified against the affected target. Vulnerability class: Privilege Escalation. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00237.
What Is CVE-2026-58165?
CVE-2026-58165 is a high-severity privilege escalation vulnerability in the OpenZiti controller (through version 2.0.0) where an authenticated non-admin identity with fine-grained enrollment-management permissions can create enrollments for any identity, including the default admin, and use them to obtain admin access. Pruva reproduced it (reproduction REPRO-2026-00237).
CVE-2026-58165 Severity & CVSS Score
CVE-2026-58165 is rated high severity, with a CVSS base score of 8.8 out of 10.
High — serious impact or readily exploitable. Prioritize remediation.
How to Reproduce CVE-2026-58165
pruva-verify REPRO-2026-00237 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00237/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-58165
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
non-admin management API session with enrollment permission; POST /enrollments body targeting an admin identityId
- OpenZiti controller management API /edge/management/v1/enrollments
- Create
- unauthenticated /enroll redeem
- admin management API session
reproduction_steps.sh Alternative enrollment types (OTT-CA, UPDB) and CRUD/list paths for admin enrollments are blocked by the CVE-2026-58165 fix; no bypass found.
How the agent worked
Root Cause and Exploit Chain for CVE-2026-58165
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.
- 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
enrollmentpermission 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
enrollmentpermission 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:
- Reads
bundle/project_cache_context.jsonand uses the prepared project cache directory (/data/pruva/project-cache/92b8f1e5-395e-41fd-8f92-ae0d5f2888dc). - Installs Go 1.26.4 into the project cache if it is not already present.
- Clones (or reuses) the
openziti/zitirepository at<project_cache_dir>/repo. - Resolves the vulnerable commit (
3027fdffd^) and the fixed commit (3027fdffd). - Verifies that the fixed commit contains the patch to
controller/internal/routes/enrollment_router.goand that the vulnerable commit does not. - Copies
bundle/repro/cve_2026_58165_repro_test.gointo the repo'stests/directory at runtime. - Runs the Go integration test
Test_CVE202658165_AdminEnrollmentEscalationagainst the vulnerable commit, capturing the result tobundle/repro/vulnerable_result.json. - Runs the same test against the fixed commit, capturing the result to
bundle/repro/fixed_result.json. - Compares the two results and writes
bundle/repro/runtime_manifest.json.
Expected evidence of reproduction:
bundle/repro/vulnerable_result.jsonshows"vulnerable": true,"create_status_code": 201, and a non-emptycreated_identity_idproving an admin-only operation succeeded.bundle/repro/fixed_result.jsonshows"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:
{
"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:
{
"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
3027fdffdor later. - Temporary mitigation: Remove the
enrollmentpermission 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.
Variant Analysis & Alternative Triggers for CVE-2026-58165
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.Createincontroller/internal/routes/enrollment_router.go. - The only other enrollment-creating path (
IdentityRouter.Createwith 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:
- OTT-CA enrollment creation (
POST /edge/management/v1/enrollmentswithmethod: ottca) — targets the same sink as the original OTT reproduction but through a different enrollment method. - UPDB enrollment creation (
POST /edge/management/v1/enrollmentswithmethod: updb) — another enrollment method variant. - List query injection (
GET /edge/management/v1/enrollmentswith customfilterqueries such astrue,identity.isAdmin = true,not (identity.isAdmin = true),identity.isAdmin = false) — attempts to bypass the server-sidenot (identity.isAdmin = true)filter. - Detail/Refresh/Delete (
GET/POST/DELETE /edge/management/v1/enrollments/{id}) on an admin-owned enrollment — tests whether the per-enrollment guard is effective. - Residual token redemption (
POST /enrollwith 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.
- 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 commit3027fdffdonward; strengthened from commit5fb197935onward. - Risk level: High on vulnerable versions. The same privilege-escalation impact (non-admin with
enrollmentpermission 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→checkNonAdminEnrollmentForIdentityDetail/Delete/Refresh→checkNonAdminAccessToEnrollmentList→ query predicatenot (identity.isAdmin = true)
Reproduction Steps
The reproduction is automated in bundle/vuln_variant/reproduction_steps.sh. It performs the following steps:
- Uses the prepared project cache (
/data/pruva/project-cache/92b8f1e5-395e-41fd-8f92-ae0d5f2888dc). - Installs Go 1.26.4 if not already present.
- Clones or reuses the
openziti/zitirepository. - Resolves the vulnerable commit (
c2e6c8ee3), the original fix commit (3027fdffd), and the latest main commit (45b5046f5). - Copies
bundle/vuln_variant/cve_2026_58165_variant_test.gointo the repo'stests/directory at runtime. - Runs
Test_CVE202658165_Variantsagainst each of the three commits, capturing JSON results tobundle/vuln_variant/{vulnerable,original_fix,latest}_result.json. - Restores the repo checkout to the original fixed commit (
3027fdffd) used by the repro stage. - Compares results and writes
bundle/vuln_variant/runtime_manifest.json.
Expected evidence:
vulnerable_result.json:"vulnerable": truewith OTT-CA/UPDB create statuses of 201, list leaktrue, and detail/refresh/delete statuses of 200.original_fix_result.jsonandlatest_result.json:"fixed": truewith all admin-targeted create/read/refresh/delete paths returning 401 and list leakfalse.
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:
{
"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:
{
"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_AdminIdentityEscalationcovers 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
/enrollredemption test returned 400/404 and is considered expected behavior, not a vulnerability.
CVE-2026-58165 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
Artifacts and Evidence for CVE-2026-58165
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-58165
FAQ: CVE-2026-58165
How does the CVE-2026-58165 privilege escalation work?
How severe is CVE-2026-58165?
How can I reproduce CVE-2026-58165?
References for CVE-2026-58165
Authoritative sources for CVE-2026-58165 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.