# Nginx-UI: Unauthenticated First-Run Installer Allows Remote Initial Admin Claim

Unauthenticated POST /api/install on fresh Nginx-UI instances lets a remote attacker set the initial admin credentials and permanently take over the instance.

- Payout: Research
- Severity: high
- Program: Nginx-UI
- Found by: R1ZZG0D
- Disclosed: 2026-05-06
- CVE: CVE-2026-42221
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-h27v-ph7w-m9fp
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-h27v-ph7w-m9fp
- Tags: area:web domain:web impact:ato impact:privesc kind:research platform:ghsa sev:high tech:go technique:source-review vuln:access-control vuln:auth-bypass vuln:misconfiguration

**Target:** Nginx-UI HTTP API installer endpoint (/api/install)
**Root cause:** The /api/install route is mounted without AuthRequired() and only gates on 'already installed' plus a 10-minute startup window; the encrypted_params flow provides confidentiality but no authentication of the caller.
**Steps:**
1. `GET /api/install` returns `{"lock":false,"timeout":false}` on a fresh instance
2. `POST /api/crypto/public_key` to fetch the server RSA public key
3. Encrypt `{"email":"attacker@example.com","username":"attacker","password":"Password12345"}` with that key, base64-encode
4. `POST /api/install` with `{"encrypted_params":"..."}` returns `{"message":"ok"}`
5. `GET /api/install` now returns `{"lock":true,"timeout":false}`
6. `sqlite3 database.db 'select id,name,length(password) from users where id=1;'` shows `1|attacker|60`
**How found:** Code review of router mounting: noticed system.InitPublicRouter registered GET/POST /api/install without AuthRequired(), then traced install.go to confirm the only guards were JwtSecret emptiness and a 10-minute timer, and that the crypto middleware only decrypts rather than authenticates.
**Exploitation:** Attacker who reaches a fresh instance within the 10-minute startup window fetches the public key, encrypts chosen admin credentials, and posts them to /api/install; the handler generates JWT/node secrets and overwrites user ID 1, giving permanent admin control of the Nginx management interface.
**Escalation:** Admin control of nginx-ui yields unauthorized Nginx config changes, certificate management abuse, backup manipulation and broader host operational takeover.
**Insight:** First-run/bootstrap endpoints are a recurring auth gap: check whether 'install'/'setup' routes are mounted on the public router and whether their only guard is a time window or an 'already initialized' flag — the encryption layer often gives a false sense of authentication.

## Proof of concept (copied exactly from the original report)

**Helper script claiming initial admin account**

```
ATTACKER_EMAIL='attacker@example.com' ATTACKER_USER='attacker' ATTACKER_PASS='Password12345' \
'/Users/r1zzg0d/Documents/CVE hunting/targets/nginx-ui/output/verify/verify_fresh_install_takeover.sh'
```

**sqlite3 query confirming overwritten admin record**

```
sqlite3 '/Users/r1zzg0d/Documents/CVE hunting/targets/nginx-ui/tmp/poc-install-takeover/database.db' \
'select id,name,length(password) from users where id=1;'
```

