Nginx UI: Casdoor SSO callback uses a constant `state` parameter (login CSRF)
CVE-2026-84312 · GHSA-44xr-mrhh-qm78 · GO-2026-6773 · published 15 Oct 2026
How serious is it
- Severity: high, CVSS score 7.1 out of 10.
- No exploit prediction (EPSS) score yet.
- Not on the CISA list of exploited vulnerabilities.
- No public exploit in Exploit-DB.
Is your app affected by CVE-2026-84312?
Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.
Who is affected
Nginx-UI(Go) < 1.9.10-0.20260703131214-23d1934099b0
How to check
- Run
go list -m Nginx-UIin your project to see the installed version, including copies pulled in by other packages. - Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.
How to fix
Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
- No fixed version of
Nginx-UIyet. Replace or remove it, or apply the workaround in the advisory below. - If a vulnerable version was live, check your logs and rotate the secrets that server could read.
The advisory
## Summary
In the Casdoor SSO flow, the OAuth `state` parameter is hard-coded to `settings.CasdoorSettings.Application` — a value identical for every user and every login. The callback handler accepts whatever `state` the client sends without comparing it to anything stored in the user agent, enabling a login-CSRF: an attacker can force the victim's browser to authenticate as the *attacker*.
## Details
`api/user/casdoor.go:92-117` (`GetCasdoorUri`):
```go
func GetCasdoorUri(c *gin.Context) {
clientId := settings.CasdoorSettings.ClientId
redirectUri := settings.CasdoorSettings.RedirectUri
state := settings.CasdoorSettings.Application // ← constant
...
c.JSON(http.StatusOK, gin.H{
"uri": fmt.Sprintf(
"%s/login/oauth/authorize?client_id=%s&response_type=code&redirect_uri=%s&state=%s&scope=read",
endpoint, clientId, encodedRedirectUri, state),
})
}
```
`api/user/casdoor.go:24-90` (`CasdoorCallback`):
```go
type CasdoorLoginUser struct {
Code string `json:"code" binding:"required,max=255"`
State string `json:"state" binding:"required,max=255"`
}
func CasdoorCallback(c *gin.Context) {
var loginUser CasdoorLoginUser
if !cosy.BindAndValid(c, &loginUser) { return }
...
// state is NEVER compared to a per-session value
token, err := casdoorsdk.GetOAuthToken(loginUser.Code, loginUser.State)
...
u, err := user.GetUser(claims.Name) // username from Casdoor's JWT
...
userToken, err := user.GenerateJWT(u) // JWT minted for whatever user came back
}
```
Compare with the OIDC implementation in `api/user/oidc.go:35-48`, which generates a random state, stores it in an `oidc_state` cookie, and validates it on the callback. Casdoor has no such check.
## Proof of Concept
1. Attacker initiates a Casdoor authorisation flow on their own browser, obtaining an authorisation `code` for *their* Casdoor account.
2. Attacker hosts a page that auto-submits the captured `code` to the victim's nginx-ui:
```html
<html><body><form action="https://victim.example/api/casdoor_callback" method="POST"
enctype="text/plain" id="x">
<input name='{"code":"<attacker_code>","state":"<App>","junk":"' value='"}'>
</form>
<script>document.getElementById('x').submit()</script>
</body></html>
```
3. Server exchanges the attacker's `code`, looks up the username from `claims.Name` in the local user table, and mints a JWT for *that* row. The callback also sets the `_nginx_ui_secure_session` cookie via `middleware.EnsureSecureSessionCookie(c)` (`casdoor.go:84`) — meaning the victim is now silently authenticated as the attacker.
4. Victim, believing they are using their own account, configures sites / TLS keys; the attacker reads them.
## Impact
Login CSRF — the victim is forced to authenticate as the attacker, leading to data exfiltration of anything the victim subsequently uploads or configures. Particularly dangerous when nginx-ui is used by an organisation where Casdoor maps internal identities — the attacker can attribute their changes to the victim.
## Patches
Mirror the OIDC implementation:
```diff
+ import (
+ "crypto/rand"
+ "crypto/subtle"
+ "encoding/hex"
+ ...
+ )
func GetCasdoorUri(c *gin.Context) {
...
- state := settings.CasdoorSettings.Application
+ b := make([]byte, 16)
+ if _, err := rand.Read(b); err != nil {
+ cosy.ErrHandler(c, err); return
+ }
+ state := hex.EncodeToString(b)
+ c.SetSameSite(http.SameSiteLaxMode)
+ c.SetCookie("casdoor_state", state, 600, "/", "",
+ cSettings.ServerSettings.EnableHTTPS, true)
...
}
func CasdoorCallback(c *gin.Context) {
+ expected, err := c.Cookie("casdoor_state")
+ if err != nil {
+ c.AbortWithStatusJSON(http.StatusBadRequest,
+ gin.H{"message":"missing state cookie"}); return
+ }
var loginUser CasdoorLoginUser
if !cosy.BindAndValid(c, &loginUser) { return }
+ if subtle.ConstantTimeCompare([]byte(expected), []byte(loginUser.State)) != 1 {
+ c.AbortWithStatusJSON(http.StatusBadRequest,
+ gin.H{"message":"invalid state"}); return
+ }
...
}
```
## Workarounds
Disable Casdoor SSO until patched. The OIDC integration in `api/user/oidc.go` is correctly implemented and can be used as a substitute.
## References
* [`api/user/casdoor.go:92-117`](https://github.com/0xJacky/nginx-ui/blob/dev/api/user/casdoor.go#L92-L117)
* [`api/user/casdoor.go:24-90`](https://github.com/0xJacky/nginx-ui/blob/dev/api/user/casdoor.go#L24-L90)
* [`api/user/oidc.go:35-48`](https://github.com/0xJacky/nginx-ui/blob/dev/api/user/oidc.go#L35-L48) (correct implementation for comparison)
## Credits
Vishal Shukla ([@shukla304](https://github.com/shukla304)) and Sechub.dev AI Agent.
Check your whole app
Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.
References
- github.com/0xJacky/nginx-ui/security/advisories/GHSA-44xr-mrhh-qm78
- nvd.nist.gov/vuln/detail/CVE-2026-84312
- github.com/0xJacky/nginx-ui/commit/23d1934099b05b2a01589b48129dc3d3159d91c2
- github.com/0xJacky/nginx-ui/releases/tag/v2.4.1
- github.com/advisories/GHSA-44xr-mrhh-qm78
Merged from GitHub Advisory Database, OSV. Last updated 23 Oct 2026. All vulnerabilities · RSS · JSON