{"id":"GHSA-g9g6-qhrc-p3qc","summary":"Gitea: Improper authorization on OAuth sign-in callback silently re-enables administrator-disabled accounts","details":"### Summary\n\nThe OAuth2 sign-in callback in Gitea 1.26.1 unconditionally re-enables a locally-disabled account whenever the user authenticates through a linked external identity provider, silently undoing any administrator-initiated `Disable Account` action and issuing a fresh authenticated session in the same response. Once a user has linked any external IdP, the administrator's disable toggle becomes non-binding — the user can re-enable themselves on demand by completing one OAuth callback, regaining full read and write access to their repositories, organizations, and access tokens.\n\n### Details\n\nImproper Authorization is present on the `code` and `state` parameters of the `/user/oauth2/{source-name}/callback` endpoint in Gitea version 1.26.1. The handler `routers/web/auth/oauth.go::handleOAuth2SignIn` reads the local user's `IsActive` flag at lines 350-352 and, when it is `false`, sets `opts.IsActive = optional.Some(true)` before calling `user_service.UpdateUser` and immediately establishing a session. The active-state precondition that the request middleware `routers/web/web.go::verifyAuthWithOptions` enforces for every other request is therefore evaluated against the freshly-flipped row on the next request, so a site administrator's \"disable user\" action is silently undone the next time the user authenticates through any linked external identity provider.\n\nThe root cause is that the callback path treats the local `IsActive` flag as stale bookkeeping to reconcile against the external identity, rather than as an authoritative administrative override. The local-credential sign-in path correctly renders `IsErrUserInactive` in the same condition; only the OAuth callback path flips the flag and proceeds.\n\n### PoC\n\n1. As a site administrator, configure an OAuth2 authentication source via `Site Administration -\u003e Authentication Sources -\u003e Add Authentication Source`. Any external provider works (a self-hosted Keycloak, an upstream Gitea acting as OIDC, GitHub, GitLab, Microsoft, Google).\n2. Have a normal user (`alice` in the example) sign in through that source at least once. The callback creates the row in `external_login_user` linking alice's local account to the IdP identity.\n3. As the site administrator, disable alice's account: `Site Administration -\u003e User Accounts -\u003e alice -\u003e \"Disable Account\"`. Confirm the flag is set with the admin API.\n\n#### HTTP REQUEST\n\n```http\nGET /api/v1/admin/users/alice HTTP/1.1\nHost: \u003cHOST\u003e\nAuthorization: token \u003cADMIN_TOKEN\u003e\nAccept: application/json\n```\n\n#### HTTP RESPONSE\n\n```http\nHTTP/1.1 200 OK\nContent-Type: application/json;charset=utf-8\n\n{\"id\":2,\"login\":\"alice\",\"active\":false,...}\n```\n\n4. Have alice click \"Sign in with \u003csource-name\u003e\" on the Gitea login page and complete the OAuth dance with the IdP. The callback handler runs, observes `IsActive=false`, sets `opts.IsActive=true`, calls `UpdateUser`, and issues a session on the same response. Alice lands on `/`.\n5. Re-read the same admin API endpoint and observe that `active` has flipped back to `true` without administrator action.\n\n#### HTTP REQUEST\n\n```http\nGET /api/v1/admin/users/alice HTTP/1.1\nHost: \u003cHOST\u003e\nAuthorization: token \u003cADMIN_TOKEN\u003e\nAccept: application/json\n```\n\n#### HTTP RESPONSE\n\n```http\nHTTP/1.1 200 OK\nContent-Type: application/json;charset=utf-8\n\n{\"id\":2,\"login\":\"alice\",\"active\":true,...}\n```\n\n### Impact\n\nOnce a user has linked any external identity provider, the site administrator's \"disable user\" toggle becomes non-binding on that account: the user can re-enable themselves on demand by completing one OAuth callback, regaining full read and write access to their repositories, organizations, and tokens. In SSO-fronted Gitea deployments where the disable toggle is the documented incident-response action for compromised accounts or departed employees, this defeats the only short-term remediation available before the identity provider can revoke the underlying federated identity.","aliases":["CVE-2026-58422","GO-2026-6056"],"modified":"2026-07-22T21:39:19.190446526Z","published":"2026-07-21T21:48:48Z","database_specific":{"cwe_ids":["CWE-284"],"severity":"HIGH","github_reviewed":true,"github_reviewed_at":"2026-07-21T21:48:48Z","nvd_published_at":"2026-07-03T21:17:05Z"},"references":[{"type":"WEB","url":"https://github.com/go-gitea/gitea/security/advisories/GHSA-g9g6-qhrc-p3qc"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-58422"},{"type":"WEB","url":"https://github.com/go-gitea/gitea/pull/38009"},{"type":"WEB","url":"https://github.com/go-gitea/gitea/commit/c43eb7c33a100ffc7b2367adf165f7085e0ccdc5"},{"type":"WEB","url":"https://blog.gitea.com/release-of-1.26.3-and-1.26.4"},{"type":"PACKAGE","url":"https://github.com/go-gitea/gitea"},{"type":"WEB","url":"https://github.com/go-gitea/gitea/releases/tag/v1.26.4"}],"affected":[{"package":{"name":"code.gitea.io/gitea","ecosystem":"Go","purl":"pkg:golang/code.gitea.io/gitea"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.26.4"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-g9g6-qhrc-p3qc/GHSA-g9g6-qhrc-p3qc.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N"}]}