nginx ignition has Unauthenticated Admin Account Creation via Onboarding Race Condition
CVE-2026-61628 (GitHub advisory GHSA-pxcx-fv34-x9p5) is a high-severity vulnerability with a CVSS 3.1 base score of 8.1, classed as Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition') (CWE-362), in github.com/lucasdillmann/nginx-ignition (Go). Affected versions: < 0.0.0-20260621194639-0586b4e55ab. It is fixed in 0.0.0-20260621194639-0586b4e55ab.
Published 21 Sept 2026Updated 21 Sept 2026
- CVE ID
- CVE-2026-61628
- GHSA ID
- GHSA-pxcx-fv34-x9p5
- EPSS
- Not scored
- Weakness (CWE)
- CWE-362Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')
- KEV status
- Not in KEV
- Published
- 21 Sept 2026
- Updated
- 21 Sept 2026
- Ecosystem
- Go
- Assigned by (CNA)
- GitHub_M
Summary
Source: OSV (GitHub advisory)
POST /api/users/onboarding/finish is registered as anonymous (unauthenticated) and creates a user with full ReadWrite admin permissions. Because the handler uses a check-then-act (TOCTOU) pattern between the "onboarding already completed?" check and the user-creation write, with no atomic guard, a remote unauthenticated attacker who can reach an instance in its pre-onboarding state can create an administrator account for themselves — and concurrent requests can create multiple admin accounts in a single race.
Affected component
- Endpoint:
POST /api/users/onboarding/finish - Route registration:
api/user/routes.go:49→authorizer.AllowAnonymous(http.MethodPost, "/api/users/onboarding/finish") - Handler:
api/user/onboarding_finish_handler.go
Technical details
The route is explicitly allowed without authentication:
// api/user/routes.go:48-49
authorizer.AllowAnonymous(http.MethodGet, "/api/users/onboarding/status")
authorizer.AllowAnonymous(http.MethodPost, "/api/users/onboarding/finish")
The handler reads the onboarding state, returns 403 if already finished, and otherwise creates a user with every permission set to ReadWrite:
// api/user/onboarding_finish_handler.go
func (h onboardingFinishHandler) handle(ctx *gin.Context) {
alreadyFinished, err := h.commands.OnboardingCompleted(ctx.Request.Context()) // (1) CHECK
if err != nil { panic(err) }
if alreadyFinished {
ctx.Status(http.StatusForbidden)
return
}
requestPayload := &userRequestDTO{}
if err = ctx.BindJSON(requestPayload); err != nil { panic(err) }
domainModel := converter.Wrap(ctx.Request.Context(), toDomain, requestPayload)
domainModel.ID = uuid.New()
domainModel.Enabled = true
domainModel.Permissions = user.Permissions{ // full admin
Hosts: user.ReadWriteAccessLevel,
Streams: user.ReadWriteAccessLevel,
Certificates: user.ReadWriteAccessLevel,
Integrations: user.ReadWriteAccessLevel,
AccessLists: user.ReadWriteAccessLevel,
Settings: user.ReadWriteAccessLevel,
Users: user.ReadWriteAccessLevel,
NginxServer: user.ReadWriteAccessLevel,
Caches: user.ReadWriteAccessLevel,
// ...all remaining permissions ReadWrite/ReadOnly
}
if err = h.commands.Save(ctx.Request.Context(), domainModel, nil); err != nil { // (2) ACT
panic(err)
}
// ...authenticates and returns a JWT for the new admin
}
The gap between (1) OnboardingCompleted() and (2) Save() is not protected by a lock, transaction, or unique constraint. Two or more requests can each pass the alreadyFinished == false check before any of them commits, so every racing request proceeds to create an admin user and receive a valid admin JWT.
Preconditions (stated honestly)
This is exploitable when the instance is in a pre-onboarding state:
- Fresh deployment — the time window between the service coming online and the legitimate operator completing onboarding. During this window any unauthenticated party who can reach the instance can register the first/an additional admin. The race lets an attacker slip an admin account in alongside the operator's, so the operator's onboarding appears to succeed normally while the attacker silently holds admin.
- State reset — if onboarding state can return to "not completed" (e.g. all users removed), the endpoint reopens and becomes a repeatable unauthenticated admin-creation primitive.
The single-request path is a setup-window exposure; the race is what turns "first legitimate admin" into "attacker also gets admin," and what allows multiple admin accounts to be minted from one burst.
Proof of concept
Against an instance that has not yet completed onboarding:
# Fire concurrent onboarding-finish requests; multiple admin accounts are created,
# each returning a valid admin JWT, despite the single-admin intent.
for i in $(seq 1 20); do
curl -s -X POST http://TARGET/api/users/onboarding/finish \
-H 'Content-Type: application/json' \
-d '{"username":"attacker'"$i"'","password":"P@ssw0rd123!"}' \
-o /dev/null -w "%{http_code}\n" &
done
wait
# Multiple 200 responses (each with a login token) instead of exactly one 200 + N×403.
Each 200 response body contains a userLoginResponseDTO with a JWT granting full admin access (Hosts/Streams/Certificates/Settings/Users/NginxServer/AccessLists/Caches = ReadWrite). The attacker then has complete control of the nginx-ignition instance and the nginx server it manages.
Impact
- Unauthenticated administrative account takeover of a fresh (or reset) instance.
- Full admin enables every downstream capability: creating hosts/routes, editing global and per-route nginx configuration, managing access lists and certificates, and controlling the nginx server process. (The config surface is itself injectable — see the related nginx-configuration-injection issues — so admin here is a path to SSRF / arbitrary nginx directives.)
- The TOCTOU race additionally allows minting multiple admin accounts from a single concurrent burst, aiding persistence/stealth.
Remediation
- Make onboarding completion atomic: enforce a database-level unique constraint (e.g. "at most one onboarding user" / single-row guard) so concurrent creates collide, or wrap the check-and-create in a single transaction / mutex.
- Re-check
OnboardingCompleted()inside the same transaction that performs the insert, and abort on conflict. - Consider requiring a one-time setup token (printed to server logs / env at first boot) for the initial admin creation, eliminating the unauthenticated window entirely.
Finding ID: GM-4607
Technical details
Source: GitHub Advisory Database
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:HHigh8.1Base score from GitHub Advisory Database
- Attack Vector AV
- Network
- Attack Complexity AC
- High
- Privileges Required PR
- None
- User Interaction UI
- None
- Scope S
- Unchanged
- Confidentiality C
- High
- Integrity I
- High
- Availability A
- High
CISA SSVC decision points
- Exploitation
- poc
- Proof-of-concept code is public
- Automatable
- no
- Needs manual steps per target
- Technical impact
- total
- Full control of the affected system
CISA Coordinator assessment (SSVC 2.0.3) via CVE.org.
Affected packages
Source: OSV (GitHub advisory)
- github.com/lucasdillmann/nginx-ignitionGo
pkg:golang/github.com/lucasdillmann/nginx-ignition- Affected versions
- < 0.0.0-20260621194639-0586b4e55ab
- Fixed in
- 0.0.0-20260621194639-0586b4e55ab
| Package | Affected versions | Fixed in |
|---|---|---|
github.com/lucasdillmann/nginx-ignition Go pkg:golang/github.com/lucasdillmann/nginx-ignition |
|
|
Fix
Source: OSV (GitHub advisory)
Fix available
Upgrade to 0.0.0-20260621194639-0586b4e55ab or later.
Patch commits
Security fixes and updates (#131)
+1551 −1134 across 30 files
- .github/workflows/release.yml+1 −1
- .github/workflows/snapshot.yml+2 −2
- CHANGELOG.md+5 −2
- Dockerfile+1 −1
- Makefile+1 −0
- api/go.mod+10 −10
- api/go.sum+19 −18
- api/user/onboarding_finish_handler.go+7 −1
- and 22 more files
Release commits
The commits each fixed release tag points at. They usually bump the version rather than contain the fix itself.
Vulnerability intelligence
Root cause analysis
Vulnerable functions
References
Fixes 2
Advisories 4
Credits
- tikket1reporter
- lucasdillmannremediation developer
Questions about CVE-2026-61628
- What is the GHSA ID for CVE-2026-61628?
- GitHub tracks CVE-2026-61628 as GHSA-pxcx-fv34-x9p5.
- What is the CVSS score of CVE-2026-61628?
- CVE-2026-61628 has a CVSS 3.1 base score of 8.1 (High), vector CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H.
- Which versions are affected by CVE-2026-61628?
- github.com/lucasdillmann/nginx-ignition (Go): < 0.0.0-20260621194639-0586b4e55ab.
- How do I fix CVE-2026-61628?
- Upgrade to 0.0.0-20260621194639-0586b4e55ab or later. The fix is in commit 0586b4e and pull request #131.
- What is the Package URL (PURL) for CVE-2026-61628?
- The affected package's PURL is pkg:golang/github.com/lucasdillmann/nginx-ignition.
- Is CVE-2026-61628 exploited in the wild?
- CISA's Known Exploited Vulnerabilities catalog doesn't list CVE-2026-61628. That means no confirmed exploitation has been catalogued, not that it is safe to leave unpatched. CISA's SSVC assessment rates exploitation as "poc".
Data sources
Upstream records
- OSV (GitHub advisory)LoadedRaw JSON
- GitHub Advisory DatabaseLoadedRaw JSON
- OSV (CVE record)LoadedRaw JSON
- CVE.orgLoaded
- CISA KEV catalogLoaded
- FIRST EPSSUnavailable
Fetched 1 day ago (14 Oct 2026, 17:50 UTC). Records refresh automatically after 24 hours.
Where each part of this page came from
- Title and description
- OSV (GitHub advisory)
- Severity score
- GitHub Advisory Database
- Weakness (CWE)
- GitHub Advisory Database
- Affected packages
- OSV (GitHub advisory)
- Fix
- OSV (GitHub advisory)
- References
- Computed by CVEminer
- SSVC
- CVE.org
- KEV status
- CISA KEV catalog
OSV and GitHub Advisory Database content is licensed CC BY 4.0. Values marked as computed or derived were calculated by CVEminer from the upstream data.